added more feature according to updated model
This commit is contained in:
@@ -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<string | null>(null)
|
||||
const [projectSummary, setProjectSummary] = useState<ProjectSummary | null>(null)
|
||||
const [stats, setStats] = useState<DetectionStats>({
|
||||
totalPotholes: 0,
|
||||
totalSignboards: 0,
|
||||
totalDetections: 0,
|
||||
totalDefectedSignboard: 0,
|
||||
totalPothole: 0,
|
||||
totalRoadCrack: 0,
|
||||
totalDamagedRoadMarking: 0,
|
||||
totalGoodSignboard: 0,
|
||||
totalRoadDamage: 0,
|
||||
locationData: []
|
||||
})
|
||||
const [error, setError] = useState<string | null>(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 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<GradientStatsCard
|
||||
title="Total Detections"
|
||||
subtitle="All detected objects"
|
||||
value={stats.totalDetections}
|
||||
title="Total Road Damage"
|
||||
subtitle="Combined damage detections"
|
||||
value={stats.totalRoadDamage}
|
||||
icon={TrendingUp}
|
||||
gradient="purple"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<GradientStatsCard
|
||||
title="Potholes"
|
||||
subtitle="Road surface damage"
|
||||
value={stats.totalPotholes}
|
||||
subtitle="Surface depressions"
|
||||
value={stats.totalPothole}
|
||||
icon={AlertTriangle}
|
||||
gradient="green"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<GradientStatsCard
|
||||
title="Signboards"
|
||||
subtitle="Traffic signs detected"
|
||||
value={stats.totalSignboards}
|
||||
title="Defected Signboards"
|
||||
subtitle="Damaged traffic signs"
|
||||
value={stats.totalDefectedSignboard}
|
||||
icon={RectangleHorizontal}
|
||||
gradient="blue"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<GradientStatsCard
|
||||
title="Road Cracks"
|
||||
subtitle="Surface fissures"
|
||||
value={stats.totalRoadCrack}
|
||||
icon={AlertTriangle}
|
||||
gradient="orange"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<GradientStatsCard
|
||||
title="Damaged Markings"
|
||||
subtitle="Worn road lines"
|
||||
value={stats.totalDamagedRoadMarking}
|
||||
icon={TrendingUp}
|
||||
gradient="indigo"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<GradientStatsCard
|
||||
title="Good Signboards"
|
||||
subtitle="Informational markers"
|
||||
value={stats.totalGoodSignboard}
|
||||
icon={RectangleHorizontal}
|
||||
gradient="emerald"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filter Selector - Above main content */}
|
||||
@@ -368,8 +470,11 @@ export default function DashboardPage() {
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4">
|
||||
<DetectionDonutChart
|
||||
potholes={stats.totalPotholes}
|
||||
signboards={stats.totalSignboards}
|
||||
defectedSignboard={stats.totalDefectedSignboard}
|
||||
pothole={stats.totalPothole}
|
||||
roadCrack={stats.totalRoadCrack}
|
||||
damagedRoadMarking={stats.totalDamagedRoadMarking}
|
||||
goodSignboard={stats.totalGoodSignboard}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</CardContent>
|
||||
@@ -389,7 +494,15 @@ export default function DashboardPage() {
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4">
|
||||
<LocationBarChart
|
||||
data={stats.locationData}
|
||||
data={stats.locationData.map(loc => ({
|
||||
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}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body[data-scroll-locked] {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
<Card className={`overflow-hidden ${className}`}>
|
||||
@@ -105,14 +107,26 @@ export function DashboardMap({
|
||||
</div>
|
||||
|
||||
{/* Compact Color Legend */}
|
||||
<div className="flex items-center gap-4 text-xs">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500 shadow-sm shadow-emerald-500/50" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Potholes ({potholeCount})</span>
|
||||
<div className="flex flex-wrap items-center justify-end gap-x-4 gap-y-1 text-[10px] max-w-[60%]">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#ef4444] shadow-sm shadow-[#ef4444]/30" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Potholes ({counts.pothole})</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-3 h-3 rounded-full bg-blue-500 shadow-sm shadow-blue-500/50" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Signboards ({signboardCount})</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#3b82f6] shadow-sm shadow-[#3b82f6]/30" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Defected Signs ({counts.defected_sign_board})</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#f59e0b] shadow-sm shadow-[#f59e0b]/30" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Cracks ({counts.road_crack})</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#6366f1] shadow-sm shadow-[#6366f1]/30" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Markings ({counts.damaged_road_marking})</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#10b981] shadow-sm shadow-[#10b981]/30" />
|
||||
<span className="text-gray-600 dark:text-gray-400 font-medium">Good Signs ({counts.good_sign_board})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="h-[250px] flex items-center justify-center">
|
||||
@@ -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 (
|
||||
<div className="h-[200px] relative">
|
||||
<div className="h-[220px] relative">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<defs>
|
||||
<linearGradient id="gradPothole" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#ff8a8a" />
|
||||
<stop offset="100%" stopColor="#ef4444" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradDefectedSign" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#60a5fa" />
|
||||
<stop offset="100%" stopColor="#3b82f6" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradCrack" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#fbbf24" />
|
||||
<stop offset="100%" stopColor="#f59e0b" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradMarking" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#818cf8" />
|
||||
<stop offset="100%" stopColor="#6366f1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradGoodSign" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#34d399" />
|
||||
<stop offset="100%" stopColor="#10b981" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Pie
|
||||
data={data}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
cy="45%"
|
||||
innerRadius={50}
|
||||
outerRadius={70}
|
||||
paddingAngle={5}
|
||||
@@ -54,14 +92,20 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti
|
||||
strokeWidth={0}
|
||||
isAnimationActive={false}
|
||||
>
|
||||
{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 (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={entry.color}
|
||||
fill={`url(#${gradId})`}
|
||||
fillOpacity={1}
|
||||
className="hover:fill-opacity-80"
|
||||
/>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
content={({ active, payload }) => {
|
||||
@@ -84,16 +128,16 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti
|
||||
/>
|
||||
<Legend
|
||||
verticalAlign="bottom"
|
||||
height={36}
|
||||
height={40}
|
||||
content={({ payload }) => (
|
||||
<div className="flex items-center justify-center gap-6 mt-2">
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 mt-2">
|
||||
{payload?.map((entry, index) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div key={`legend-${index}`} className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
<span className="text-[9px] text-muted-foreground whitespace-nowrap">
|
||||
{entry.value}: {data[index].value}
|
||||
</span>
|
||||
</div>
|
||||
@@ -104,7 +148,7 @@ export function DetectionDonutChart({ potholes, signboards, isLoading }: Detecti
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
{/* Center label */}
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none" style={{ marginTop: '-40px' }}>
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none" style={{ marginTop: '-55px' }}>
|
||||
<div className="text-center">
|
||||
<p className="text-xl font-extrabold text-[#2563eb]">{total}</p>
|
||||
<p className="text-[9px] uppercase tracking-wider text-muted-foreground">Total</p>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
<BarChart
|
||||
data={data}
|
||||
margin={{ top: 10, right: 10, left: 0, bottom: 20 }}
|
||||
barCategoryGap="25%"
|
||||
barCategoryGap="10%"
|
||||
barGap={2}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="barGradientPothole" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#10b981" stopOpacity={0.8} />
|
||||
<stop offset="100%" stopColor="#10b981" stopOpacity={0.4} />
|
||||
<linearGradient id="barPothole" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#ff8a8a" />
|
||||
<stop offset="100%" stopColor="#ef4444" />
|
||||
</linearGradient>
|
||||
<linearGradient id="barGradientSignboard" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#3b82f6" stopOpacity={0.8} />
|
||||
<stop offset="100%" stopColor="#3b82f6" stopOpacity={0.4} />
|
||||
<linearGradient id="barDefectedSign" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#60a5fa" />
|
||||
<stop offset="100%" stopColor="#3b82f6" />
|
||||
</linearGradient>
|
||||
<linearGradient id="barCrack" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#fbbf24" />
|
||||
<stop offset="100%" stopColor="#f59e0b" />
|
||||
</linearGradient>
|
||||
<linearGradient id="barMarking" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#818cf8" />
|
||||
<stop offset="100%" stopColor="#6366f1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="barGoodSign" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#34d399" />
|
||||
<stop offset="100%" stopColor="#10b981" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid
|
||||
@@ -89,7 +108,7 @@ export function LocationBarChart({ data, isLoading }: LocationBarChartProps) {
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-muted-foreground">{entry.name}:</span>
|
||||
<span className="text-muted-foreground">{(entry.name as string).replace(/_/g, ' ')}:</span>
|
||||
<span className="font-semibold">{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -103,15 +122,15 @@ export function LocationBarChart({ data, isLoading }: LocationBarChartProps) {
|
||||
verticalAlign="top"
|
||||
height={30}
|
||||
content={({ payload }) => (
|
||||
<div className="flex items-center justify-center gap-6 mb-2">
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 mb-2">
|
||||
{payload?.map((entry, index) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div key={`legend-${index}`} className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="w-3 h-3 rounded-sm"
|
||||
className="w-2.5 h-2.5 rounded-sm"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{entry.value}
|
||||
<span className="text-[9px] text-muted-foreground whitespace-nowrap">
|
||||
{(entry.value as string).replace(/_/g, ' ')}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -119,16 +138,37 @@ export function LocationBarChart({ data, isLoading }: LocationBarChartProps) {
|
||||
)}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="potholes"
|
||||
name="Potholes"
|
||||
fill="url(#barGradientPothole)"
|
||||
dataKey="pothole"
|
||||
name="Pothole"
|
||||
fill="url(#barPothole)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="signboards"
|
||||
name="Signboards"
|
||||
fill="url(#barGradientSignboard)"
|
||||
dataKey="defected_sign_board"
|
||||
name="Defected Signboard"
|
||||
fill="url(#barDefectedSign)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="road_crack"
|
||||
name="Road Crack"
|
||||
fill="url(#barCrack)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="damaged_road_marking"
|
||||
name="Damaged Marking"
|
||||
fill="url(#barMarking)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="good_sign_board"
|
||||
name="Good Signboard"
|
||||
fill="url(#barGoodSign)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
|
||||
@@ -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 (
|
||||
<CircleMarker
|
||||
@@ -135,8 +152,8 @@ export default function MapModal({ open, onClose, detections, detectionType }: M
|
||||
>
|
||||
<Popup>
|
||||
<div className="text-xs space-y-1">
|
||||
<div className="font-semibold">
|
||||
{isDetPothole ? "Pothole" : (detection.class || detection.type || "").replace(/_/g, " ")} #{detection.id}
|
||||
<div className="font-semibold capitalize">
|
||||
{(detection.type || "").replace(/_/g, " ")} #{detection.id}
|
||||
</div>
|
||||
<div>Frame: {detection.frame_number}</div>
|
||||
<div>Confidence: {(detection.confidence * 100).toFixed(1)}%</div>
|
||||
|
||||
@@ -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 (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Detection Summary</CardTitle>
|
||||
<CardDescription>Overview of pothole detection results</CardDescription>
|
||||
<CardDescription>Overview of road analysis results</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
{stats.map((stat, index) => {
|
||||
const Icon = stat.icon
|
||||
return (
|
||||
<div
|
||||
key={stat.label}
|
||||
className="flex flex-col items-center justify-center p-4 rounded-lg"
|
||||
className="flex flex-col items-center justify-center p-4 rounded-lg border border-border/50 bg-card hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className={`${stat.bgColor} p-3 rounded-full mb-3`}>
|
||||
<Icon className={`h-5 w-5 ${stat.color}`} />
|
||||
</div>
|
||||
<div className={`text-3xl font-bold ${stat.color} mb-1`}>{stat.value}</div>
|
||||
<div className="text-xs text-muted-foreground text-center">{stat.label}</div>
|
||||
<div className={`text-xl font-bold ${stat.color} mb-1`}>{stat.value}</div>
|
||||
<div className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground text-center">{stat.label}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -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
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className={`grid gap-3 ${isCombined ? 'grid-cols-3 md:grid-cols-7' : 'grid-cols-3 md:grid-cols-6'}`}>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
|
||||
{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<string, string> = {
|
||||
'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}%`
|
||||
|
||||
37
lib/api.ts
37
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<Video[]> {
|
||||
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()
|
||||
|
||||
49
lib/project-service.ts
Normal file
49
lib/project-service.ts
Normal file
@@ -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"
|
||||
49
lib/types.ts
49
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<DetectionListItem>
|
||||
defected_sign_board_list?: Array<DetectionListItem>
|
||||
road_crack_list?: Array<DetectionListItem>
|
||||
damaged_road_marking_list?: Array<DetectionListItem>
|
||||
good_sign_board_list?: Array<DetectionListItem>
|
||||
signboard_list?: Array<DetectionListItem> // Keeping for backward compatibility
|
||||
frames: Array<{
|
||||
frame_id: number
|
||||
// Legacy format: separate arrays
|
||||
|
||||
Reference in New Issue
Block a user