chore: add culvert type in model method and fix dashboard skeleton issue
This commit is contained in:
@@ -24,22 +24,15 @@ import { cn } from '@/lib/utils';
|
|||||||
|
|
||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||||
|
|
||||||
const DETECTION_TYPES = [
|
|
||||||
{ value: 'pothole-detection', label: 'Pothole Detection' },
|
|
||||||
{ value: 'sign-board-detection', label: 'Signboard Detection' },
|
|
||||||
{ value: 'pot-sign-detection', label: 'Pothole & Signboard Detection' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const DETECTION_METHODS = [
|
const DETECTION_METHODS = [
|
||||||
{ value: 'yolo', label: 'YOLO Detection Model' },
|
{ value: 'yolo', label: 'YOLO Detection Model' },
|
||||||
{ value: 'yolo_vl', label: 'YOLO with Vision-Language Model' },
|
{ value: 'yolo_vl', label: 'YOLO with Vision-Language Model' },
|
||||||
{ value: 'sam3', label: 'OpenAI SAM 3 Segmentation Model' },
|
{ value: 'sam3', label: 'OpenAI SAM 3 Segmentation Model' },
|
||||||
{ value: 'yoloe', label: 'YOLOE Open-Vocabulary Detection' },
|
{ value: 'yoloe', label: 'YOLOE Open-Vocabulary Detection' },
|
||||||
{ value: 'yoloe_trained_vl', label: 'YOLOE With Vision Language Model' },
|
{ value: 'yoloe_trained_vl', label: 'YOLOE With Vision Language Model' },
|
||||||
|
{ value: 'culvert_detection', label: 'Culvert Detection' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type DetectionType = 'pothole-detection' | 'sign-board-detection' | 'pot-sign-detection';
|
|
||||||
|
|
||||||
export default function UploadPage() {
|
export default function UploadPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [session, setSession] = useState<SessionContext | null>(null);
|
const [session, setSession] = useState<SessionContext | null>(null);
|
||||||
@@ -48,8 +41,6 @@ export default function UploadPage() {
|
|||||||
// Form states
|
// Form states
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [jsonFile, setJsonFile] = useState<File | null>(null);
|
const [jsonFile, setJsonFile] = useState<File | null>(null);
|
||||||
const [speed, setSpeed] = useState(30);
|
|
||||||
const [detectionType, setDetectionType] = useState<DetectionType>('pothole-detection');
|
|
||||||
const [selectMethod, setSelectMethod] = useState('yolo_vl');
|
const [selectMethod, setSelectMethod] = useState('yolo_vl');
|
||||||
|
|
||||||
// Upload states
|
// Upload states
|
||||||
@@ -77,9 +68,12 @@ export default function UploadPage() {
|
|||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
formData.append('detection_type', detectionType);
|
|
||||||
formData.append('speed_kmh', speed.toString());
|
|
||||||
formData.append('detection_mode', selectMethod);
|
formData.append('detection_mode', selectMethod);
|
||||||
|
|
||||||
|
if (session?.chainageId) {
|
||||||
|
formData.append('chainage_id', session.chainageId);
|
||||||
|
}
|
||||||
|
|
||||||
if (jsonFile) {
|
if (jsonFile) {
|
||||||
formData.append('json_file', jsonFile);
|
formData.append('json_file', jsonFile);
|
||||||
}
|
}
|
||||||
@@ -101,7 +95,7 @@ export default function UploadPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionService.saveVideoData({ videoId: result.video_id, detectionType });
|
sessionService.saveVideoData({ videoId: result.video_id });
|
||||||
|
|
||||||
// Redirect to the dynamic processing page
|
// Redirect to the dynamic processing page
|
||||||
router.push(`/upload/${result.video_id}`);
|
router.push(`/upload/${result.video_id}`);
|
||||||
@@ -124,11 +118,7 @@ export default function UploadPage() {
|
|||||||
router.push(ROUTES.NEW_ANALYSIS);
|
router.push(ROUTES.NEW_ANALYSIS);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTitle = () => {
|
const getTitle = () => 'Road Analysis';
|
||||||
if (detectionType === 'pothole-detection') return 'Pothole Detection';
|
|
||||||
if (detectionType === 'sign-board-detection') return 'Signboard Detection';
|
|
||||||
return 'Pothole & Signboard Detection';
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -160,30 +150,33 @@ export default function UploadPage() {
|
|||||||
<Card className="p-0 border shadow-sm overflow-hidden">
|
<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-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-wrap items-center gap-x-12 gap-y-4">
|
||||||
<div className="flex flex-col">
|
{[
|
||||||
|
{ label: 'Project', value: session.projectName, primary: true },
|
||||||
|
{ label: 'Package', value: session.packageName },
|
||||||
|
{ label: 'Chainage', value: session.chainageName },
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={item.label}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col',
|
||||||
|
idx !== 0 && 'border-l pl-12 border-border/60',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
|
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
|
||||||
Project
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base font-bold leading-tight">
|
<span
|
||||||
{session.projectName}
|
className={cn(
|
||||||
</span>
|
'leading-tight',
|
||||||
</div>
|
item.primary
|
||||||
<div className="flex flex-col border-l pl-12 border-border/60">
|
? 'text-base font-bold'
|
||||||
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1.5">
|
: 'text-sm font-semibold text-muted-foreground',
|
||||||
Package
|
)}
|
||||||
</span>
|
>
|
||||||
<span className="text-sm font-semibold text-muted-foreground leading-tight">
|
{item.value}
|
||||||
{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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -203,12 +196,12 @@ export default function UploadPage() {
|
|||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<CardTitle className="text-xl font-bold">Upload Video</CardTitle>
|
<CardTitle className="text-xl font-bold">Upload Video</CardTitle>
|
||||||
<CardDescription className="text-sm">
|
<CardDescription className="text-sm">
|
||||||
Select video file, detection type, vehicle speed, and method for analysis
|
Select video file, GPS JSON file, and method for analysis
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-4 space-y-6">
|
<CardContent className="pt-4 space-y-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-4">
|
<div className="space-y-6">
|
||||||
{/* Video File Input */}
|
{/* Video File Input - Full Width */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="video-file" className="text-sm font-semibold">
|
<Label htmlFor="video-file" className="text-sm font-semibold">
|
||||||
Video File
|
Video File
|
||||||
@@ -226,6 +219,8 @@ export default function UploadPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* JSON File and Method - Both in one line */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{/* JSON File Input */}
|
{/* JSON File Input */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="json-file" className="text-sm font-semibold">
|
<Label htmlFor="json-file" className="text-sm font-semibold">
|
||||||
@@ -243,56 +238,17 @@ export default function UploadPage() {
|
|||||||
className="h-11 bg-muted/20 file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:bg-primary/10 file:text-primary file:font-medium file:text-xs hover:file:bg-primary/20"
|
className="h-11 bg-muted/20 file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:bg-primary/10 file:text-primary file:font-medium file:text-xs hover:file:bg-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Detection, Speed, and Method Row */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
{/* Detection Type */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="detection-type" className="text-sm font-semibold">
|
|
||||||
Detection Type
|
|
||||||
</Label>
|
|
||||||
<Select
|
|
||||||
value={detectionType}
|
|
||||||
onValueChange={(v) => setDetectionType(v as DetectionType)}
|
|
||||||
disabled={uploading}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="detection-type" className="h-11">
|
|
||||||
<SelectValue placeholder="Select detection type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{DETECTION_TYPES.map((type) => (
|
|
||||||
<SelectItem key={type.value} value={type.value}>
|
|
||||||
{type.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Speed Input */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="speed" className="text-sm font-semibold">
|
|
||||||
Vehicle Speed (km/h)
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="speed"
|
|
||||||
type="number"
|
|
||||||
min={1}
|
|
||||||
max={200}
|
|
||||||
value={speed}
|
|
||||||
onChange={(e) => setSpeed(Number(e.target.value))}
|
|
||||||
disabled={uploading}
|
|
||||||
className="h-11"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Select Method */}
|
{/* Select Method */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="select-method" className="text-sm font-semibold">
|
<Label htmlFor="select-method" className="text-sm font-semibold">
|
||||||
Select Method
|
Select Method
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={selectMethod} onValueChange={setSelectMethod} disabled={uploading}>
|
<Select
|
||||||
|
value={selectMethod}
|
||||||
|
onValueChange={setSelectMethod}
|
||||||
|
disabled={uploading}
|
||||||
|
>
|
||||||
<SelectTrigger id="select-method" className="h-11">
|
<SelectTrigger id="select-method" className="h-11">
|
||||||
<SelectValue placeholder="Select method" />
|
<SelectValue placeholder="Select method" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@@ -306,6 +262,7 @@ export default function UploadPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Error Display */}
|
{/* Error Display */}
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -7,16 +7,18 @@ export function DashboardSkeleton() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Stats Cards Skeleton */}
|
{/* Stats Cards Skeleton */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
||||||
{[...Array(6)].map((_, i) => (
|
{[...Array(6)].map((_, i) => (
|
||||||
<Card key={i} className="py-8 px-6">
|
<Card key={i} className="py-6 px-4">
|
||||||
<CardContent className="p-0 flex items-center justify-between gap-6">
|
<CardContent className="p-0 flex items-center justify-between gap-6">
|
||||||
<div className="flex flex-col gap-2 flex-1">
|
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
||||||
<Skeleton className="h-4 w-32" />
|
<Skeleton className="h-4 w-24 mb-1" />
|
||||||
<Skeleton className="h-10 w-20" />
|
<div className="flex flex-col gap-1">
|
||||||
<Skeleton className="h-3 w-40" />
|
<Skeleton className="h-9 w-20" />
|
||||||
|
<Skeleton className="h-3 w-32" />
|
||||||
</div>
|
</div>
|
||||||
<Skeleton className="w-16 h-16 rounded-lg shrink-0" />
|
</div>
|
||||||
|
<Skeleton className="w-12 h-12 rounded-xl shrink-0" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -24,30 +26,59 @@ export function DashboardSkeleton() {
|
|||||||
|
|
||||||
{/* Charts Row Skeleton */}
|
{/* Charts Row Skeleton */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||||
{[...Array(2)].map((_, i) => (
|
{/* Donut Chart Skeleton */}
|
||||||
<Card key={i}>
|
<Card className="h-full">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="items-center pb-2">
|
||||||
<div className="flex items-center gap-2">
|
<Skeleton className="h-6 w-48 mb-2" />
|
||||||
<Skeleton className="w-10 h-10 rounded-md" />
|
<Skeleton className="h-4 w-64" />
|
||||||
<Skeleton className="h-6 w-48" />
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-4 h-[350px] flex items-center justify-center">
|
<CardContent className="h-[300px] flex items-center justify-center">
|
||||||
<Skeleton className="h-[300px] w-full max-w-[300px] rounded-full" />
|
<Skeleton className="h-56 w-56 rounded-full border-20 border-muted" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Bar Chart Skeleton */}
|
||||||
|
<Card className="h-full">
|
||||||
|
<CardHeader className="items-center pb-2">
|
||||||
|
<Skeleton className="h-6 w-40 mb-2" />
|
||||||
|
<Skeleton className="h-4 w-52" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="h-[300px] flex items-end justify-between gap-2 px-6 pb-8 pt-4">
|
||||||
|
{[...Array(8)].map((_, i) => {
|
||||||
|
const heights = ['60%', '40%', '75%', '50%', '65%', '35%', '80%', '45%'];
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex flex-col gap-1 items-center flex-1">
|
||||||
|
<Skeleton
|
||||||
|
className="w-full rounded-t-sm"
|
||||||
|
style={{ height: heights[i % heights.length] }}
|
||||||
|
/>
|
||||||
|
<Skeleton className="h-2 w-full mt-2" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Map Skeleton */}
|
{/* Map Skeleton */}
|
||||||
<Card>
|
<Card className="overflow-hidden">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<Skeleton className="w-10 h-10 rounded-md" />
|
<Skeleton className="w-9 h-9 rounded-md" />
|
||||||
<Skeleton className="h-6 w-48" />
|
<div className="flex flex-col gap-1">
|
||||||
|
<Skeleton className="h-5 w-32" />
|
||||||
|
<Skeleton className="h-3 w-48" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{[...Array(3)].map((_, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-1">
|
||||||
|
<Skeleton className="w-2.5 h-2.5 rounded-full" />
|
||||||
|
<Skeleton className="h-3 w-16" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Skeleton className="h-8 w-32 rounded-full" />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-2">
|
<CardContent className="p-2">
|
||||||
|
|||||||
@@ -583,9 +583,7 @@ function SidebarMenuSkeleton({
|
|||||||
showIcon?: boolean;
|
showIcon?: boolean;
|
||||||
}) {
|
}) {
|
||||||
// Random width between 50 to 90%.
|
// Random width between 50 to 90%.
|
||||||
const width = React.useMemo(() => {
|
const width = '70%';
|
||||||
return `${Math.floor(Math.random() * 40) + 50}%`;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user