setup husky prettier eslint

This commit is contained in:
2026-03-18 19:39:52 +05:30
parent 42624cae94
commit b675dd857b
93 changed files with 11269 additions and 6011 deletions

View File

@@ -1,163 +1,175 @@
"use client"
'use client';
import { useState, useEffect } from "react"
import { useRouter, useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Loader2, TrendingUp } from "lucide-react"
import VideoPlayerSection from "@/components/video-player-section"
import { PageHeader } from "@/components/page-header"
import { PoweredBy } from "@/components/powered-by"
import { sessionService, videoService } from "@/services/api"
import { SessionContext, DetectionData, DetectionType } from "@/types"
import { getVideoFile, clearVideoFile } from "@/lib/video-storage"
import { ROUTES } from "@/utils/routes"
import { Card } from "@/components/ui/card"
import { useState, useEffect } from 'react';
import { useRouter, useParams } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Loader2, TrendingUp } from 'lucide-react';
import VideoPlayerSection from '@/components/video-player-section';
import { PageHeader } from '@/components/page-header';
import { PoweredBy } from '@/components/powered-by';
import { sessionService, videoService } from '@/services/api';
import { SessionContext, DetectionData, DetectionType } from '@/types';
import { getVideoFile, clearVideoFile } from '@/lib/video-storage';
import { ROUTES } from '@/utils/routes';
import { Card } from '@/components/ui/card';
const API_URL = process.env.NEXT_PUBLIC_API_URL
const API_URL = process.env.NEXT_PUBLIC_API_URL;
export default function VideoResultsPage() {
const router = useRouter()
const { videoId } = useParams() as { videoId: string }
const [session, setSession] = useState<SessionContext | null>(null)
const [detectionData, setDetectionData] = useState<DetectionData | null>(null)
const [detectionType, setDetectionType] = useState<DetectionType>("pothole-detection")
const [videoFile, setVideoFile] = useState<File | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const router = useRouter();
const { videoId } = useParams() as { videoId: string };
const [session, setSession] = useState<SessionContext | null>(null);
const [detectionData, setDetectionData] = useState<DetectionData | null>(null);
const [detectionType, setDetectionType] = useState<DetectionType>('pothole-detection');
const [videoFile, setVideoFile] = useState<File | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const storedSession = sessionService.loadSession()
setSession(storedSession)
useEffect(() => {
const storedSession = sessionService.loadSession();
setSession(storedSession);
const fetchResults = async () => {
try {
// Fetch detection data from backend
const data = await videoService.getVideoResults(videoId);
setDetectionData(data as any)
const fetchResults = async () => {
try {
// Fetch detection data from backend
const data = await videoService.getVideoResults(videoId);
setDetectionData(data as any);
// Try to infer detection type from results if possible
if (data.summary?.unique_signboards !== undefined && data.summary?.unique_signboards > 0) {
setDetectionType("sign-board-detection")
} else if (data.summary?.unique_potholes !== undefined && data.summary?.unique_potholes > 0) {
setDetectionType("pothole-detection")
}
// Retrieve video file from IndexedDB
const storedVideoFile = await getVideoFile(videoId)
if (storedVideoFile) {
setVideoFile(storedVideoFile)
}
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load results")
} finally {
setIsLoading(false)
}
// Try to infer detection type from results if possible
if (data.summary?.unique_signboards !== undefined && data.summary?.unique_signboards > 0) {
setDetectionType('sign-board-detection');
} else if (
data.summary?.unique_potholes !== undefined &&
data.summary?.unique_potholes > 0
) {
setDetectionType('pothole-detection');
}
if (videoId) {
fetchResults()
// Retrieve video file from IndexedDB
const storedVideoFile = await getVideoFile(videoId);
if (storedVideoFile) {
setVideoFile(storedVideoFile);
}
}, [videoId])
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load results');
} finally {
setIsLoading(false);
}
};
const handleNewAnalysis = async () => {
if (videoId) {
try {
await clearVideoFile(videoId)
} catch (err) {
console.error("Failed to clear video file:", err)
}
}
sessionService.clearSession()
router.push(ROUTES.NEW_ANALYSIS)
if (videoId) {
fetchResults();
}
}, [videoId]);
const getTitle = () => {
if (detectionType === "pothole-detection") return "Pothole Detection Results"
if (detectionType === "sign-board-detection") return "Signboard Detection Results"
return "Pothole & Signboard Detection Results"
const handleNewAnalysis = async () => {
if (videoId) {
try {
await clearVideoFile(videoId);
} catch (err) {
console.error('Failed to clear video file:', err);
}
}
sessionService.clearSession();
router.push(ROUTES.NEW_ANALYSIS);
};
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-4 p-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">Loading detection results...</p>
</Card>
</div>
)
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-6 p-8 text-center max-w-md">
<p className="text-destructive font-medium">{error}</p>
<div className="flex gap-4">
<Button onClick={() => router.push(ROUTES.UPLOAD)} variant="outline">Back to Upload</Button>
<Button onClick={handleNewAnalysis}>New Analysis</Button>
</div>
</Card>
</div>
)
}
const getTitle = () => {
if (detectionType === 'pothole-detection') return 'Pothole Detection Results';
if (detectionType === 'sign-board-detection') return 'Signboard Detection Results';
return 'Pothole & Signboard Detection Results';
};
if (isLoading) {
return (
<div className="min-h-screen">
<main className="min-h-screen">
<div className="container mx-auto px-6 py-10 max-w-[1600px]">
<div className="mb-8">
<PageHeader
title={getTitle()}
description={`Video ID: ${videoId}`}
icon={TrendingUp}
/>
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-4 p-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">Loading detection results...</p>
</Card>
</div>
);
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-6 p-8 text-center max-w-md">
<p className="text-destructive font-medium">{error}</p>
<div className="flex gap-4">
<Button onClick={() => router.push(ROUTES.UPLOAD)} variant="outline">
Back to Upload
</Button>
<Button onClick={handleNewAnalysis}>New Analysis</Button>
</div>
</Card>
</div>
);
}
return (
<div className="min-h-screen">
<main className="min-h-screen">
<div className="container mx-auto px-6 py-10 max-w-[1600px]">
<div className="mb-8">
<PageHeader title={getTitle()} description={`Video ID: ${videoId}`} icon={TrendingUp} />
</div>
{session && (
<div className="mb-6">
<Card className="p-0 border shadow-sm overflow-hidden">
<div className="flex flex-col md:flex-row md:items-center justify-between py-4 px-6 gap-6 bg-card">
<div className="flex flex-wrap items-center gap-x-12 gap-y-4">
<div className="flex flex-col">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
Project
</span>
<span className="text-base font-bold leading-tight">
{session.projectName}
</span>
</div>
{session && (
<div className="mb-6">
<Card className="p-0 border shadow-sm overflow-hidden">
<div className="flex flex-col md:flex-row md:items-center justify-between py-4 px-6 gap-6 bg-card">
<div className="flex flex-wrap items-center gap-x-12 gap-y-4">
<div className="flex flex-col">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">Project</span>
<span className="text-base font-bold leading-tight">
{session.projectName}
</span>
</div>
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">Package</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.packageName}
</span>
</div>
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">Chainage</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.chainageName}
</span>
</div>
</div>
<Button onClick={handleNewAnalysis} variant="outline" size="sm" className="font-semibold px-6 shrink-0 h-9">
Start New Analysis
</Button>
</div>
</Card>
</div>
)}
{detectionData && (
<VideoPlayerSection
data={detectionData}
videoId={videoId}
videoFile={videoFile}
detectionType={detectionType}
projectId={session?.projectId || undefined}
/>
)}
<PoweredBy />
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
Package
</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.packageName}
</span>
</div>
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
Chainage
</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.chainageName}
</span>
</div>
</div>
<Button
onClick={handleNewAnalysis}
variant="outline"
size="sm"
className="font-semibold px-6 shrink-0 h-9"
>
Start New Analysis
</Button>
</div>
</main>
</Card>
</div>
)}
{detectionData && (
<VideoPlayerSection
data={detectionData}
videoId={videoId}
videoFile={videoFile}
detectionType={detectionType}
projectId={session?.projectId || undefined}
/>
)}
<PoweredBy />
</div>
)
</main>
</div>
);
}

