tr]:last:border-b-0",
- className
- )}
+ className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
{...props}
/>
- )
+ );
}
-function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return (
- )
+ );
}
-function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
[role=checkbox]]:translate-y-[2px]",
- className
+ 'h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
+ className,
)}
{...props}
/>
- )
+ );
}
-function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
| [role=checkbox]]:translate-y-[2px]",
- className
+ 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
+ className,
)}
{...props}
/>
- )
+ );
}
-function TableCaption({
- className,
- ...props
-}: React.ComponentProps<"caption">) {
+function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
return (
- )
+ );
}
-export {
- Table,
- TableHeader,
- TableBody,
- TableFooter,
- TableHead,
- TableRow,
- TableCell,
- TableCaption,
-}
+export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index a4e90d4..1e14133 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -1,9 +1,9 @@
-"use client"
+'use client';
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+import * as React from 'react';
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function TooltipProvider({
delayDuration = 0,
@@ -15,23 +15,19 @@ function TooltipProvider({
delayDuration={delayDuration}
{...props}
/>
- )
+ );
}
-function Tooltip({
- ...props
-}: React.ComponentProps) {
+function Tooltip({ ...props }: React.ComponentProps) {
return (
- )
+ );
}
-function TooltipTrigger({
- ...props
-}: React.ComponentProps) {
- return
+function TooltipTrigger({ ...props }: React.ComponentProps) {
+ return ;
}
function TooltipContent({
@@ -46,8 +42,8 @@ function TooltipContent({
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
- "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
- className
+ 'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
+ className,
)}
{...props}
>
@@ -55,7 +51,7 @@ function TooltipContent({
- )
+ );
}
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/src/components/video-player-section.tsx b/src/components/video-player-section.tsx
index 724ddd9..449f96d 100644
--- a/src/components/video-player-section.tsx
+++ b/src/components/video-player-section.tsx
@@ -1,133 +1,142 @@
-"use client"
+'use client';
-import { useEffect, useRef, useState, useCallback } from "react"
-import dynamic from "next/dynamic"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { ScrollArea } from "@/components/ui/scroll-area"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { Target, AlertTriangle, Film, Activity, Gauge, Monitor, SignpostBig, Map as MapIcon } from "lucide-react"
+import { useEffect, useRef, useState, useCallback } from 'react';
+import dynamic from 'next/dynamic';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import {
+ Target,
+ AlertTriangle,
+ Film,
+ Activity,
+ Gauge,
+ Monitor,
+ SignpostBig,
+ Map as MapIcon,
+} from 'lucide-react';
// Dynamically import MapModal with SSR disabled (Leaflet requires window object)
-const MapModal = dynamic(() => import("@/components/map-modal"), { ssr: false })
+const MapModal = dynamic(() => import('@/components/map-modal'), { ssr: false });
-import { DetectionData, DetectionType } from "@/types"
-import { cn } from "@/lib/utils"
+import { DetectionData, DetectionType } from '@/types';
+import { cn } from '@/lib/utils';
-import { projectService } from "@/services/api"
+import { projectService } from '@/services/api';
type VideoPlayerSectionProps = {
- data: DetectionData
- videoId: string
- videoFile: File | null // null when loading from server (results page)
- detectionType: DetectionType
- projectId?: string // Optional project ID for fetching detailed summary
-}
+ data: DetectionData;
+ videoId: string;
+ videoFile: File | null; // null when loading from server (results page)
+ detectionType: DetectionType;
+ projectId?: string; // Optional project ID for fetching detailed summary
+};
type DetectionLog = {
- frame: number
+ frame: number;
detections: Array<{
- id: number
- type?: string // For signboards
- bbox: { x1: number; y1: number; x2: number; y2: number }
- confidence: number
- latitude?: number
- longitude?: number
- }>
- videoTime: string // Video timestamp (MM:SS)
-}
+ id: number;
+ type?: string; // For signboards
+ bbox: { x1: number; y1: number; x2: number; y2: number };
+ confidence: number;
+ latitude?: number;
+ longitude?: number;
+ }>;
+ videoTime: string; // Video timestamp (MM:SS)
+};
type ChainageSummaryData = {
project: {
- id: string
- name: string
- corridor_name: string | null
- state: string | null
- }
+ id: string;
+ name: string;
+ corridor_name: string | null;
+ state: string | null;
+ };
packages: {
[packageName: string]: {
- package_id: string
- region: string | null
+ package_id: string;
+ region: string | null;
chainages: {
[chainageName: string]: {
- chainage_id: string
- chainage: string | null
- detection_count: number
+ chainage_id: string;
+ chainage: string | null;
+ detection_count: number;
detections: Array<{
- id: number
- video_id: string
- type: string
- class: string
- confidence: number
- latitude: number
- longitude: number
- frame_number: number
- timestamp_ms: number
+ id: number;
+ video_id: string;
+ type: string;
+ class: string;
+ confidence: number;
+ latitude: number;
+ longitude: number;
+ frame_number: number;
+ timestamp_ms: number;
bounding_box: {
- x1: number
- y1: number
- x2: number
- y2: number
- }
- }>
- }
- }
- }
- }
-}
+ x1: number;
+ y1: number;
+ x2: number;
+ y2: number;
+ };
+ }>;
+ };
+ };
+ };
+ };
+};
function DetailedSummarySection({
projectId,
videoId,
show,
- detectionType
+ detectionType,
}: {
- projectId: string
- videoId: string
- show: boolean
- detectionType: DetectionType
+ projectId: string;
+ videoId: string;
+ show: boolean;
+ detectionType: DetectionType;
}) {
- const [summaryData, setSummaryData] = useState(null)
- const [loading, setLoading] = useState(false)
- const [showMap, setShowMap] = useState(false)
+ const [summaryData, setSummaryData] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [showMap, setShowMap] = useState(false);
useEffect(() => {
- if (!show || !videoId || !projectId) return
+ if (!show || !videoId || !projectId) return;
const fetchSummary = async () => {
- setLoading(true)
+ setLoading(true);
try {
const data = await projectService.getProjectSummaryByVideo(projectId, videoId);
- setSummaryData(data)
+ setSummaryData(data);
} catch (err) {
- console.error("Failed to fetch summary:", err)
+ console.error('Failed to fetch summary:', err);
} finally {
- setLoading(false)
+ setLoading(false);
}
- }
+ };
- fetchSummary()
- }, [show, videoId, projectId])
+ fetchSummary();
+ }, [show, videoId, projectId]);
- if (!show || loading || !summaryData) return null
+ if (!show || loading || !summaryData) return null;
- const isCombined = detectionType === "pot-sign-detection"
- const isPothole = detectionType === "pothole-detection" || isCombined
+ const isCombined = detectionType === 'pot-sign-detection';
+ const isPothole = detectionType === 'pothole-detection' || isCombined;
// Flatten all detections for the scrollable list
const allDetections: Array<{
- detection: any
- chainageName: string
- packageName: string
- }> = []
+ detection: any;
+ chainageName: string;
+ packageName: string;
+ }> = [];
Object.entries(summaryData.packages || {}).forEach(([packageName, packageData]) => {
Object.entries(packageData?.chainages || {}).forEach(([chainageName, chainageData]) => {
- chainageData?.detections?.forEach(detection => {
- allDetections.push({ detection, chainageName, packageName })
- })
- })
- })
+ chainageData?.detections?.forEach((detection) => {
+ allDetections.push({ detection, chainageName, packageName });
+ });
+ });
+ });
return (
@@ -139,20 +148,14 @@ function DetailedSummarySection({
-
- Chainages
-
+ Chainages
- {isCombined ? "Potholes & Signboards" : isPothole ? "Potholes" : "Signboards"} detected
+ {isCombined ? 'Potholes & Signboards' : isPothole ? 'Potholes' : 'Signboards'}{' '}
+ detected
- |