ui changes

This commit is contained in:
2026-03-10 15:03:50 +05:30
parent 01c1117a0b
commit dd13fa139e
28 changed files with 1022 additions and 849 deletions

View File

@@ -89,12 +89,12 @@ export function DashboardMap({
}
return (
<Card className={`overflow-hidden ${className}`}>
<Card className={`overflow-hidden pb-0 bg-white/60 rounded-b-none shadow-none border-none ${className}`}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center shadow-lg border-2 border-[#1e40af]">
<MapPin className="h-5 w-5 text-[#2563eb]" />
<div className="w-10 h-10 rounded-md bg-linear-to-b from-[#225999] to-[#56A5FF] flex items-center justify-center ">
<MapPin className="h-5 w-5 text-white" />
</div>
<div>
<CardTitle className="text-base font-bold text-[#2563eb]">
@@ -132,7 +132,7 @@ export function DashboardMap({
</div>
</CardHeader>
<CardContent className="p-0">
<div className="h-[400px] w-full relative">
<div className="h-[500px] p-2 w-full relative">
{isLoading ? (
<div className="h-full w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900/50">
<Loader2 className="h-8 w-8 text-indigo-500" />

View File

@@ -0,0 +1,59 @@
"use client"
import { Skeleton } from "@/components/ui/skeleton"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
export function DashboardSkeleton() {
return (
<div className="space-y-6">
{/* Stats Cards Skeleton */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
{[...Array(6)].map((_, i) => (
<Card key={i} className="bg-white/60 backdrop-blur-lg border-none shadow-none overflow-hidden py-8 px-6">
<CardContent className="p-0 flex items-center justify-between gap-6">
<div className="flex flex-col gap-2 flex-1">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-10 w-20" />
<Skeleton className="h-3 w-40" />
</div>
<Skeleton className="w-16 h-16 rounded-lg shrink-0" />
</CardContent>
</Card>
))}
</div>
{/* Charts Row Skeleton */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
{[...Array(2)].map((_, i) => (
<Card key={i} className="rounded-md bg-white/60 backdrop-blur-lg overflow-hidden border-none shadow-none">
<CardHeader className="pb-2 border-none">
<div className="flex items-center gap-2">
<Skeleton className="w-10 h-10 rounded-md" />
<Skeleton className="h-6 w-48" />
</div>
</CardHeader>
<CardContent className="pt-4 h-[350px] flex items-center justify-center">
<Skeleton className="h-[300px] w-full max-w-[300px] rounded-full" />
</CardContent>
</Card>
))}
</div>
{/* Map Skeleton */}
<Card className="rounded-md bg-white/60 backdrop-blur-lg overflow-hidden border-none shadow-none">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Skeleton className="w-10 h-10 rounded-md" />
<Skeleton className="h-6 w-48" />
</div>
<Skeleton className="h-8 w-32 rounded-full" />
</div>
</CardHeader>
<CardContent className="p-2">
<Skeleton className="h-[500px] w-full rounded-md" />
</CardContent>
</Card>
</div>
)
}

View File

@@ -9,7 +9,7 @@ interface DetectionDonutChartProps {
roadCrack: number
damagedRoadMarking: number
goodSignboard: number
isLoading: boolean
isLoading?: boolean
}
const COLORS = {
@@ -26,7 +26,7 @@ export function DetectionDonutChart({
roadCrack,
damagedRoadMarking,
goodSignboard,
isLoading
isLoading = false
}: DetectionDonutChartProps) {
if (isLoading) {
return (

View File

@@ -36,23 +36,26 @@ export function FilterSelector({
isLoading = false
}: FilterSelectorProps) {
return (
<div className="flex flex-wrap items-center gap-3 p-3 rounded-xl bg-card border border-[var(--border)] shadow-sm">
<div className="flex flex-col gap-4 p-4">
{/* Project Dropdown */}
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-blue-100 dark:bg-blue-900/50">
<FolderOpen className="h-4 w-4 text-indigo-500" />
<div className="space-y-2">
<div className="flex items-center gap-2 mb-1">
<div className="p-1 rounded-md bg-blue-100 dark:bg-blue-900/50">
<FolderOpen className="h-3.5 w-3.5 text-blue-600" />
</div>
<span className="text-[11px] font-bold text-gray-400 uppercase tracking-wider">Project</span>
</div>
<Select
value={selectedProjectId || ""}
onValueChange={onProjectChange}
disabled={isLoading || projects.length === 0}
>
<SelectTrigger className="w-[200px] h-8 text-sm bg-transparent border-0 shadow-none focus:ring-0 px-1">
<SelectTrigger className="h-9 text-sm bg-slate-50 border border-slate-200 shadow-none ring-0! focus:ring-0 px-3 w-full rounded-lg">
<SelectValue placeholder="Select project" />
</SelectTrigger>
<SelectContent>
<SelectContent className="rounded-xl border-slate-200">
{projects.map((project) => (
<SelectItem key={project.id} value={project.id}>
<SelectItem key={project.id} value={project.id} className="rounded-lg">
{project.name}
</SelectItem>
))}
@@ -60,29 +63,27 @@ export function FilterSelector({
</Select>
</div>
{/* Divider */}
{selectedProjectId && packages.length > 0 && (
<div className="h-5 w-px bg-gray-300 dark:bg-gray-600" />
)}
{/* Package Dropdown */}
{selectedProjectId && packages.length > 0 && (
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-emerald-100 dark:bg-emerald-900/50">
<Package className="h-4 w-4 text-emerald-500" />
{selectedProjectId && (
<div className="space-y-2">
<div className="flex items-center gap-2 mb-1">
<div className="p-1 rounded-md bg-emerald-100 dark:bg-emerald-900/50">
<Package className="h-3.5 w-3.5 text-emerald-600" />
</div>
<span className="text-[11px] font-bold text-gray-400 uppercase tracking-wider">Package</span>
</div>
<Select
value={selectedPackageId || "all"}
onValueChange={onPackageChange}
disabled={isLoading}
>
<SelectTrigger className="w-[200px] h-8 text-sm bg-transparent border-0 shadow-none focus:ring-0 px-1">
<SelectTrigger className="h-9 text-sm bg-slate-50 border border-slate-200 shadow-none ring-0! focus:ring-0 px-3 w-full rounded-lg">
<SelectValue placeholder="All packages" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Packages</SelectItem>
<SelectContent className="rounded-xl border-slate-200">
<SelectItem value="all" className="rounded-lg">All Packages</SelectItem>
{packages.map((pkg) => (
<SelectItem key={pkg.id} value={pkg.id}>
<SelectItem key={pkg.id} value={pkg.id} className="rounded-lg">
{pkg.name}
</SelectItem>
))}
@@ -91,29 +92,27 @@ export function FilterSelector({
</div>
)}
{/* Divider */}
{selectedPackageId && selectedPackageId !== "all" && locations.length > 0 && (
<div className="h-5 w-px bg-gray-300 dark:bg-gray-600" />
)}
{/* Location Dropdown */}
{selectedPackageId && selectedPackageId !== "all" && locations.length > 0 && (
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-purple-100 dark:bg-purple-900/50">
<MapPin className="h-4 w-4 text-purple-500" />
{selectedPackageId && selectedPackageId !== "all" && (
<div className="space-y-2">
<div className="flex items-center gap-2 mb-1">
<div className="p-1 rounded-md bg-purple-100 dark:bg-purple-900/50">
<MapPin className="h-3.5 w-3.5 text-purple-600" />
</div>
<span className="text-[11px] font-bold text-gray-400 uppercase tracking-wider">Location</span>
</div>
<Select
value={selectedLocationId || "all"}
onValueChange={onLocationChange}
disabled={isLoading}
>
<SelectTrigger className="w-[200px] h-8 text-sm bg-transparent border-0 shadow-none focus:ring-0 px-1">
<SelectTrigger className="h-9 text-sm bg-slate-50 border border-slate-200 shadow-none ring-0! focus:ring-0 px-3 w-full rounded-lg">
<SelectValue placeholder="All locations" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Locations</SelectItem>
<SelectContent className="rounded-xl border-slate-200">
<SelectItem value="all" className="rounded-lg">All Locations</SelectItem>
{locations.map((loc) => (
<SelectItem key={loc.id} value={loc.id}>
<SelectItem key={loc.id} value={loc.id} className="rounded-lg">
{loc.name}
</SelectItem>
))}
@@ -122,5 +121,5 @@ export function FilterSelector({
</div>
)}
</div>
)
);
}

View File

@@ -17,43 +17,43 @@ const gradientStyles = {
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"
},
coral: {
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"
},
blue: {
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"
},
purple: {
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"
},
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"
}
}
@@ -68,7 +68,7 @@ export function GradientStatsCard({
const styles = gradientStyles[gradient]
return (
<Card className="bg-white dark:bg-gray-900 border-none shadow-xl shadow-blue-500/5 overflow-hidden py-8 px-6">
<Card className="bg-white/60 backdrop-blur-lg border-none shadow-none hover:shadow-none overflow-hidden py-8 px-6">
<CardContent className="p-0 flex items-center justify-between gap-6">
<div className="flex flex-col gap-1 min-w-0">
<h3 className="text-sm font-bold text-blue-600/70 dark:text-blue-400/70 uppercase tracking-widest">
@@ -79,11 +79,11 @@ export function GradientStatsCard({
<div className="h-14 w-24 bg-gray-100 dark:bg-gray-800 rounded-xl mt-2" />
) : (
<>
<p className="text-3xl md:text-4xl font-black tracking-tighter leading-tight text-[#1e3a8a]">
<p className="text-4xl font-bold heading-blue-gradient">
{value}
</p>
{subtitle && (
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 mt-1 opacity-80 italic">
<p className="text-xs font-medium text-gray-500 mt-1">
{subtitle}
</p>
)}
@@ -92,9 +92,12 @@ export function GradientStatsCard({
</div>
</div>
<div className={`
w-20 h-20 rounded-3xl flex items-center justify-center flex-shrink-0
${styles.iconBg} shadow-[0_10px_30px_rgba(37,99,235,0.2)]
<div
style={{
background: "linear-gradient(180deg, #225999 0%, #56A5FF 100%)",
}}
className={`
w-16 h-16 self-start rounded-lg flex items-center justify-center
`}>
<Icon className="h-10 w-10 text-white" />
</div>

View File

@@ -15,7 +15,7 @@ interface LocationData {
interface LocationBarChartProps {
data: LocationData[]
isLoading: boolean
isLoading?: boolean
}
const COLORS = {
@@ -26,7 +26,7 @@ const COLORS = {
good_sign_board: "#34d399" // Lighter Emerald
}
export function LocationBarChart({ data, isLoading }: LocationBarChartProps) {
export function LocationBarChart({ data, isLoading = false }: LocationBarChartProps) {
if (isLoading) {
return (
<div className="h-[300px] flex items-center justify-center">

View File

@@ -5,6 +5,7 @@ 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[]
@@ -56,9 +57,19 @@ export function RecentAnalysesTable({ videos, isLoading, onViewResults }: Recent
</CardHeader>
<CardContent className="p-0">
{isLoading ? (
<div className="p-6 space-y-3">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="h-14 bg-primary/5 rounded-lg" />
<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 ? (
@@ -83,11 +94,11 @@ export function RecentAnalysesTable({ videos, isLoading, onViewResults }: Recent
{formatDate(video.created_at)}
</p>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<div className="flex items-center gap-2 shrink-0">
{getDetectionTypeBadge(video.detection_type)}
{getStatusBadge(video.status)}
</div>
<div className="text-right flex-shrink-0 w-20">
<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
@@ -103,7 +114,7 @@ export function RecentAnalysesTable({ videos, isLoading, onViewResults }: Recent
variant="ghost"
size="sm"
onClick={() => onViewResults(video.id, video.detection_type)}
className="ml-4 flex-shrink-0"
className="ml-4 shrink-0"
>
<Eye className="h-4 w-4 mr-1" />
View