feat(api,ui): enhance ticket data mapping and add test endpoint; improve error handling and logging
This commit is contained in:
Binary file not shown.
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(){
|
||||
console.log('Fetching tickets from:', `${BACKEND_BASE}/api/tickets`);
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_BASE}/api/tickets`);
|
||||
if(!res.ok) throw new Error('Failed to fetch 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) {
|
||||
// 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.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
|
||||
return {
|
||||
id: report.id || report.ticket_id || report.ticketId,
|
||||
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('<svg xmlns="http://www.w3.org/2000/svg" width="120" height="90"><rect width="100%" height="100%" fill="#e5e7eb"/><text x="50%" y="50%" dy=".3em" font-size="12" text-anchor="middle" fill="#6b7280">No image</text></svg>');
|
||||
|
||||
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]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user