"use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; import { AlertCircle, Activity, MapPin as MapPinIcon, BarChart3, AlertTriangle, Zap, PencilLine, CheckCircle2, Settings2, } from "lucide-react"; import { StatsCard } from "@/components/dashboard/stats-card"; import { CompactProjectSelector } from "@/components/dashboard/compact-project-selector"; import { FilterSelector } from "@/components/dashboard/filter-selector"; import { DetectionDonutChart } from "@/components/dashboard/detection-donut-chart"; import { LocationBarChart } from "@/components/dashboard/location-bar-chart"; import { DashboardMap } from "@/components/dashboard/dashboard-map"; import { PageHeader } from "@/components/page-header"; import { fetchProjects, type Project } from "@/lib/api"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { DashboardSkeleton } from "@/components/dashboard/dashboard-skeleton" import { PoweredBy } from "@/components/powered-by"; import { toast } from "sonner"; import { Reveal, StaggerContainer, StaggerItem } from "@/components/ui/reveal"; const API_URL = process.env.NEXT_PUBLIC_API_URL; interface ProjectSummary { project: { id: string; name: string; corridor_name: string | null; state: string | null; }; packages: { [key: string]: { package_id: string; region: string | null; locations: { [key: string]: { location_id: string; chainage: string | null; detection_count: number; detections: Array<{ id: number; type: string; class: string; confidence: number; latitude: number; longitude: number; }>; }; }; }; }; } interface DetectionStats { totalDefectedSignboard: number; totalPothole: number; totalRoadCrack: number; totalDamagedRoadMarking: number; totalGoodSignboard: number; totalRoadDamage: number; locationData: Array<{ name: string; defected_sign_board: number; pothole: number; road_crack: number; damaged_road_marking: number; good_sign_board: number; total: number; }>; } function calculateStats(summary: ProjectSummary | null): DetectionStats { if (!summary) { return { totalDefectedSignboard: 0, totalPothole: 0, totalRoadCrack: 0, totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalRoadDamage: 0, locationData: [], }; } let totalDefectedSignboard = 0; let totalPothole = 0; let totalRoadCrack = 0; let totalDamagedRoadMarking = 0; let totalGoodSignboard = 0; let totalRoadDamage = 0; const locationData: DetectionStats["locationData"] = []; for (const pkg of Object.values(summary.packages || {})) { for (const [locName, loc] of Object.entries(pkg.locations || {})) { let locDefectedSignboard = 0; let locPothole = 0; let locRoadCrack = 0; let locDamagedRoadMarking = 0; let locGoodSignboard = 0; for (const detection of loc.detections || []) { const type = detection.type.toLowerCase(); if (type === "defected_sign_board") { locDefectedSignboard++; totalDefectedSignboard++; } else if (type === "pothole") { locPothole++; totalPothole++; } else if (type === "road_crack") { locRoadCrack++; totalRoadCrack++; } else if (type === "damaged_road_marking") { locDamagedRoadMarking++; totalDamagedRoadMarking++; } else if (type === "good_sign_board") { locGoodSignboard++; totalGoodSignboard++; } } const locTotalDamage = locDefectedSignboard + locPothole + locRoadCrack + locDamagedRoadMarking; totalRoadDamage += locDefectedSignboard > 0 || locPothole > 0 || locRoadCrack > 0 || locDamagedRoadMarking > 0 ? 1 : 0; // This logic might need refinement based on "total_road_damage" definition if ( locDefectedSignboard > 0 || locPothole > 0 || locRoadCrack > 0 || locDamagedRoadMarking > 0 || locGoodSignboard > 0 ) { const shortName = locName.length > 20 ? locName.substring(0, 20) + "..." : locName; locationData.push({ name: shortName, defected_sign_board: locDefectedSignboard, pothole: locPothole, road_crack: locRoadCrack, damaged_road_marking: locDamagedRoadMarking, good_sign_board: locGoodSignboard, total: locDefectedSignboard + locPothole + locRoadCrack + locDamagedRoadMarking + locGoodSignboard, }); } } } // Recalculate totalRoadDamage based on backends unique count // But since we are aggregating from locations, we just sum them up or use a simpler metric // The user's JSON shows "total_road_damage": 9 which is sum of 2+6+0+1 totalRoadDamage = totalDefectedSignboard + totalPothole + totalRoadCrack + totalDamagedRoadMarking; return { totalDefectedSignboard, totalPothole, totalRoadCrack, totalDamagedRoadMarking, totalGoodSignboard, totalRoadDamage, locationData, }; } export default function DashboardPage() { const [isLoading, setIsLoading] = useState(true); const [projects, setProjects] = useState([]); const [selectedProjectId, setSelectedProjectId] = useState( null, ); const [projectSummary, setProjectSummary] = useState( null, ); const [stats, setStats] = useState({ totalDefectedSignboard: 0, totalPothole: 0, totalRoadCrack: 0, totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalRoadDamage: 0, locationData: [], }); // Load projects on mount useEffect(() => { const loadProjects = async () => { try { setIsLoading(true); const projectsData = await fetchProjects(); setProjects(projectsData.items); if (projectsData.items.length > 0) { setSelectedProjectId(projectsData.items[0].id); } } catch (err) { console.error("Failed to load projects:", err); toast.error("Failed to load projects", { description: "Please check if the backend is running." }); } finally { setTimeout(() => { setIsLoading(false); }, 1500); } }; loadProjects(); }, []); const selectedProject = projects.find((p) => p.id === selectedProjectId); // Extract packages from project summary const packages = projectSummary ? Object.keys(projectSummary.packages || {}).map((pkgName) => ({ id: pkgName, name: pkgName, })) : []; // Extract locations from selected package const [selectedPackageId, setSelectedPackageId] = useState( null, ); const [selectedLocationId, setSelectedLocationId] = useState( null, ); const locations = projectSummary && selectedPackageId && selectedPackageId !== "all" ? Object.keys( projectSummary.packages[selectedPackageId]?.locations || {}, ).map((locName) => ({ id: locName, name: locName, })) : []; // Load project summary when project changes useEffect(() => { if (!selectedProjectId) { setProjectSummary(null); return; } const loadProjectSummary = async () => { try { setIsLoading(true); const response = await fetch( `${API_URL}/summary/projects/${selectedProjectId}`, { headers: { "Content-Type": "application/json", "ngrok-skip-browser-warning": "true", }, }, ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const summary: ProjectSummary = await response.json(); setProjectSummary(summary); } catch (err) { console.error("Failed to load project summary:", err); toast.error("Failed to load project summary"); setProjectSummary(null); } finally { setTimeout(() => { setIsLoading(false); }, 1500); } }; loadProjectSummary(); }, [selectedProjectId]); // Reset package and location when project changes useEffect(() => { setSelectedPackageId(null); setSelectedLocationId(null); }, [selectedProjectId]); // Reset location when package changes useEffect(() => { setSelectedLocationId(null); }, [selectedPackageId]); // Filter stats based on selections useEffect(() => { if (!projectSummary) { setStats({ totalDefectedSignboard: 0, totalPothole: 0, totalRoadCrack: 0, totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalRoadDamage: 0, locationData: [], }); return; } let totalDefectedSignboard = 0; let totalPothole = 0; let totalRoadCrack = 0; let totalDamagedRoadMarking = 0; let totalGoodSignboard = 0; const locationData: DetectionStats["locationData"] = []; const packagesToProcess = selectedPackageId && selectedPackageId !== "all" ? { [selectedPackageId]: projectSummary.packages[selectedPackageId] } : projectSummary.packages || {}; for (const [pkgName, pkg] of Object.entries(packagesToProcess)) { const locationsToProcess = selectedLocationId && selectedLocationId !== "all" ? { [selectedLocationId]: pkg.locations[selectedLocationId] } : pkg.locations || {}; for (const [locName, loc] of Object.entries(locationsToProcess)) { if (!loc) continue; let locDefectedSignboard = 0; let locPothole = 0; let locRoadCrack = 0; let locDamagedRoadMarking = 0; let locGoodSignboard = 0; for (const detection of loc.detections || []) { const type = detection.type.toLowerCase(); if (type === "defected_sign_board") { locDefectedSignboard++; totalDefectedSignboard++; } else if (type === "pothole") { locPothole++; totalPothole++; } else if (type === "road_crack") { locRoadCrack++; totalRoadCrack++; } else if (type === "damaged_road_marking") { locDamagedRoadMarking++; totalDamagedRoadMarking++; } else if (type === "good_sign_board") { locGoodSignboard++; totalGoodSignboard++; } } if ( locDefectedSignboard > 0 || locPothole > 0 || locRoadCrack > 0 || locDamagedRoadMarking > 0 || locGoodSignboard > 0 ) { const shortName = locName.length > 20 ? locName.substring(0, 20) + "..." : locName; locationData.push({ name: shortName, defected_sign_board: locDefectedSignboard, pothole: locPothole, road_crack: locRoadCrack, damaged_road_marking: locDamagedRoadMarking, good_sign_board: locGoodSignboard, total: locDefectedSignboard + locPothole + locRoadCrack + locDamagedRoadMarking + locGoodSignboard, }); } } } setStats({ totalDefectedSignboard, totalPothole, totalRoadCrack, totalDamagedRoadMarking, totalGoodSignboard, totalRoadDamage: totalDefectedSignboard + totalPothole + totalRoadCrack + totalDamagedRoadMarking, locationData, }); }, [projectSummary, selectedPackageId, selectedLocationId]); return ( <> {/* Main Content */}
{/* Header */}

Filter Analysis

Refine your view by project, package, or location

{isLoading && !projectSummary ? ( ) : ( <> {/* Stats Cards - Top Row */} {/* Charts Row - Side by Side */}
Detection Distribution Breakdown of all detected road conditions
Total detections: {stats.totalRoadDamage + stats.totalGoodSignboard}
Detections by Location Frequency of road issues across different map segments ({ name: loc.name, defected_sign_board: loc.defected_sign_board, pothole: loc.pothole, road_crack: loc.road_crack, damaged_road_marking: loc.damaged_road_marking, total: loc.total, }))} />
Analysis based on latest processed sequence
{/* Map - Full Width Below Charts */}
)}
); }