refactor: refactor the color schema
This commit is contained in:
178
src/app/(modules)/results/[videoId]/page.tsx
Normal file
178
src/app/(modules)/results/[videoId]/page.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
"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 {
|
||||
type SessionContext,
|
||||
loadSession,
|
||||
clearSession
|
||||
} from "@/lib/api"
|
||||
import { getVideoFile, clearVideoFile } from "@/lib/video-storage"
|
||||
import { DetectionData, DetectionType } from "@/lib/types"
|
||||
import { ROUTES } from "@/utils/routes"
|
||||
import { Card } from "@/components/ui/card"
|
||||
|
||||
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)
|
||||
|
||||
useEffect(() => {
|
||||
const storedSession = loadSession()
|
||||
setSession(storedSession)
|
||||
|
||||
const fetchResults = async () => {
|
||||
try {
|
||||
// Fetch detection data from backend
|
||||
const response = await fetch(`${API_URL}/results/${videoId}`, {
|
||||
headers: { "ngrok-skip-browser-warning": "true" }
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error("Results not found for this video.")
|
||||
}
|
||||
throw new Error(`Failed to load results: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if (videoId) {
|
||||
fetchResults()
|
||||
}
|
||||
}, [videoId])
|
||||
|
||||
const handleNewAnalysis = async () => {
|
||||
if (videoId) {
|
||||
try {
|
||||
await clearVideoFile(videoId)
|
||||
} catch (err) {
|
||||
console.error("Failed to clear video file:", err)
|
||||
}
|
||||
}
|
||||
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 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>
|
||||
<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">Location</span>
|
||||
<span className="text-sm font-semibold text-muted-foreground leading-tight">
|
||||
{session.locationName}
|
||||
</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>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
150
src/app/(modules)/results/page.tsx
Normal file
150
src/app/(modules)/results/page.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
"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 {
|
||||
type SessionContext,
|
||||
loadSession,
|
||||
loadVideoData,
|
||||
isSessionValid,
|
||||
clearSession
|
||||
} from "@/lib/api"
|
||||
import { clearVideoFile } from "@/lib/video-storage"
|
||||
import { DetectionData, DetectionType } from "@/lib/types"
|
||||
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)
|
||||
|
||||
// Load session and video data on mount
|
||||
useEffect(() => {
|
||||
const storedSession = loadSession()
|
||||
const videoData = loadVideoData()
|
||||
|
||||
if (!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)
|
||||
}
|
||||
}
|
||||
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 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>
|
||||
<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">Location</span>
|
||||
<span className="text-sm font-semibold text-muted-foreground leading-tight">
|
||||
{session.locationName}
|
||||
</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>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user