diff --git a/README.md b/README.md index 05969d3..7c6910e 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,37 @@ uvicorn main:app --reload flutter run ``` +### Method E: Dashboard Access (Web Interface) +```bash +# Terminal 1 - Start Backend Server +cd backend +python main.py +# OR (if port conflicts occur): +uvicorn main:app --host 127.0.0.1 --port 8000 --reload + +# Terminal 2 - Start Dashboard Server +cd dashboard +python -m http.server 3000 + +# Open browser and navigate to: +# http://localhost:3000 +``` + +**Dashboard Features:** +- πŸ—ΊοΈ Interactive map with report visualization +- πŸ” Advanced filtering by category, severity, status, and date +- πŸ“Š Real-time statistics and analytics +- 🌑️ Heatmap toggle for density visualization +- πŸ”„ Status management (submitted β†’ in_progress β†’ fixed) +- πŸ“± Responsive design for desktop and mobile +- 🌍 Bilingual support (English/Bahasa Malaysia) + +**Troubleshooting Dashboard:** +- **Port 8000 in use?** Try: `uvicorn main:app --host 127.0.0.1 --port 8080 --reload` (then update dashboard to use port 8080) +- **Port 3000 in use?** Try: `python -m http.server 3001` +- **Backend connection fails?** Dashboard will automatically use demo data +- **CORS issues?** Ensure backend allows requests from `http://localhost:3000` + ## πŸ“± API Endpoints The Flutter app communicates with these backend endpoints: @@ -249,6 +280,7 @@ flutter config --enable-web - Ensure backend server is running on port 8000 - Check firewall settings - Verify API base URL in `lib/services/api_service.dart` +- For dashboard: Check browser console for CORS errors **Camera/Geolocation not working:** - Grant permissions in device settings diff --git a/backend/app/routes/report.py b/backend/app/routes/report.py index 6c7a0ad..d5206ae 100644 --- a/backend/app/routes/report.py +++ b/backend/app/routes/report.py @@ -11,7 +11,7 @@ router = APIRouter() logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -UPLOAD_DIR = "app/static/uploads" +UPLOAD_DIR = "static/uploads" os.makedirs(UPLOAD_DIR, exist_ok=True) @router.post("/report") @@ -33,8 +33,23 @@ async def report_issue( raise HTTPException(status_code=404, detail=f"User with id {user_id} not found") logger.debug(f"User found: {user.name} ({user.email})") + # Validate file type + allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'} + allowed_content_types = { + 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp', + 'application/octet-stream' # Some cameras/mobile devices use this + } + + file_ext = os.path.splitext(image.filename.lower())[1] + if file_ext not in allowed_extensions: + logger.error(f"Invalid file extension: {file_ext}") + raise HTTPException(status_code=400, detail="Only image files are allowed") + + if image.content_type not in allowed_content_types: + logger.error(f"Invalid content type: {image.content_type}") + raise HTTPException(status_code=400, detail="Invalid file type") + # Save uploaded image - file_ext = os.path.splitext(image.filename)[1] filename = f"{uuid.uuid4()}{file_ext}" file_path = os.path.join(UPLOAD_DIR, filename) try: diff --git a/backend/app/static/uploads/a5b48fdf-07d4-4274-b5c3-961fc31ae81e.py b/backend/app/static/uploads/a5b48fdf-07d4-4274-b5c3-961fc31ae81e.py deleted file mode 100644 index a7006b7..0000000 --- a/backend/app/static/uploads/a5b48fdf-07d4-4274-b5c3-961fc31ae81e.py +++ /dev/null @@ -1,74 +0,0 @@ -import uuid -from sqlalchemy import Column, String, Float, Enum, DateTime, ForeignKey, Index -from sqlalchemy.orm import relationship -from sqlalchemy.sql import func -from app.database import Base -import enum - -# ---------------------- -# Enums -# ---------------------- -class TicketStatus(str, enum.Enum): - NEW = "New" - IN_PROGRESS = "In Progress" - FIXED = "Fixed" - -class SeverityLevel(str, enum.Enum): - LOW = "Low" - MEDIUM = "Medium" - HIGH = "High" - NA = "N/A" - -# ---------------------- -# User Model -# ---------------------- -class User(Base): - __tablename__ = "users" - - id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()), index=True) - name = Column(String, nullable=False) - email = Column(String, unique=True, nullable=False) - - tickets = relationship("Ticket", back_populates="user", cascade="all, delete-orphan") - - def __repr__(self): - return f"" - -# ---------------------- -# Ticket Model -# ---------------------- -class Ticket(Base): - __tablename__ = "tickets" - - id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()), index=True) - user_id = Column(String, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) - image_path = Column(String, nullable=False) - category = Column(String, nullable=False) - severity = Column(Enum(SeverityLevel), nullable=False, default=SeverityLevel.NA) - description = Column(String, default="") - status = Column(Enum(TicketStatus), nullable=False, default=TicketStatus.NEW) - latitude = Column(Float, nullable=False) - longitude = Column(Float, nullable=False) - created_at = Column(DateTime(timezone=True), server_default=func.now()) - updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) - - user = relationship("User", back_populates="tickets") - - __table_args__ = ( - Index("idx_category_status", "category", "status"), - ) - - def __repr__(self): - return f"" - -# ---------------------- -# Ticket Audit Model -# ---------------------- -class TicketAudit(Base): - __tablename__ = "ticket_audit" - - id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) - ticket_id = Column(String, ForeignKey("tickets.id", ondelete="CASCADE")) - old_status = Column(Enum(TicketStatus)) - new_status = Column(Enum(TicketStatus)) - updated_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/backend/main.py b/backend/main.py index d2d8ff2..a08b97d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -67,6 +67,7 @@ print("βœ… FastAPI server setup complete") # Start the server when running this script directly if __name__ == "__main__": import uvicorn - print("πŸš€ Starting server on http://127.0.0.1:8000") + print("πŸš€ Starting server on http://0.0.0.0:8000") print("πŸ“š API documentation available at http://127.0.0.1:8000/docs") - uvicorn.run(app, host="127.0.0.1", port=8000) + print("πŸ”— Also accessible from mobile/emulator at http://192.168.100.59:8000") + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 189f2d0..1915220 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -7,8 +7,9 @@ import '../models/enums.dart'; /// Service for communicating with the FixMate Backend API class ApiService { // Configure this to match your backend URL - static const String _baseUrl = 'http://127.0.0.1:8000/api'; - static const String _uploadsUrl = 'http://127.0.0.1:8000/static/uploads'; + // Use localhost for web/desktop, network IP for mobile/emulator + static const String _baseUrl = 'http://192.168.100.59:8000/api'; + static const String _uploadsUrl = 'http://192.168.100.59:8000/static/uploads'; // Create a user ID for this device if not exists static Future _getOrCreateUserId() async {