'use client'; import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter, } from '@/components/ui/card'; import { AlertCircle, Activity, MapPin as MapPinIcon, Milestone, BarChart3, AlertTriangle, Zap, PencilLine, CheckCircle2, Settings2, Droplets, } from 'lucide-react'; import { StatsCard } from '@/components/dashboard/stats-card'; import { FilterSelector } from '@/components/dashboard/filter-selector'; import { DetectionDonutChart } from '@/components/dashboard/detection-donut-chart'; import { ChainageBarChart } from '@/components/dashboard/chainage-bar-chart'; import { DashboardMap } from '@/components/dashboard/dashboard-map'; import { PageHeader } from '@/components/page-header'; import { projectService } from '@/services/api'; import { Project } from '@/types'; 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; chainages: { [key: string]: { chainage_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; totalDrainIssue: number; totalDefectiveCulvert: number; totalRoadDamage: number; chainageData: Array<{ name: string; defected_sign_board: number; pothole: number; road_crack: number; damaged_road_marking: number; // good_sign_board: number; drain_issue: number; defective_culvert: number; total: number; }>; } function calculateStats(summary: ProjectSummary | null): DetectionStats { if (!summary) { return { totalDefectedSignboard: 0, totalPothole: 0, totalRoadCrack: 0, totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalDrainIssue: 0, totalDefectiveCulvert: 0, totalRoadDamage: 0, chainageData: [], }; } let total_defected_sign_board = 0; let total_pothole = 0; let total_road_crack = 0; let total_damaged_road_marking = 0; let total_good_sign_board = 0; let total_drain_issue = 0; let total_defective_culvert = 0; let total_road_damage = 0; const chainageData: DetectionStats['chainageData'] = []; for (const pkg of Object.values(summary.packages || {})) { for (const [chnName, chn] of Object.entries(pkg.chainages || {})) { let chnDefectedSignboard = 0; let chnPothole = 0; let chnRoadCrack = 0; let chnDamagedRoadMarking = 0; let chnGoodSignboard = 0; let chnDrainIssue = 0; let chnDefectiveCulvert = 0; for (const detection of chn.detections || []) { const type = (detection.type || '').toLowerCase(); if (type === 'defected_sign_board') { chnDefectedSignboard++; total_defected_sign_board++; } else if (type === 'pothole') { chnPothole++; total_pothole++; } else if (type === 'road_crack') { chnRoadCrack++; total_road_crack++; } else if (type === 'damaged_road_marking') { chnDamagedRoadMarking++; total_damaged_road_marking++; } else if (type === 'good_sign_board') { chnGoodSignboard++; // total_good_sign_board++; } else if (type === 'drain_issue') { chnDrainIssue++; total_drain_issue++; } else if (type === 'defective_culvert') { chnDefectiveCulvert++; total_defective_culvert++; } } if ( chnDefectedSignboard > 0 || chnPothole > 0 || chnRoadCrack > 0 || chnDamagedRoadMarking > 0 || chnGoodSignboard > 0 || chnDrainIssue > 0 || chnDefectiveCulvert > 0 ) { const shortName = chnName.length > 20 ? chnName.substring(0, 20) + '...' : chnName; chainageData.push({ name: shortName, defected_sign_board: chnDefectedSignboard, pothole: chnPothole, road_crack: chnRoadCrack, damaged_road_marking: chnDamagedRoadMarking, // good_sign_board: chnGoodSignboard, drain_issue: chnDrainIssue, defective_culvert: chnDefectiveCulvert, total: chnDefectedSignboard + chnPothole + chnRoadCrack + chnDamagedRoadMarking + // chnGoodSignboard + chnDrainIssue + chnDefectiveCulvert, }); } } } total_road_damage = total_defected_sign_board + total_pothole + total_road_crack + total_damaged_road_marking + total_drain_issue + total_defective_culvert; return { totalDefectedSignboard: total_defected_sign_board, totalPothole: total_pothole, totalRoadCrack: total_road_crack, totalDamagedRoadMarking: total_damaged_road_marking, totalGoodSignboard: total_good_sign_board, totalDrainIssue: total_drain_issue, totalDefectiveCulvert: total_defective_culvert, totalRoadDamage: total_road_damage, chainageData, }; } 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, totalDrainIssue: 0, totalDefectiveCulvert: 0, totalRoadDamage: 0, chainageData: [], }); // Load projects on mount useEffect(() => { const loadProjects = async () => { try { setIsLoading(true); const projectsData = await projectService.getProjects(); setProjects(projectsData.items); if (projectsData.items.length > 0) { setSelectedProjectId(projectsData.items[projectsData.items.length - 1].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 chainages from selected package const [selectedPackageId, setSelectedPackageId] = useState(null); const [selectedChainageId, setSelectedChainageId] = useState(null); // Changed 'selectedLocationId' to 'selectedChainageId' const chainages = projectSummary && selectedPackageId && selectedPackageId !== 'all' ? Object.keys(projectSummary.packages[selectedPackageId]?.chainages || {}).map((chnName) => ({ id: chnName, name: chnName, })) : []; // Load project summary when project changes useEffect(() => { if (!selectedProjectId) { setProjectSummary(null); return; } setProjectSummary(null); const loadProjectSummary = async () => { try { setIsLoading(true); const summary: ProjectSummary = await projectService.getProjectSummary(selectedProjectId); 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 chainage when project changes useEffect(() => { setSelectedPackageId(null); setSelectedChainageId(null); }, [selectedProjectId]); // Auto-select last package when project summary is loaded useEffect(() => { if (projectSummary && selectedPackageId === null) { const packageIds = Object.keys(projectSummary.packages || {}); if (packageIds.length > 0) { setSelectedPackageId(packageIds[packageIds.length - 1]); } } }, [projectSummary, selectedPackageId]); // Reset chainage when package changes useEffect(() => { setSelectedChainageId(null); }, [selectedPackageId]); // Auto-select last chainage when package is selected useEffect(() => { if (projectSummary && selectedPackageId && selectedPackageId !== 'all' && selectedChainageId === null) { const chainageIds = Object.keys(projectSummary.packages[selectedPackageId]?.chainages || {}); if (chainageIds.length > 0) { setSelectedChainageId(chainageIds[chainageIds.length - 1]); } } }, [projectSummary, selectedPackageId, selectedChainageId]); // Filter stats based on selections useEffect(() => { if (!projectSummary) { setStats({ totalDefectedSignboard: 0, totalPothole: 0, totalRoadCrack: 0, totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalDrainIssue: 0, totalDefectiveCulvert: 0, totalRoadDamage: 0, chainageData: [], // Changed 'locationData' to 'chainageData' }); return; } let totalDefectedSignboard = 0; let totalPothole = 0; let totalRoadCrack = 0; let totalDamagedRoadMarking = 0; let totalGoodSignboard = 0; let totalDrainIssue = 0; let totalDefectiveCulvert = 0; const chainageData: DetectionStats['chainageData'] = []; const packagesToProcess = selectedPackageId && selectedPackageId !== 'all' ? { [selectedPackageId]: projectSummary.packages[selectedPackageId] } : projectSummary.packages || {}; for (const [pkgName, pkg] of Object.entries(packagesToProcess)) { if (!pkg) continue; const chainagesToProcess = selectedChainageId && selectedChainageId !== 'all' ? { [selectedChainageId]: pkg.chainages?.[selectedChainageId] } : pkg.chainages || {}; for (const [chnName, chn] of Object.entries(chainagesToProcess)) { if (!chn) continue; let chnDefectedSignboard = 0; let chnPothole = 0; let chnRoadCrack = 0; let chnDamagedRoadMarking = 0; let chnGoodSignboard = 0; let chnDrainIssue = 0; let chnDefectiveCulvert = 0; for (const detection of chn.detections || []) { const type = (detection.type || '').toLowerCase(); if (type === 'defected_sign_board') { chnDefectedSignboard++; totalDefectedSignboard++; } else if (type === 'pothole') { chnPothole++; totalPothole++; } else if (type === 'road_crack') { chnRoadCrack++; totalRoadCrack++; } else if (type === 'damaged_road_marking') { chnDamagedRoadMarking++; totalDamagedRoadMarking++; } else if (type === 'good_sign_board') { // chnGoodSignboard++; // totalGoodSignboard++; } else if (type === 'drain_issue') { chnDrainIssue++; totalDrainIssue++; } else if (type === 'defective_culvert') { chnDefectiveCulvert++; totalDefectiveCulvert++; } } if ( chnDefectedSignboard > 0 || chnPothole > 0 || chnRoadCrack > 0 || chnDamagedRoadMarking > 0 || chnGoodSignboard > 0 || chnDrainIssue > 0 || chnDefectiveCulvert > 0 ) { const shortName = chnName.length > 20 ? chnName.substring(0, 20) + '...' : chnName; chainageData.push({ name: shortName, defected_sign_board: chnDefectedSignboard, pothole: chnPothole, road_crack: chnRoadCrack, damaged_road_marking: chnDamagedRoadMarking, // good_sign_board: chnGoodSignboard, drain_issue: chnDrainIssue, defective_culvert: chnDefectiveCulvert, total: chnDefectedSignboard + chnPothole + chnRoadCrack + chnDamagedRoadMarking + // chnGoodSignboard + chnDrainIssue + chnDefectiveCulvert, }); } } } setStats({ totalDefectedSignboard, totalPothole, totalRoadCrack, totalDamagedRoadMarking, totalGoodSignboard, totalDrainIssue, totalDefectiveCulvert, totalRoadDamage: totalDefectedSignboard + totalPothole + totalRoadCrack + totalDamagedRoadMarking + totalDrainIssue + totalDefectiveCulvert, chainageData, }); }, [projectSummary, selectedPackageId, selectedChainageId]); return ( <> {/* Main Content */}
{/* Header */}

Filter Analysis

Refine your view by project, package, or chainage

{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 + stats.totalDrainIssue + stats.totalDefectiveCulvert}
Severity by Chainage Detections grouped by road segment
Analysis based on latest processed sequence
{/* Map - Full Width Below Charts */}
)}
); }