From 7cb7b68446f68ca8376fa22bc2af82c7ee150f68 Mon Sep 17 00:00:00 2001 From: Zahar Date: Sat, 27 Sep 2025 10:27:25 +0800 Subject: [PATCH] feat(api,ui): enhance ticket data mapping and add test endpoint; improve error handling and logging --- backend/app/db/fixmate.db | Bin 45056 -> 45056 bytes backend/app/utils.py | 33 +++++++++++--- backend/main.py | 10 +++-- dashboard/app.js | 88 +++++++++++++++++++++++--------------- 4 files changed, 87 insertions(+), 44 deletions(-) diff --git a/backend/app/db/fixmate.db b/backend/app/db/fixmate.db index 0b2cb5676efdc5e2f715909030cb645ffa62ce58..5a6fa7a648091caf278d6c393202e02a894edefd 100644 GIT binary patch delta 159 zcmZp8z|`=7X@WE(|3n#QM*fWn3;lWd1sIrk^BDN-dGmPlHVXlO#E#O{B8W}`PXk2EU4h0+?F2%lGm855U;|=#s3kg;35P6MgE8U4>t== hILtr!V!eVaPys(@XkK<+etDjsYd9AHa~4fd001yYE*$^> delta 159 zcmZp8z|`=7X@WE(-$WT_M!t;+3;lT+7#Ns&^BDLA`0RP}HVXEHwZC diff --git a/backend/app/utils.py b/backend/app/utils.py index b58bdfa..2a9f13d 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -82,16 +82,37 @@ def ticket_to_dict(ticket, request=None) -> dict: logger.exception("Failed to build image_url") image_url = None + # Map backend enum values to dashboard expected values + severity_mapping = { + "N/A": "low", + "Low": "low", + "Medium": "medium", + "High": "high" + } + + status_mapping = { + "New": "submitted", + "In Progress": "in_progress", + "Fixed": "fixed" + } + + # Map category to expected values + category_mapping = { + "Unknown": "other", + "garbage": "trash" + } + return { "id": ticket.id, - "category": ticket.category, - "severity": ticket.severity.value if getattr(ticket, "severity", None) else None, - "status": ticket.status.value if getattr(ticket, "status", None) else None, - "description": ticket.description, + "category": category_mapping.get(ticket.category, ticket.category) if ticket.category else "other", + "severity": severity_mapping.get(ticket.severity.value, "low") if getattr(ticket, "severity", None) else "low", + "status": status_mapping.get(ticket.status.value, "submitted") if getattr(ticket, "status", None) else "submitted", + "notes": ticket.description, # Map description to notes "user_id": ticket.user_id, - "user_name": ticket.user.name if getattr(ticket, "user", None) else None, + "userName": ticket.user.name if getattr(ticket, "user", None) else None, "user_email": ticket.user.email if getattr(ticket, "user", None) else None, - "created_at": created, + "createdAt": created, # Map created_at to createdAt + "updatedAt": getattr(ticket, "updated_at", None).isoformat() if getattr(ticket, "updated_at", None) else created, "latitude": ticket.latitude, "longitude": ticket.longitude, "address": ticket.address, diff --git a/backend/main.py b/backend/main.py index 52576ac..d1ef0a3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -48,17 +48,17 @@ app.mount("/static", StaticFiles(directory="static"), name="static") # ---------------------- # CORS - allow dashboard & emulator origins # ---------------------- -DEFAULT_ORIGINS = "http://localhost:3000,http://127.0.0.1:3000,http://10.0.2.2:3000,http://192.168.100.59:3000" +DEFAULT_ORIGINS = "http://localhost:3000,http://127.0.0.1:3000,http://[::1]:3000,http://10.0.2.2:3000,http://192.168.100.59:3000" origins_env = os.environ.get("FIXMATE_CORS_ORIGINS", DEFAULT_ORIGINS) allowed_origins = [o.strip() for o in origins_env.split(",") if o.strip()] # Ensure common development origins are always allowed (localhost, emulator, LAN) -for origin in ("http://localhost:3000", "http://127.0.0.1:3000", "http://10.0.2.2:3000", "http://192.168.100.59:3000"): +for origin in ("http://localhost:3000", "http://127.0.0.1:3000", "http://[::1]:3000", "http://10.0.2.2:3000", "http://192.168.100.59:3000"): if origin not in allowed_origins: allowed_origins.append(origin) app.add_middleware( CORSMiddleware, - allow_origins=allowed_origins, + allow_origins=["*"], # Allow all origins for development allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -82,6 +82,10 @@ except Exception as e: def root(): return {"message": "Welcome to FixMate Backend API! Visit /docs for API documentation."} +@app.get("/test") +def test(): + return {"status": "Backend is working", "timestamp": "2025-09-27T10:12:41"} + print("✅ FastAPI server setup complete") # Start the server when running this script directly diff --git a/dashboard/app.js b/dashboard/app.js index d1475bb..ae09862 100644 --- a/dashboard/app.js +++ b/dashboard/app.js @@ -2,10 +2,12 @@ const { useState, useEffect, useRef, useMemo } = React; dayjs.extend(window.dayjs_plugin_relativeTime); -const CATEGORY_LIST = ['pothole','streetlight','signage','trash','drainage','other']; +console.log('Dashboard app.js loaded, BACKEND_BASE:', BACKEND_BASE); + +const CATEGORY_LIST = ['pothole','streetlight','signage','trash','garbage','drainage','other']; const SEVERITIES = ['high','medium','low']; const STATUSES = ['submitted','in_progress','fixed']; -const BACKEND_BASE = "http://192.168.100.59:8000"; +const BACKEND_BASE = "http://127.0.0.1:8000"; const SEVERITY_COLOR = { high:'#D32F2F', medium:'#F57C00', low:'#388E3C' }; const STATUS_COLOR = { submitted:'#1976D2', in_progress:'#7B1FA2', fixed:'#455A64' }; @@ -14,49 +16,40 @@ function fetchJSON(path){ return fetch(path).then(r=>r.json()); } // Fetch tickets from backend async function fetchTickets(){ - const res = await fetch(`${BACKEND_BASE}/api/tickets`); - if(!res.ok) throw new Error('Failed to fetch tickets'); - const data = await res.json(); - return data; + console.log('Fetching tickets from:', `${BACKEND_BASE}/api/tickets`); + try { + const res = await fetch(`${BACKEND_BASE}/api/tickets`); + console.log('Response status:', res.status); + if(!res.ok) throw new Error(`Failed to fetch tickets: ${res.status}`); + const data = await res.json(); + console.log('Fetched data:', data); + return data; + } catch (error) { + console.error('Error fetching tickets:', error); + throw error; + } } // Normalize API data to expected format function normalizeReportData(report) { - // Already normalized demo format (has location.lat) - if (report.location && report.location.lat !== undefined) { - return { - id: report.id || report.ticket_id || report.ticketId, - category: report.category || 'other', - severity: report.severity || 'low', - status: report.status || 'submitted', - notes: report.notes || report.description || '', - location: report.location, - createdAt: report.createdAt || report.created_at, - updatedAt: report.updatedAt || report.updated_at, - userId: report.userId || report.user_id, - userName: report.userName || report.user_name || null, - address: report.address || null, - image_url: report.image_url || report.imagePath || report.image_path || null - }; - } - - // Convert backend API format to the app format + // Backend is already returning data in correct format + // Just ensure all required fields are present return { - id: report.id || report.ticket_id || report.ticketId, + id: report.id, category: report.category || 'other', severity: report.severity || 'low', status: report.status || 'submitted', - notes: report.description || report.notes || '', + notes: report.notes || '', location: { - lat: (report.latitude !== undefined ? report.latitude : (report.lat !== undefined ? report.lat : null)), - lng: (report.longitude !== undefined ? report.longitude : (report.lng !== undefined ? report.lng : null)) + lat: report.latitude, + lng: report.longitude }, - createdAt: report.created_at || report.createdAt, - updatedAt: report.updated_at || report.updatedAt, - userId: report.user_id || report.userId, - userName: report.user_name || report.userName || null, + createdAt: report.createdAt, + updatedAt: report.updatedAt, + userId: report.user_id, + userName: report.userName || null, address: report.address || null, - image_url: report.image_url || report.image_path || report.imagePath || null + image_url: report.image_url || null }; } @@ -153,16 +146,35 @@ function App(){ const PLACEHOLDER_SRC = 'data:image/svg+xml;utf8,' + encodeURIComponent('No image'); useEffect(()=>{ + console.log('Dashboard useEffect triggered - about to fetch tickets'); + + // First test if we can reach the backend at all + fetch(`${BACKEND_BASE}/test`) + .then(response => { + console.log('Test endpoint response:', response.status); + return response.json(); + }) + .then(testData => { + console.log('Test data:', testData); + }) + .catch(testErr => { + console.error('Test request failed:', testErr); + }); + setLoading(true); fetchTickets() .then(data => { console.log('Loaded data from backend:', (Array.isArray(data) ? data.length : 0), 'reports'); + console.log('Raw data received:', data); const normalizedData = (data || []).map(normalizeReportData); + console.log('Normalized data:', normalizedData); + console.log('Sample normalized item:', JSON.stringify(normalizedData[0], null, 2)); setRawData(normalizedData); setLoading(false); }) .catch(err => { - console.warn('Failed to load tickets from backend:', err); + console.error('Failed to load tickets from backend:', err); + console.error('Error details:', err.message, err.stack); showToast('Failed to load tickets from backend.'); setRawData([]); setLoading(false); @@ -196,6 +208,12 @@ function App(){ if(!appliedFilters.statuses.has(r.status)) return false; return true; }); + console.log('Filtered data:', out.length, 'tickets'); + console.log('Applied filters:', { + categories: Array.from(appliedFilters.categories), + severities: Array.from(appliedFilters.severities), + statuses: Array.from(appliedFilters.statuses) + }); setFiltered(out); },[rawData, appliedFilters]);