diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 177417a..fa40cd3 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -55,57 +55,102 @@ interface ProjectSummary { } interface DetectionStats { - totalPotholes: number - totalSignboards: number - totalDetections: number + totalDefectedSignboard: number + totalPothole: number + totalRoadCrack: number + totalDamagedRoadMarking: number + totalGoodSignboard: number + totalRoadDamage: number locationData: Array<{ name: string - potholes: number - signboards: number + 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 { totalPotholes: 0, totalSignboards: 0, totalDetections: 0, locationData: [] } + return { + totalDefectedSignboard: 0, + totalPothole: 0, + totalRoadCrack: 0, + totalDamagedRoadMarking: 0, + totalGoodSignboard: 0, + totalRoadDamage: 0, + locationData: [] + } } - let totalPotholes = 0 - let totalSignboards = 0 + 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 locPotholes = 0 - let locSignboards = 0 + let locDefectedSignboard = 0 + let locPothole = 0 + let locRoadCrack = 0 + let locDamagedRoadMarking = 0 + let locGoodSignboard = 0 for (const detection of loc.detections || []) { - if (detection.type.toLowerCase().includes("pothole")) { - locPotholes++ - totalPotholes++ - } else { - locSignboards++ - totalSignboards++ + 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 (locPotholes > 0 || locSignboards > 0) { + 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, - potholes: locPotholes, - signboards: locSignboards, - total: locPotholes + locSignboards + 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 { - totalPotholes, - totalSignboards, - totalDetections: totalPotholes + totalSignboards, + totalDefectedSignboard, + totalPothole, + totalRoadCrack, + totalDamagedRoadMarking, + totalGoodSignboard, + totalRoadDamage, locationData } } @@ -116,9 +161,12 @@ export default function DashboardPage() { const [selectedProjectId, setSelectedProjectId] = useState(null) const [projectSummary, setProjectSummary] = useState(null) const [stats, setStats] = useState({ - totalPotholes: 0, - totalSignboards: 0, - totalDetections: 0, + totalDefectedSignboard: 0, + totalPothole: 0, + totalRoadCrack: 0, + totalDamagedRoadMarking: 0, + totalGoodSignboard: 0, + totalRoadDamage: 0, locationData: [] }) const [error, setError] = useState(null) @@ -218,12 +266,23 @@ export default function DashboardPage() { // Filter stats based on selections useEffect(() => { if (!projectSummary) { - setStats({ totalPotholes: 0, totalSignboards: 0, totalDetections: 0, locationData: [] }) + setStats({ + totalDefectedSignboard: 0, + totalPothole: 0, + totalRoadCrack: 0, + totalDamagedRoadMarking: 0, + totalGoodSignboard: 0, + totalRoadDamage: 0, + locationData: [] + }) return } - let totalPotholes = 0 - let totalSignboards = 0 + let totalDefectedSignboard = 0 + let totalPothole = 0 + let totalRoadCrack = 0 + let totalDamagedRoadMarking = 0 + let totalGoodSignboard = 0 const locationData: DetectionStats["locationData"] = [] const packagesToProcess = selectedPackageId && selectedPackageId !== "all" @@ -238,35 +297,54 @@ export default function DashboardPage() { for (const [locName, loc] of Object.entries(locationsToProcess)) { if (!loc) continue - let locPotholes = 0 - let locSignboards = 0 + let locDefectedSignboard = 0 + let locPothole = 0 + let locRoadCrack = 0 + let locDamagedRoadMarking = 0 + let locGoodSignboard = 0 for (const detection of loc.detections || []) { - if (detection.type.toLowerCase().includes("pothole")) { - locPotholes++ - totalPotholes++ - } else { - locSignboards++ - totalSignboards++ + 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 (locPotholes > 0 || locSignboards > 0) { + 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, - potholes: locPotholes, - signboards: locSignboards, - total: locPotholes + locSignboards + defected_sign_board: locDefectedSignboard, + pothole: locPothole, + road_crack: locRoadCrack, + damaged_road_marking: locDamagedRoadMarking, + good_sign_board: locGoodSignboard, + total: locDefectedSignboard + locPothole + locRoadCrack + locDamagedRoadMarking + locGoodSignboard }) } } } setStats({ - totalPotholes, - totalSignboards, - totalDetections: totalPotholes + totalSignboards, + totalDefectedSignboard, + totalPothole, + totalRoadCrack, + totalDamagedRoadMarking, + totalGoodSignboard, + totalRoadDamage: totalDefectedSignboard + totalPothole + totalRoadCrack + totalDamagedRoadMarking, locationData }) }, [projectSummary, selectedPackageId, selectedLocationId]) @@ -311,29 +389,53 @@ export default function DashboardPage() { {/* Stats Cards - Top Row */}
+ + +
{/* Filter Selector - Above main content */} @@ -368,8 +470,11 @@ export default function DashboardPage() { @@ -389,7 +494,15 @@ export default function DashboardPage() { ({ + 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} /> diff --git a/app/globals.css b/app/globals.css index 48c9550..3029ea6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -175,6 +175,7 @@ html { scrollbar-gutter: stable; + scroll-behavior: smooth; } body[data-scroll-locked] { diff --git a/components/dashboard/dashboard-map-content.tsx b/components/dashboard/dashboard-map-content.tsx index bb071b2..6dc5d67 100644 --- a/components/dashboard/dashboard-map-content.tsx +++ b/components/dashboard/dashboard-map-content.tsx @@ -53,18 +53,24 @@ export default function DashboardMapContent({ detections }: DashboardMapContentP // Get marker color based on detection type const getMarkerColor = (type: string) => { - if (type.toLowerCase().includes("pothole")) { - return { fill: "#10b981", stroke: "#065f46" } // Green for potholes + const t = type.toLowerCase() + if (t === "pothole") { + return { fill: "#ef4444", stroke: "#b91c1c" } // Red + } else if (t === "defected_sign_board") { + return { fill: "#3b82f6", stroke: "#1d4ed8" } // Blue + } else if (t === "road_crack") { + return { fill: "#f59e0b", stroke: "#b45309" } // Orange + } else if (t === "damaged_road_marking") { + return { fill: "#6366f1", stroke: "#4338ca" } // Indigo + } else if (t === "good_sign_board") { + return { fill: "#10b981", stroke: "#047857" } // Emerald } - return { fill: "#3b82f6", stroke: "#2563eb" } // Blue for signboards + return { fill: "#64748b", stroke: "#475569" } // Default Slate } // Get display name for detection type const getTypeName = (type: string) => { - if (type.toLowerCase().includes("pothole")) { - return "Pothole" - } - return "Signboard" + return type.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') } return ( diff --git a/components/dashboard/dashboard-map.tsx b/components/dashboard/dashboard-map.tsx index 619cedb..7e4478f 100644 --- a/components/dashboard/dashboard-map.tsx +++ b/components/dashboard/dashboard-map.tsx @@ -79,12 +79,14 @@ export function DashboardMap({ // Filter detections with valid GPS coordinates const validDetections = detections.filter(d => d.latitude && d.longitude) - // Count potholes and signboards - const potholeCount = validDetections.filter(d => - d.type?.toLowerCase().includes("pothole") || - d.class?.toLowerCase().includes("pothole") - ).length - const signboardCount = validDetections.length - potholeCount + // Count detections by category + const counts = { + defected_sign_board: validDetections.filter(d => d.type?.toLowerCase() === "defected_sign_board").length, + pothole: validDetections.filter(d => d.type?.toLowerCase() === "pothole").length, + road_crack: validDetections.filter(d => d.type?.toLowerCase() === "road_crack").length, + damaged_road_marking: validDetections.filter(d => d.type?.toLowerCase() === "damaged_road_marking").length, + good_sign_board: validDetections.filter(d => d.type?.toLowerCase() === "good_sign_board").length + } return ( @@ -105,14 +107,26 @@ export function DashboardMap({ {/* Compact Color Legend */} -
-
-
- Potholes ({potholeCount}) +
+
+
+ Potholes ({counts.pothole})
-
-
- Signboards ({signboardCount}) +
+
+ Defected Signs ({counts.defected_sign_board}) +
+
+
+ Cracks ({counts.road_crack}) +
+
+
+ Markings ({counts.damaged_road_marking}) +
+
+
+ Good Signs ({counts.good_sign_board})
diff --git a/components/dashboard/detection-donut-chart.tsx b/components/dashboard/detection-donut-chart.tsx index aad9038..53b4c83 100644 --- a/components/dashboard/detection-donut-chart.tsx +++ b/components/dashboard/detection-donut-chart.tsx @@ -4,17 +4,30 @@ import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recha import { Loader2 } from "lucide-react" interface DetectionDonutChartProps { - potholes: number - signboards: number + defectedSignboard: number + pothole: number + roadCrack: number + damagedRoadMarking: number + goodSignboard: number isLoading: boolean } const COLORS = { - pothole: "#10b981", - signboard: "#3b82f6" + defectedSignboard: "#3b82f6", // Blue + pothole: "#ef4444", // Red + roadCrack: "#f59e0b", // Amber/Orange + damagedRoadMarking: "#6366f1", // Indigo + goodSignboard: "#10b981" // Emerald } -export function DetectionDonutChart({ potholes, signboards, isLoading }: DetectionDonutChartProps) { +export function DetectionDonutChart({ + defectedSignboard, + pothole, + roadCrack, + damagedRoadMarking, + goodSignboard, + isLoading +}: DetectionDonutChartProps) { if (isLoading) { return (
@@ -23,7 +36,7 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti ) } - const total = potholes + signboards + const total = defectedSignboard + pothole + roadCrack + damagedRoadMarking + goodSignboard if (total === 0) { return ( @@ -35,18 +48,43 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti } const data = [ - { name: "Potholes", value: potholes, color: COLORS.pothole }, - { name: "Signboards", value: signboards, color: COLORS.signboard } - ] + { name: "Defected Signboards", value: defectedSignboard, color: COLORS.defectedSignboard }, + { name: "Potholes", value: pothole, color: COLORS.pothole }, + { name: "Road Cracks", value: roadCrack, color: COLORS.roadCrack }, + { name: "Damaged Markings", value: damagedRoadMarking, color: COLORS.damagedRoadMarking }, + { name: "Good Signboards", value: goodSignboard, color: COLORS.goodSignboard } + ].filter(item => item.value > 0) return ( -
+
+ + + + + + + + + + + + + + + + + + + + + + - {data.map((entry, index) => ( - - ))} + {data.map((entry, index) => { + const gradId = entry.name === "Potholes" ? "gradPothole" : + entry.name === "Defected Signboards" ? "gradDefectedSign" : + entry.name === "Road Cracks" ? "gradCrack" : + entry.name === "Damaged Markings" ? "gradMarking" : "gradGoodSign" + return ( + + ) + })} { @@ -84,16 +128,16 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti /> ( -
+
{payload?.map((entry, index) => ( -
+
- + {entry.value}: {data[index].value}
@@ -104,7 +148,7 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti {/* Center label */} -
+

{total}

Total

diff --git a/components/dashboard/gradient-stats-card.tsx b/components/dashboard/gradient-stats-card.tsx index 23518eb..290bff4 100644 --- a/components/dashboard/gradient-stats-card.tsx +++ b/components/dashboard/gradient-stats-card.tsx @@ -8,7 +8,7 @@ interface GradientStatsCardProps { subtitle?: string value: number | string icon: LucideIcon - gradient: "green" | "coral" | "blue" | "purple" + gradient: "green" | "coral" | "blue" | "purple" | "orange" | "indigo" | "emerald" isLoading?: boolean } @@ -36,6 +36,24 @@ const gradientStyles = { border: "border-l-4 border-l-[var(--border)]", iconBg: "bg-gradient-to-br from-blue-400 to-blue-600", iconShadow: "shadow-lg shadow-blue-500/20" + }, + orange: { + background: "bg-card", + border: "border-l-4 border-l-[var(--border)]", + iconBg: "bg-gradient-to-br from-blue-400 to-blue-600", + iconShadow: "shadow-lg shadow-blue-500/20" + }, + indigo: { + background: "bg-card", + border: "border-l-4 border-l-[var(--border)]", + iconBg: "bg-gradient-to-br from-blue-400 to-blue-600", + iconShadow: "shadow-lg shadow-blue-500/20" + }, + emerald: { + background: "bg-card", + border: "border-l-4 border-l-[var(--border)]", + iconBg: "bg-gradient-to-br from-blue-400 to-blue-600", + iconShadow: "shadow-lg shadow-blue-500/20" } } diff --git a/components/dashboard/location-bar-chart.tsx b/components/dashboard/location-bar-chart.tsx index 08c94fb..9641832 100644 --- a/components/dashboard/location-bar-chart.tsx +++ b/components/dashboard/location-bar-chart.tsx @@ -5,8 +5,11 @@ import { Loader2 } from "lucide-react" interface LocationData { name: string - potholes: number - signboards: number + defected_sign_board: number + pothole: number + road_crack: number + damaged_road_marking: number + good_sign_board: number total: number } @@ -16,8 +19,11 @@ interface LocationBarChartProps { } const COLORS = { - pothole: "#10b981", - signboard: "#3b82f6" + defected_sign_board: "#60a5fa", // Lighter Blue + pothole: "#ff8a8a", // Lighter Red + road_crack: "#fbbf24", // Lighter Orange + damaged_road_marking: "#818cf8", // Lighter Indigo + good_sign_board: "#34d399" // Lighter Emerald } export function LocationBarChart({ data, isLoading }: LocationBarChartProps) { @@ -44,16 +50,29 @@ export function LocationBarChart({ data, isLoading }: LocationBarChartProps) { - - - + + + - - - + + + + + + + + + + + + + + + - {entry.name}: + {(entry.name as string).replace(/_/g, ' ')}: {entry.value}
))} @@ -103,15 +122,15 @@ export function LocationBarChart({ data, isLoading }: LocationBarChartProps) { verticalAlign="top" height={30} content={({ payload }) => ( -
+
{payload?.map((entry, index) => ( -
+
- - {entry.value} + + {(entry.value as string).replace(/_/g, ' ')}
))} @@ -119,16 +138,37 @@ export function LocationBarChart({ data, isLoading }: LocationBarChartProps) { )} /> + + + diff --git a/components/map-modal.tsx b/components/map-modal.tsx index b47b3a3..11a2304 100644 --- a/components/map-modal.tsx +++ b/components/map-modal.tsx @@ -118,9 +118,26 @@ export default function MapModal({ open, onClose, detections, detectionType }: M {/* Detection markers */} {validDetections.map((detection, idx) => { - const isDetPothole = detection.type === "pothole" - const markerColor = isDetPothole ? "#ef4444" : "#3b82f6" - const strokeColor = isDetPothole ? "#991b1b" : "#1e40af" + const type = (detection.type || "").toLowerCase() + let markerColor = "#64748b" // Default Slate + let strokeColor = "#475569" + + if (type === "pothole") { + markerColor = "#ef4444" + strokeColor = "#b91c1c" + } else if (type === "defected_sign_board") { + markerColor = "#3b82f6" + strokeColor = "#1d4ed8" + } else if (type === "road_crack") { + markerColor = "#f59e0b" + strokeColor = "#b45309" + } else if (type === "damaged_road_marking") { + markerColor = "#6366f1" + strokeColor = "#4338ca" + } else if (type === "good_sign_board") { + markerColor = "#10b981" + strokeColor = "#047857" + } return (
-
- {isDetPothole ? "Pothole" : (detection.class || detection.type || "").replace(/_/g, " ")} #{detection.id} +
+ {(detection.type || "").replace(/_/g, " ")} #{detection.id}
Frame: {detection.frame_number}
Confidence: {(detection.confidence * 100).toFixed(1)}%
diff --git a/components/summary-section.tsx b/components/summary-section.tsx index c75698d..dcbfb4c 100644 --- a/components/summary-section.tsx +++ b/components/summary-section.tsx @@ -1,7 +1,7 @@ "use client" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Target, AlertTriangle, Film, Activity, Gauge, Monitor } from "lucide-react" +import { Target, AlertTriangle, Film, Activity, Gauge, Monitor, SignpostBig } from "lucide-react" import type { DetectionData } from "@/lib/types" type SummarySectionProps = { @@ -10,26 +10,47 @@ type SummarySectionProps = { export function SummarySection({ data }: SummarySectionProps) { const stats = [ + { + label: "Total Road Damage", + value: data.summary.total_road_damage || 0, + icon: Target, + color: "text-purple-500", + bgColor: "bg-purple-50 dark:bg-purple-950/30", + }, + { + label: "Defected Signboards", + value: data.summary.unique_defected_sign_board || 0, + icon: SignpostBig, + color: "text-blue-500", + bgColor: "bg-blue-50 dark:bg-blue-950/30", + }, { label: "Unique Potholes", - value: data.summary.unique_potholes || 0, + value: data.summary.unique_pothole || 0, icon: AlertTriangle, color: "text-red-500", bgColor: "bg-red-50 dark:bg-red-950/30", }, { - label: "Total Detections", - value: data.summary.total_detections || 0, - icon: Target, - color: "text-blue-500", - bgColor: "bg-blue-50 dark:bg-blue-950/30", + label: "Road Cracks", + value: data.summary.unique_road_crack || 0, + icon: AlertTriangle, + color: "text-orange-500", + bgColor: "bg-orange-50 dark:bg-orange-950/30", }, { - label: "Total Frames", - value: data.summary.total_frames || data.video_info.total_frames, - icon: Film, - color: "text-purple-500", - bgColor: "bg-purple-50 dark:bg-purple-950/30", + label: "Damaged Markings", + value: data.summary.unique_damaged_road_marking || 0, + icon: Activity, + color: "text-indigo-500", + bgColor: "bg-indigo-50 dark:bg-indigo-950/30", + }, + { + label: "Good Signboards", + value: data.summary.unique_good_sign_board || 0, + icon: SignpostBig, + color: "text-emerald-500", + bgColor: "bg-emerald-50 dark:bg-emerald-950/30", }, { label: "Detection Rate", @@ -52,28 +73,35 @@ export function SummarySection({ data }: SummarySectionProps) { color: "text-blue-500", bgColor: "bg-blue-50 dark:bg-blue-950/30", }, + { + label: "Total Frames", + value: data.summary.total_frames || data.video_info.total_frames, + icon: Film, + color: "text-purple-500", + bgColor: "bg-purple-50 dark:bg-purple-950/30", + }, ] return ( Detection Summary - Overview of pothole detection results + Overview of road analysis results -
+
{stats.map((stat, index) => { const Icon = stat.icon return (
-
{stat.value}
-
{stat.label}
+
{stat.value}
+
{stat.label}
) })} diff --git a/components/video-player-section.tsx b/components/video-player-section.tsx index 56dd8e9..ebcd771 100644 --- a/components/video-player-section.tsx +++ b/components/video-player-section.tsx @@ -283,33 +283,47 @@ function SummarySection({ data, show, detectionType }: { data: DetectionData; sh const isSignboard = detectionType === "sign-board-detection" || isCombined const stats = [ - ...(isPothole ? [{ - label: "Unique Potholes", - value: data.summary.unique_potholes || 0, - icon: AlertTriangle, - color: "text-red-500", - bgColor: "bg-red-50 dark:bg-red-950/30", - }] : []), - ...(isSignboard ? [{ - label: "Unique Signboards", - value: data.summary.unique_signboards || 0, - icon: SignpostBig, - color: "text-blue-500", - bgColor: "bg-blue-50 dark:bg-blue-950/30", - }] : []), { - label: "Total Detections", - value: data.summary.total_detections || 0, + label: "Total Road Damage", + value: data.summary.total_road_damage || 0, icon: Target, + color: "text-purple-500", + bgColor: "bg-purple-50 dark:bg-purple-950/30", + }, + { + label: "Defected Signboards", + value: data.summary.unique_defected_sign_board || 0, + icon: SignpostBig, color: "text-blue-500", bgColor: "bg-blue-50 dark:bg-blue-950/30", }, { - label: "Total Frames", - value: data.summary.total_frames || data.video_info.total_frames, - icon: Film, - color: "text-purple-500", - bgColor: "bg-purple-50 dark:bg-purple-950/30", + label: "Unique Potholes", + value: data.summary.unique_pothole || 0, + icon: AlertTriangle, + color: "text-red-500", + bgColor: "bg-red-50 dark:bg-red-950/30", + }, + { + label: "Road Cracks", + value: data.summary.unique_road_crack || 0, + icon: AlertTriangle, + color: "text-orange-500", + bgColor: "bg-orange-50 dark:bg-orange-950/30", + }, + { + label: "Damaged Markings", + value: data.summary.unique_damaged_road_marking || 0, + icon: Activity, + color: "text-indigo-500", + bgColor: "bg-indigo-50 dark:bg-indigo-950/30", + }, + { + label: "Good Signboards", + value: data.summary.unique_good_sign_board || 0, + icon: SignpostBig, + color: "text-emerald-500", + bgColor: "bg-emerald-50 dark:bg-emerald-950/30", }, { label: "Detection Rate", @@ -332,6 +346,13 @@ function SummarySection({ data, show, detectionType }: { data: DetectionData; sh color: "text-blue-500", bgColor: "bg-blue-50 dark:bg-blue-950/30", }, + { + label: "Total Frames", + value: data.summary.total_frames || data.video_info.total_frames, + icon: Film, + color: "text-purple-500", + bgColor: "bg-purple-50 dark:bg-purple-950/30", + }, ] return ( @@ -356,7 +377,7 @@ function SummarySection({ data, show, detectionType }: { data: DetectionData; sh
-
+
{stats.map((stat, index) => { const Icon = stat.icon return ( @@ -526,12 +547,19 @@ export default function VideoPlayerSection({ data, videoId, videoFile, detection const width = x2 - x1 const height = y2 - y1 - // Determine if this specific detection is a pothole or signboard - const isDetPothole = detection._detType === 'pothole' || detection.type === 'pothole' || (detection.pothole_id !== undefined && !detection.signboard_id) + // Map individual detection types to colors + const type = (detection.type || detection._detType || '').toLowerCase() + const detectionColors: Record = { + 'pothole': '#ef4444', + 'defected_sign_board': '#3b82f6', + 'road_crack': '#f59e0b', + 'damaged_road_marking': '#6366f1', + 'good_sign_board': '#10b981', + 'signboard': '#3b82f6' // fallback + } - // Set colors based on individual detection type - const boxColor = isDetPothole ? '#ef4444' : '#3b82f6' // red for potholes, blue for signboards - const textBgColor = isDetPothole ? 'rgba(239, 68, 68, 0.9)' : 'rgba(59, 130, 246, 0.9)' + const boxColor = detectionColors[type] || '#3b82f6' + const textBgColor = boxColor + 'e6' // Adding alpha // Draw bounding box ctx.strokeStyle = boxColor @@ -539,13 +567,13 @@ export default function VideoPlayerSection({ data, videoId, videoFile, detection ctx.strokeRect(x1, y1, width, height) // Draw semi-transparent fill - ctx.fillStyle = isDetPothole ? 'rgba(239, 68, 68, 0.15)' : 'rgba(59, 130, 246, 0.15)' + ctx.fillStyle = type === 'pothole' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(59, 130, 246, 0.15)' ctx.fillRect(x1, y1, width, height) // Prepare label text const id = detection.pothole_id ?? detection.signboard_id ?? detection.detection_id const confidence = (detection.confidence * 100).toFixed(1) - let labelText = isDetPothole + let labelText = type === 'pothole' ? `Pothole #${id}` : `${(detection.type || 'Sign').replace(/_/g, ' ')} #${id}` labelText += ` ${confidence}%` diff --git a/lib/api.ts b/lib/api.ts index 2b9c302..77913e7 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -278,8 +278,12 @@ export interface Video { filename: string detection_type: "pothole-detection" | "sign-board-detection" | "pot-sign-detection" status: "pending" | "processing" | "completed" | "failed" - unique_potholes?: number - unique_signboards?: number + unique_defected_sign_board?: number + unique_pothole?: number + unique_road_crack?: number + unique_damaged_road_marking?: number + unique_good_sign_board?: number + total_road_damage?: number total_detections?: number created_at: string updated_at: string @@ -289,16 +293,35 @@ export interface Video { * Fetch all videos (for dashboard) */ export async function fetchVideos(): Promise { - const response = await apiRequest<{ videos: Array<{ video_id: string; status: string; progress: number; summary?: { unique_potholes?: number; unique_signboards?: number; total_detections?: number } }> }>("/videos") + const response = await apiRequest<{ + videos: Array<{ + video_id: string; + status: string; + progress: number; + summary?: { + unique_defected_sign_board?: number; + unique_pothole?: number; + unique_road_crack?: number; + unique_damaged_road_marking?: number; + unique_good_sign_board?: number; + total_road_damage?: number; + total_detections?: number; + } + }> + }>("/videos") // Transform the response to match our Video interface return response.videos.map(v => ({ id: v.video_id, - filename: v.video_id, // Using video_id as filename since backend doesn't provide it - detection_type: (v.summary?.unique_potholes !== undefined && v.summary?.unique_signboards !== undefined) ? "pot-sign-detection" as const : v.summary?.unique_potholes !== undefined ? "pothole-detection" as const : "sign-board-detection" as const, + filename: v.video_id, + detection_type: "pot-sign-detection" as const, status: v.status as Video["status"], - unique_potholes: v.summary?.unique_potholes, - unique_signboards: v.summary?.unique_signboards, + unique_defected_sign_board: v.summary?.unique_defected_sign_board, + unique_pothole: v.summary?.unique_pothole, + unique_road_crack: v.summary?.unique_road_crack, + unique_damaged_road_marking: v.summary?.unique_damaged_road_marking, + unique_good_sign_board: v.summary?.unique_good_sign_board, + total_road_damage: v.summary?.total_road_damage, total_detections: v.summary?.total_detections, created_at: new Date().toISOString(), updated_at: new Date().toISOString() diff --git a/lib/project-service.ts b/lib/project-service.ts new file mode 100644 index 0000000..001530c --- /dev/null +++ b/lib/project-service.ts @@ -0,0 +1,49 @@ +"use client" + +import { + type Project, + type Package, + type Location, + type Video, + type Detection +} from "./api" + +/** + * Service to handle data extraction from project summaries + */ +export const projectDataService = { + /** + * Extracts all detections from a project summary, optionally filtered by package and location + */ + extractDetections( + projectSummary: any, + selectedPackageId?: string | null, + selectedLocationId?: string | null + ): Detection[] { + if (!projectSummary) return [] + + const detections: Detection[] = [] + 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 as any).locations[selectedLocationId] } + : (pkg as any).locations || {} + + for (const [locName, loc] of Object.entries(locationsToProcess)) { + if (!loc) continue + const locationDetections = (loc as any).detections || [] + detections.push(...locationDetections) + } + } + + return detections + } +} + +/** + * API service for project and video data + */ +export * from "./api" diff --git a/lib/types.ts b/lib/types.ts index d1ea4a1..2855d42 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,18 @@ export type DetectionType = "pothole-detection" | "sign-board-detection" | "pot-sign-detection" +export interface DetectionListItem { + detection_id?: number + pothole_id?: number + signboard_id?: number + type: string + first_detected_frame: number + first_detected_time: number + confidence: number + bbox?: { x1: number; y1: number; x2: number; y2: number } + lat?: number + lng?: number +} + export type DetectionData = { video_id: string detection_type?: string @@ -11,34 +24,22 @@ export type DetectionData = { total_frames: number } summary: { - unique_potholes?: number - unique_signboards?: number + unique_defected_sign_board?: number + unique_pothole?: number + unique_road_crack?: number + unique_damaged_road_marking?: number + unique_good_sign_board?: number + total_road_damage?: number total_detections: number total_frames: number detection_rate: number } - pothole_list?: Array<{ - pothole_id?: number - detection_id?: number - type?: string - first_detected_frame: number - first_detected_time: number - confidence: number - bbox?: { x1: number; y1: number; x2: number; y2: number } - lat?: number - lng?: number - }> - signboard_list?: Array<{ - signboard_id?: number - detection_id?: number - type: string - first_detected_frame: number - first_detected_time: number - confidence: number - bbox?: { x1: number; y1: number; x2: number; y2: number } - lat?: number - lng?: number - }> + pothole_list?: Array + defected_sign_board_list?: Array + road_crack_list?: Array + damaged_road_marking_list?: Array + good_sign_board_list?: Array + signboard_list?: Array // Keeping for backward compatibility frames: Array<{ frame_id: number // Legacy format: separate arrays