View File

@@ -1,144 +1,155 @@
"use client"
'use client';
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Loader2, TrendingUp } from "lucide-react"
import VideoPlayerSection from "@/components/video-player-section"
import { PageHeader } from "@/components/page-header"
import { PoweredBy } from "@/components/powered-by"
import { sessionService } from "@/services/api"
import { SessionContext, DetectionData, DetectionType } from "@/types"
import { clearVideoFile } from "@/lib/video-storage"
import { ROUTES } from "@/utils/routes"
import { Card } from "@/components/ui/card"
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Loader2, TrendingUp } from 'lucide-react';
import VideoPlayerSection from '@/components/video-player-section';
import { PageHeader } from '@/components/page-header';
import { PoweredBy } from '@/components/powered-by';
import { sessionService } from '@/services/api';
import { SessionContext, DetectionData, DetectionType } from '@/types';
import { clearVideoFile } from '@/lib/video-storage';
import { ROUTES } from '@/utils/routes';
import { Card } from '@/components/ui/card';
export default function ResultsPage() {
const router = useRouter()
const [session, setSession] = useState<SessionContext | null>(null)
const [detectionData] = useState<DetectionData | null>(null)
const [detectionType] = useState<DetectionType>("pothole-detection")
const [videoId] = useState<string | null>(null)
const [videoFile] = useState<File | null>(null)
const [isLoading] = useState(true)
const [error] = useState<string | null>(null)
const router = useRouter();
const [session, setSession] = useState<SessionContext | null>(null);
const [detectionData] = useState<DetectionData | null>(null);
const [detectionType] = useState<DetectionType>('pothole-detection');
const [videoId] = useState<string | null>(null);
const [videoFile] = useState<File | null>(null);
const [isLoading] = useState(true);
const [error] = useState<string | null>(null);
// Load session and video data on mount
useEffect(() => {
const storedSession = sessionService.loadSession()
const videoData = sessionService.loadVideoData()
// Load session and video data on mount
useEffect(() => {
const storedSession = sessionService.loadSession();
const videoData = sessionService.loadVideoData();
if (!sessionService.isSessionValid(storedSession) || !videoData) {
router.replace(ROUTES.NEW_ANALYSIS)
return
}
// If we have a videoId, redirect to the dynamic results page
if (videoData.videoId) {
router.replace(`${ROUTES.RESULTS}/${videoData.videoId}`)
return
}
setSession(storedSession)
}, [router])
const handleNewAnalysis = async () => {
// Clear video from IndexedDB
if (videoId) {
try {
await clearVideoFile(videoId)
} catch (err) {
console.error("Failed to clear video file:", err)
}
}
sessionService.clearSession()
router.push(ROUTES.NEW_ANALYSIS)
if (!sessionService.isSessionValid(storedSession) || !videoData) {
router.replace(ROUTES.NEW_ANALYSIS);
return;
}
const getTitle = () => {
if (detectionType === "pothole-detection") return "Pothole Detection Results"
if (detectionType === "sign-board-detection") return "Signboard Detection Results"
return "Pothole & Signboard Detection Results"
// If we have a videoId, redirect to the dynamic results page
if (videoData.videoId) {
router.replace(`${ROUTES.RESULTS}/${videoData.videoId}`);
return;
}
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-4 p-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">Loading results...</p>
</Card>
</div>
)
}
setSession(storedSession);
}, [router]);
if (error) {
return (
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-6 p-8 text-center max-w-md">
<p className="text-destructive font-medium">{error}</p>
<Button onClick={handleNewAnalysis}>Start New Analysis</Button>
</Card>
</div>
)
const handleNewAnalysis = async () => {
// Clear video from IndexedDB
if (videoId) {
try {
await clearVideoFile(videoId);
} catch (err) {
console.error('Failed to clear video file:', err);
}
}
sessionService.clearSession();
router.push(ROUTES.NEW_ANALYSIS);
};
const getTitle = () => {
if (detectionType === 'pothole-detection') return 'Pothole Detection Results';
if (detectionType === 'sign-board-detection') return 'Signboard Detection Results';
return 'Pothole & Signboard Detection Results';
};
if (isLoading) {
return (
<div className="min-h-screen">
<main className="min-h-screen">
<div className="container mx-auto px-6 py-10 max-w-[1600px]">
<div className="mb-8">
<PageHeader
title={getTitle()}
description="View your AI-powered road analysis results"
icon={TrendingUp}
/>
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-4 p-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">Loading results...</p>
</Card>
</div>
);
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center">
<Card className="flex flex-col items-center gap-6 p-8 text-center max-w-md">
<p className="text-destructive font-medium">{error}</p>
<Button onClick={handleNewAnalysis}>Start New Analysis</Button>
</Card>
</div>
);
}
return (
<div className="min-h-screen">
<main className="min-h-screen">
<div className="container mx-auto px-6 py-10 max-w-[1600px]">
<div className="mb-8">
<PageHeader
title={getTitle()}
description="View your AI-powered road analysis results"
icon={TrendingUp}
/>
</div>
{session && (
<div className="mb-6">
<Card className="p-0 border shadow-sm overflow-hidden">
<div className="flex flex-col md:flex-row md:items-center justify-between py-4 px-6 gap-6 bg-card">
<div className="flex flex-wrap items-center gap-x-12 gap-y-4">
<div className="flex flex-col">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
Project
</span>
<span className="text-base font-bold leading-tight">
{session.projectName}
</span>
</div>
{session && (
<div className="mb-6">
<Card className="p-0 border shadow-sm overflow-hidden">
<div className="flex flex-col md:flex-row md:items-center justify-between py-4 px-6 gap-6 bg-card">
<div className="flex flex-wrap items-center gap-x-12 gap-y-4">
<div className="flex flex-col">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">Project</span>
<span className="text-base font-bold leading-tight">
{session.projectName}
</span>
</div>
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">Package</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.packageName}
</span>
</div>
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">Chainage</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.chainageName}
</span>
</div>
</div>
<Button onClick={handleNewAnalysis} variant="outline" size="sm" className="font-semibold px-6 shrink-0 h-9">
Start New Analysis
</Button>
</div>
</Card>
</div>
)}
{detectionData && videoId && (
<VideoPlayerSection
data={detectionData}
videoId={videoId}
videoFile={videoFile}
detectionType={detectionType}
projectId={session?.projectId || undefined}
/>
)}
<PoweredBy />
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
Package
</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.packageName}
</span>
</div>
<div className="flex flex-col border-l pl-12 border-border/60">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
Chainage
</span>
<span className="text-sm font-semibold text-muted-foreground leading-tight">
{session.chainageName}
</span>
</div>
</div>
<Button
onClick={handleNewAnalysis}
variant="outline"
size="sm"
className="font-semibold px-6 shrink-0 h-9"
>
Start New Analysis
</Button>
</div>
</main>
</Card>
</div>
)}
{detectionData && videoId && (
<VideoPlayerSection
data={detectionData}
videoId={videoId}
videoFile={videoFile}
detectionType={detectionType}
projectId={session?.projectId || undefined}
/>
)}
<PoweredBy />
</div>
)
</main>
</div>
);
}