"use client" import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { AlertCircle, TrendingUp, MapPin as MapPinIcon, BarChart3, AlertTriangle, RectangleHorizontal } from "lucide-react" import { SidebarNavigation } from "@/components/sidebar-navigation" import { GradientStatsCard } from "@/components/dashboard/gradient-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 { fetchProjects, type Project } from "@/lib/api" 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: [] }) const [error, setError] = useState(null) // Load projects on mount useEffect(() => { const loadProjects = async () => { try { setIsLoading(true) setError(null) const projectsData = await fetchProjects() setProjects(projectsData) if (projectsData.length > 0) { setSelectedProjectId(projectsData[0].id) } } catch (err) { console.error("Failed to load projects:", err) setError("Failed to load projects. Please check if the backend is running.") } finally { setIsLoading(false) } } 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) setError(null) 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) setError("Failed to load project summary.") setProjectSummary(null) } finally { setIsLoading(false) } } 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 (
{/* Sidebar Navigation */} {/* Main Content - offset by sidebar width */}
{/* Header */}
{/* Constant orbit animations removed */}

VisionRoad Analytics Dashboard

A Comprehensive Overview Of Your Road Infrastructure Analysis

{/* Error Display */} {error && (

{error}

)} {/* Stats Cards - Top Row */}
{/* Filter Selector - Above main content */}
{/* Charts Row - Side by Side */}
{/* Left Chart - Detection Distribution */}
Detection Distribution
{/* Right Chart - Location Bar Chart */}
Detections by Location
({ 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, good_sign_board: loc.good_sign_board, total: loc.total }))} isLoading={isLoading} />
{/* Map - Full Width Below Charts */}
{/* Footer */}

Sentient Geeks Pvt. Ltd.

) }