144 lines
5.0 KiB
TypeScript
144 lines
5.0 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { ExternalLink, Ticket, UserRound } from 'lucide-react';
|
|
|
|
import { ProtectedVideoPlayer } from '@/components/media/ProtectedVideoPlayer';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogBody,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { PERMISSIONS } from '@/constants/permissions';
|
|
import { usePermissions } from '@/hooks/usePermissions';
|
|
import { ROUTES } from '@/utils/routes';
|
|
import type { UploadListItem } from '@/types';
|
|
import { formatDate } from '@/utils/date';
|
|
|
|
import { UploadResultCell } from './UploadResultCell';
|
|
import { UploadStatusBadge } from './UploadStatusBadge';
|
|
|
|
interface UploadDetailsDialogProps {
|
|
upload: UploadListItem | null;
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
}
|
|
|
|
export function UploadDetailsDialog({
|
|
upload,
|
|
open,
|
|
onOpenChange,
|
|
}: UploadDetailsDialogProps) {
|
|
const { hasPermission } = usePermissions();
|
|
const canOpenTicket = hasPermission(PERMISSIONS.TICKET.READ);
|
|
const details = [
|
|
{ label: 'Project', value: upload?.project },
|
|
{ label: 'Package', value: upload?.package },
|
|
{ label: 'Segment', value: upload?.segment },
|
|
{ label: 'Chainage', value: upload?.chainage },
|
|
{ label: 'Uploaded', value: formatDate(upload?.uploaded_at) },
|
|
];
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="sm:max-w-6xl">
|
|
<DialogHeader>
|
|
<DialogTitle>Upload Details</DialogTitle>
|
|
<DialogDescription>
|
|
Preview the inspection video and review its processing details.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<DialogBody>
|
|
<div className="grid gap-5 lg:grid-cols-[minmax(0,1.7fr)_minmax(18rem,0.8fr)]">
|
|
<section className="min-w-0 space-y-2">
|
|
<p className="text-sm font-medium">Video Preview</p>
|
|
<ProtectedVideoPlayer
|
|
url={open ? upload?.raw_video_url : undefined}
|
|
label={upload?.video_name}
|
|
className="rounded-lg"
|
|
unavailableTitle="Video preview unavailable"
|
|
/>
|
|
</section>
|
|
|
|
<aside className="min-w-0 space-y-4 rounded-lg border bg-muted/15 p-4">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Status</p>
|
|
<div className="mt-1.5">
|
|
{upload ? (
|
|
<UploadStatusBadge
|
|
status={upload.status}
|
|
progress={upload.progress}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-xs text-muted-foreground">Detections</p>
|
|
<Badge variant="secondary" className="mt-1.5 tabular-nums">
|
|
{upload?.total_detections ?? 0}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t pt-4">
|
|
<p className="mb-2 text-xs text-muted-foreground">Result</p>
|
|
{upload ? <UploadResultCell item={upload} /> : null}
|
|
</div>
|
|
|
|
<dl className="grid grid-cols-2 gap-x-4 gap-y-3 border-t pt-4">
|
|
{details.map((detail) => (
|
|
<div
|
|
key={detail.label}
|
|
className={
|
|
detail.label === 'Uploaded' ? 'col-span-2' : undefined
|
|
}
|
|
>
|
|
<dt className="text-xs text-muted-foreground">
|
|
{detail.label}
|
|
</dt>
|
|
<dd className="mt-1 truncate text-sm font-medium">
|
|
{detail.value || '—'}
|
|
</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
|
|
<div className="flex min-w-0 items-start gap-2 border-t pt-4">
|
|
<UserRound className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
|
|
<div className="min-w-0">
|
|
<p className="truncate text-sm font-medium">
|
|
{upload?.uploaded_by.name || 'Unknown uploader'}
|
|
</p>
|
|
<p className="truncate text-xs text-muted-foreground">
|
|
{upload?.uploaded_by.email || 'No email available'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</DialogBody>
|
|
|
|
<DialogFooter showCloseButton>
|
|
{upload?.ticket_id && canOpenTicket ? (
|
|
<Button asChild>
|
|
<Link href={ROUTES.TICKET_DETAIL(upload.ticket_id)}>
|
|
<Ticket />
|
|
Open Ticket
|
|
<ExternalLink />
|
|
</Link>
|
|
</Button>
|
|
) : null}
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|