refactor: remove unused files and fix naming convention
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
|||||||
} from "@/lib/api"
|
} from "@/lib/api"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
export default function CreateLocationPage() {
|
export default function LocationPage() {
|
||||||
const [locations, setLocations] = useState<Location[]>([])
|
const [locations, setLocations] = useState<Location[]>([])
|
||||||
const [projects, setProjects] = useState<Project[]>([])
|
const [projects, setProjects] = useState<Project[]>([])
|
||||||
const [packages, setPackages] = useState<PackageType[]>([])
|
const [packages, setPackages] = useState<PackageType[]>([])
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { NavigationMenu } from "@/components/navigation-menu"
|
|
||||||
import { DashboardMap } from "@/components/dashboard/dashboard-map"
|
|
||||||
import { PoweredBy } from "@/components/powered-by"
|
|
||||||
|
|
||||||
export default function MapPage() {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen relative overflow-hidden ml-20">
|
|
||||||
{/* Navigation Menu */}
|
|
||||||
<NavigationMenu />
|
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-8 max-w-340 relative z-10">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="mb-8">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl md:text-4xl font-bold heading-blue-gradient leading-tight">
|
|
||||||
Detection Map
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground mt-1">
|
|
||||||
View all detected potholes and signboards on the map
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Map */}
|
|
||||||
<div className="">
|
|
||||||
<DashboardMap className="h-[calc(100vh-200px)] min-h-[500px]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Legend */}
|
|
||||||
<div className="mt-6 flex flex-wrap gap-6">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-4 h-4 rounded-full bg-red-500 border-2 border-red-600" />
|
|
||||||
<span className="text-sm text-muted-foreground">Pothole</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-4 h-4 rounded-full bg-blue-500 border-2 border-blue-600" />
|
|
||||||
<span className="text-sm text-muted-foreground">Signboard</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { PoweredBy } from "@/components/powered-by"
|
import { PoweredBy } from "@/components/powered-by"
|
||||||
|
|
||||||
export default function CreatePackagePage() {
|
export default function PackagePage() {
|
||||||
const [packages, setPackages] = useState<PackageType[]>([])
|
const [packages, setPackages] = useState<PackageType[]>([])
|
||||||
const [projects, setProjects] = useState<Project[]>([])
|
const [projects, setProjects] = useState<Project[]>([])
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@@ -216,7 +216,7 @@ export default function CreatePackagePage() {
|
|||||||
{/* Error Message */}
|
{/* Error Message */}
|
||||||
{error && !isModalOpen && (
|
{error && !isModalOpen && (
|
||||||
<div className="mb-6 flex items-center gap-3 p-4 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800">
|
<div className="mb-6 flex items-center gap-3 p-4 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800">
|
||||||
<div className="w-5 h-5 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center flex-shrink-0">
|
<div className="w-5 h-5 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center shrink-0">
|
||||||
<span className="text-xs font-bold text-red-500">!</span>
|
<span className="text-xs font-bold text-red-500">!</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-red-600 dark:text-red-400 break-all">{error}</p>
|
<p className="text-sm text-red-600 dark:text-red-400 break-all">{error}</p>
|
||||||
@@ -270,7 +270,7 @@ export default function CreatePackagePage() {
|
|||||||
{/* Error Message in Modal */}
|
{/* Error Message in Modal */}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="flex items-center gap-3 p-4 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800">
|
<div className="flex items-center gap-3 p-4 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800">
|
||||||
<div className="w-5 h-5 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center flex-shrink-0">
|
<div className="w-5 h-5 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center shrink-0">
|
||||||
<span className="text-xs font-bold text-red-500">!</span>
|
<span className="text-xs font-bold text-red-500">!</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-red-600 dark:text-red-400 break-all">{error}</p>
|
<p className="text-sm text-red-600 dark:text-red-400 break-all">{error}</p>
|
||||||
@@ -14,7 +14,7 @@ import { createProject, fetchProjects, updateProject, deleteProject, type Projec
|
|||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { PoweredBy } from "@/components/powered-by"
|
import { PoweredBy } from "@/components/powered-by"
|
||||||
|
|
||||||
export default function CreateProjectPage() {
|
export default function ProjectPage() {
|
||||||
const [projects, setProjects] = useState<Project[]>([])
|
const [projects, setProjects] = useState<Project[]>([])
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
|
|
||||||
interface DetectionChartProps {
|
|
||||||
potholes: number
|
|
||||||
signboards: number
|
|
||||||
isLoading?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DetectionChart({ potholes, signboards, isLoading }: DetectionChartProps) {
|
|
||||||
const total = potholes + signboards
|
|
||||||
const potholePercent = total > 0 ? (potholes / total) * 100 : 50
|
|
||||||
const signboardPercent = total > 0 ? (signboards / total) * 100 : 50
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="overflow-hidden">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-lg font-bold text-blue-600">Detection Distribution</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="pt-0">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex items-center justify-center h-48">
|
|
||||||
<div className="w-32 h-32 rounded-full bg-primary/10" />
|
|
||||||
</div>
|
|
||||||
) : total === 0 ? (
|
|
||||||
<div className="flex items-center justify-center h-48 text-muted-foreground">
|
|
||||||
No detections yet
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
{/* Donut Chart */}
|
|
||||||
<div className="relative w-36 h-36 flex-shrink-0">
|
|
||||||
<svg viewBox="0 0 100 100" className="w-full h-full transform -rotate-90">
|
|
||||||
{/* Background circle */}
|
|
||||||
<circle
|
|
||||||
cx="50"
|
|
||||||
cy="50"
|
|
||||||
r="40"
|
|
||||||
fill="none"
|
|
||||||
stroke="hsl(var(--muted))"
|
|
||||||
strokeWidth="16"
|
|
||||||
opacity="0.2"
|
|
||||||
/>
|
|
||||||
{/* Potholes arc */}
|
|
||||||
<circle
|
|
||||||
cx="50"
|
|
||||||
cy="50"
|
|
||||||
r="40"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#potholeGradient)"
|
|
||||||
strokeWidth="16"
|
|
||||||
strokeDasharray={`${potholePercent * 2.51} 251`}
|
|
||||||
strokeLinecap="round"
|
|
||||||
className=""
|
|
||||||
/>
|
|
||||||
{/* Signboards arc */}
|
|
||||||
<circle
|
|
||||||
cx="50"
|
|
||||||
cy="50"
|
|
||||||
r="40"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#signboardGradient)"
|
|
||||||
strokeWidth="16"
|
|
||||||
strokeDashoffset={`-${potholePercent * 2.51}`}
|
|
||||||
strokeLinecap="round"
|
|
||||||
className=""
|
|
||||||
/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="potholeGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
||||||
<stop offset="0%" stopColor="#f97316" />
|
|
||||||
<stop offset="100%" stopColor="#ea580c" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="signboardGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
||||||
<stop offset="0%" stopColor="#3b82f6" />
|
|
||||||
<stop offset="100%" stopColor="#2563eb" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-2xl font-bold text-foreground">{total}</p>
|
|
||||||
<p className="text-[10px] text-muted-foreground uppercase">Total</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Legend */}
|
|
||||||
<div className="flex flex-col gap-3 flex-1">
|
|
||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-orange-500/10 border border-orange-500/20">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-3 h-3 rounded-full bg-gradient-to-r from-orange-500 to-orange-600" />
|
|
||||||
<span className="text-sm font-medium">Potholes</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="font-bold text-foreground">{potholes}</p>
|
|
||||||
<p className="text-xs text-muted-foreground">{potholePercent.toFixed(0)}%</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-blue-500/10 border border-blue-500/20">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-3 h-3 rounded-full bg-gradient-to-r from-blue-500 to-blue-600" />
|
|
||||||
<span className="text-sm font-medium">Signboards</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="font-bold text-foreground">{signboards}</p>
|
|
||||||
<p className="text-xs text-muted-foreground">{signboardPercent.toFixed(0)}%</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Eye, AlertCircle } from "lucide-react"
|
|
||||||
import { type Video } from "@/lib/api"
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
|
||||||
|
|
||||||
interface RecentAnalysesTableProps {
|
|
||||||
videos: Video[]
|
|
||||||
isLoading?: boolean
|
|
||||||
onViewResults?: (videoId: string, detectionType: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RecentAnalysesTable({ videos, isLoading, onViewResults }: RecentAnalysesTableProps) {
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
const date = new Date(dateString)
|
|
||||||
return date.toLocaleDateString("en-IN", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "short",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusBadge = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case "completed":
|
|
||||||
return <Badge className="bg-emerald-500/20 text-emerald-600 border-emerald-500/30 hover:bg-emerald-500/30">Completed</Badge>
|
|
||||||
case "processing":
|
|
||||||
return <Badge className="bg-blue-500/20 text-blue-600 border-blue-500/30 hover:bg-blue-500/30">Processing</Badge>
|
|
||||||
case "pending":
|
|
||||||
return <Badge className="bg-yellow-500/20 text-yellow-600 border-yellow-500/30 hover:bg-yellow-500/30">Pending</Badge>
|
|
||||||
case "failed":
|
|
||||||
return <Badge className="bg-red-500/20 text-red-600 border-red-500/30 hover:bg-red-500/30">Failed</Badge>
|
|
||||||
default:
|
|
||||||
return <Badge variant="secondary">{status}</Badge>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDetectionTypeBadge = (type: string) => {
|
|
||||||
if (type === "pothole-detection") {
|
|
||||||
return <Badge className="bg-orange-500/20 text-orange-600 border-orange-500/30">Pothole</Badge>
|
|
||||||
}
|
|
||||||
if (type === "pot-sign-detection") {
|
|
||||||
return <Badge className="bg-purple-500/20 text-purple-600 border-purple-500/30">Pothole & Sign</Badge>
|
|
||||||
}
|
|
||||||
return <Badge className="bg-blue-500/20 text-blue-600 border-blue-500/30">Signboard</Badge>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="overflow-hidden">
|
|
||||||
<CardHeader className="pb-3 border-b border-border/50">
|
|
||||||
<CardTitle className="text-lg font-bold text-blue-600">Recent Analyses</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-0">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="p-6 space-y-4">
|
|
||||||
{[1, 2, 3, 4, 5].map((i) => (
|
|
||||||
<div key={i} className="flex items-center justify-between gap-4">
|
|
||||||
<div className="flex-1 space-y-2">
|
|
||||||
<Skeleton className="h-4 w-[60%]" />
|
|
||||||
<Skeleton className="h-3 w-[40%]" />
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Skeleton className="h-6 w-16 rounded-full" />
|
|
||||||
<Skeleton className="h-6 w-16 rounded-full" />
|
|
||||||
</div>
|
|
||||||
<Skeleton className="h-8 w-16" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : videos.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
||||||
<AlertCircle className="h-10 w-10 mb-3 opacity-50" />
|
|
||||||
<p className="text-sm">No analyses found</p>
|
|
||||||
<p className="text-xs mt-1">Start a new analysis to see results here</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="divide-y divide-border/50">
|
|
||||||
{videos.slice(0, 8).map((video) => (
|
|
||||||
<div
|
|
||||||
key={video.id}
|
|
||||||
className="flex items-center justify-between p-4 hover:bg-primary/5"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-4 flex-1 min-w-0">
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium truncate">
|
|
||||||
{video.filename || `Video ${video.id.slice(0, 8)}...`}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{formatDate(video.created_at)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
|
||||||
{getDetectionTypeBadge(video.detection_type)}
|
|
||||||
{getStatusBadge(video.status)}
|
|
||||||
</div>
|
|
||||||
<div className="text-right shrink-0 w-20">
|
|
||||||
<p className="text-sm font-bold text-foreground">
|
|
||||||
{video.detection_type === "pothole-detection"
|
|
||||||
? video.unique_pothole ?? 0
|
|
||||||
: video.detection_type === "pot-sign-detection"
|
|
||||||
? (video.unique_pothole ?? 0) + (video.unique_defected_sign_board ?? 0) + (video.unique_good_sign_board ?? 0)
|
|
||||||
: (video.unique_defected_sign_board ?? 0) + (video.unique_good_sign_board ?? 0)}
|
|
||||||
</p>
|
|
||||||
<p className="text-[10px] text-muted-foreground uppercase">Detections</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{video.status === "completed" && onViewResults && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => onViewResults(video.id, video.detection_type)}
|
|
||||||
className="ml-4 shrink-0"
|
|
||||||
>
|
|
||||||
<Eye className="h-4 w-4 mr-1" />
|
|
||||||
View
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
|
||||||
import { LucideIcon } from "lucide-react"
|
|
||||||
|
|
||||||
interface StatsCardProps {
|
|
||||||
title: string
|
|
||||||
value: string | number
|
|
||||||
icon: LucideIcon
|
|
||||||
gradient: string
|
|
||||||
isLoading?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StatsCard({ title, value, icon: Icon, gradient, isLoading }: StatsCardProps) {
|
|
||||||
return (
|
|
||||||
<Card className="glass-card card-glow border-0 overflow-hidden group">
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
||||||
{title}
|
|
||||||
</p>
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="h-8 w-16 bg-primary/10 rounded" />
|
|
||||||
) : (
|
|
||||||
<p className="text-3xl font-bold text-foreground">
|
|
||||||
{value}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={`p-3 rounded-xl ${gradient}`}>
|
|
||||||
<Icon className="h-6 w-6 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useState } from "react"
|
|
||||||
import { useRouter, usePathname } from "next/navigation"
|
|
||||||
import {
|
|
||||||
Sheet,
|
|
||||||
SheetContent,
|
|
||||||
SheetHeader,
|
|
||||||
SheetTitle,
|
|
||||||
SheetTrigger,
|
|
||||||
} from "@/components/ui/sheet"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
|
||||||
Menu,
|
|
||||||
LayoutDashboard,
|
|
||||||
ChevronRight,
|
|
||||||
PlusCircle,
|
|
||||||
FolderPlus,
|
|
||||||
Package,
|
|
||||||
MapPin,
|
|
||||||
Map
|
|
||||||
} from "lucide-react"
|
|
||||||
import { PoweredBy } from "@/components/powered-by"
|
|
||||||
|
|
||||||
interface NavItem {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
href: string
|
|
||||||
icon: React.ElementType
|
|
||||||
}
|
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
|
||||||
{
|
|
||||||
title: "Dashboard",
|
|
||||||
description: "View analytics, statistics, and recent analyses",
|
|
||||||
href: "/dashboard",
|
|
||||||
icon: LayoutDashboard
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "New Analysis",
|
|
||||||
description: "Start a new road analysis project",
|
|
||||||
href: "/new-analysis",
|
|
||||||
icon: PlusCircle
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Create Project",
|
|
||||||
description: "Create a new infrastructure project",
|
|
||||||
href: "/create-project",
|
|
||||||
icon: FolderPlus
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Create Package",
|
|
||||||
description: "Add a package under an existing project",
|
|
||||||
href: "/create-package",
|
|
||||||
icon: Package
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Create Location",
|
|
||||||
description: "Add a location under a project & package",
|
|
||||||
href: "/create-location",
|
|
||||||
icon: MapPin
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Show Map",
|
|
||||||
description: "View all detections on an interactive map",
|
|
||||||
href: "/map",
|
|
||||||
icon: Map
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export function NavigationMenu() {
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
|
||||||
const router = useRouter()
|
|
||||||
const pathname = usePathname()
|
|
||||||
|
|
||||||
const handleNavigation = (href: string) => {
|
|
||||||
setIsOpen(false)
|
|
||||||
router.push(href)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<SheetTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="fixed top-4 left-4 z-50 h-11 w-11 rounded-xl glass-card border border-white/20 shadow-lg hover:bg-white/20 hover:scale-105 transition-all duration-300"
|
|
||||||
aria-label="Open navigation menu"
|
|
||||||
>
|
|
||||||
<Menu className="h-5 w-5 text-foreground" />
|
|
||||||
</Button>
|
|
||||||
</SheetTrigger>
|
|
||||||
<SheetContent side="left" className="w-[320px] sm:w-[380px] bg-white backdrop-blur-xl border-r border-slate-200">
|
|
||||||
<SheetHeader className="pb-6 border-b border-slate-100">
|
|
||||||
<SheetTitle className="text-2xl font-bold text-[#3b82f6]">
|
|
||||||
VisionRoad
|
|
||||||
</SheetTitle>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
AI-Powered Road Detection System
|
|
||||||
</p>
|
|
||||||
</SheetHeader>
|
|
||||||
|
|
||||||
{/* Navigation Items */}
|
|
||||||
<nav className="mt-6 space-y-2">
|
|
||||||
{navItems.map((item) => {
|
|
||||||
const isActive = pathname === item.href
|
|
||||||
const Icon = item.icon
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={item.href}
|
|
||||||
onClick={() => handleNavigation(item.href)}
|
|
||||||
className={`w-full flex items-center gap-4 p-4 rounded-xl text-left transition-all duration-300 group ${isActive
|
|
||||||
? "bg-slate-50 border border-slate-200"
|
|
||||||
: "hover:bg-slate-50 border border-transparent hover:border-slate-100"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className={`p-2.5 rounded-lg transition-all duration-300 ${isActive
|
|
||||||
? "bg-linear-to-b from-[#3895FF] to-[#86B8F1] text-white"
|
|
||||||
: "bg-[#f0fafd] text-slate-900 group-hover:bg-linear-to-b group-hover:from-[#3895FF] group-hover:to-[#86B8F1] group-hover:text-white border border-slate-100 shadow-sm"
|
|
||||||
}`}>
|
|
||||||
<Icon
|
|
||||||
className={`h-5 w-5 ${isActive ? 'stroke-[2.5]' : 'stroke-2'}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h3 className={`font-semibold text-sm ${isActive ? "text-[#1e40af]" : "text-gray-700"
|
|
||||||
}`}>
|
|
||||||
{item.title}
|
|
||||||
</h3>
|
|
||||||
<p className="text-xs text-gray-400 truncate mt-0.5">
|
|
||||||
{item.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ChevronRight className={`h-4 w-4 transition-all duration-300 ${isActive
|
|
||||||
? "text-[#1e40af] opacity-100"
|
|
||||||
: "text-gray-300 opacity-0 group-hover:opacity-100"
|
|
||||||
}`} />
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="absolute bottom-4 left-4 right-4">
|
|
||||||
<PoweredBy />
|
|
||||||
</div>
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import Image from "next/image"
|
|||||||
|
|
||||||
export function PoweredBy() {
|
export function PoweredBy() {
|
||||||
return (
|
return (
|
||||||
<div className=" flex items-center justify-center gap-2 transition-opacity duration-300">
|
<div className=" flex items-center justify-center gap-2 mt-3 transition-opacity duration-300">
|
||||||
<span className="font-medium powered-blue-gradient">
|
<span className="font-medium powered-blue-gradient">
|
||||||
Powered by
|
Powered by
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -21,19 +21,19 @@ const navItems: NavItem[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Project",
|
title: "Project",
|
||||||
href: "/create-project",
|
href: "/project",
|
||||||
icon: FolderPlus,
|
icon: FolderPlus,
|
||||||
gradient: "from-blue-400 to-blue-600"
|
gradient: "from-blue-400 to-blue-600"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Package",
|
title: "Package",
|
||||||
href: "/create-package",
|
href: "/package",
|
||||||
icon: Package,
|
icon: Package,
|
||||||
gradient: "from-violet-400 to-purple-500"
|
gradient: "from-violet-400 to-purple-500"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Location",
|
title: "Location",
|
||||||
href: "/create-location",
|
href: "/location",
|
||||||
icon: MapPin,
|
icon: MapPin,
|
||||||
gradient: "from-amber-400 to-orange-500"
|
gradient: "from-amber-400 to-orange-500"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Target, AlertTriangle, Film, Activity, Gauge, Monitor, SignpostBig } from "lucide-react"
|
|
||||||
import type { DetectionData } from "@/lib/types"
|
|
||||||
|
|
||||||
type SummarySectionProps = {
|
|
||||||
data: DetectionData
|
|
||||||
}
|
|
||||||
|
|
||||||
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_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",
|
|
||||||
value: `${(data.summary.detection_rate || 0).toFixed(1)}%`,
|
|
||||||
icon: Activity,
|
|
||||||
color: "text-green-500",
|
|
||||||
bgColor: "bg-green-50 dark:bg-green-950/30",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Video FPS",
|
|
||||||
value: (data.video_info.fps || 0).toFixed(1),
|
|
||||||
icon: Gauge,
|
|
||||||
color: "text-orange-500",
|
|
||||||
bgColor: "bg-orange-50 dark:bg-orange-950/30",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Resolution",
|
|
||||||
value: `${data.video_info.width}×${data.video_info.height}`,
|
|
||||||
icon: Monitor,
|
|
||||||
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 road analysis results</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<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 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-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>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user