feat(tickets): add repair proof status filter for class issues

This commit is contained in:
2026-07-21 19:24:40 +05:30
parent 830883c6b7
commit 76c87811d8
8 changed files with 172 additions and 146 deletions

View File

@@ -1,8 +1,10 @@
'use client';
import { useState } from 'react';
import { AlertTriangle, ImageIcon, Loader2 } from 'lucide-react';
import Image from 'next/image';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { useProtectedMediaObjectUrl } from '@/hooks/useProtectedMedia';
import type { BoundingBoxOverlayItem, DetectionResultItem } from '@/types';
@@ -12,13 +14,18 @@ interface AnnotatedDetectionImageProps {
detection?: DetectionResultItem;
videoWidth: number;
videoHeight: number;
aspectRatio?: string;
enableLightbox?: boolean;
}
export default function AnnotatedDetectionImage({
detection,
videoWidth,
videoHeight,
aspectRatio: aspectRatioOverride,
enableLightbox = false,
}: AnnotatedDetectionImageProps) {
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const { objectUrl, isLoading, isError } = useProtectedMediaObjectUrl(
detection?.detection.context_image_url,
);
@@ -35,9 +42,65 @@ export default function AnnotatedDetectionImage({
: [];
const aspectRatio =
videoWidth > 0 && videoHeight > 0
aspectRatioOverride ??
(videoWidth > 0 && videoHeight > 0
? `${videoWidth} / ${videoHeight}`
: '16 / 9';
: '16 / 9');
const annotatedMedia = objectUrl ? (
<>
<Image
src={objectUrl}
alt={`${detection?.detection.display_name ?? 'Road defect'} detection`}
fill
unoptimized
sizes="(min-width: 1280px) 18rem, (min-width: 640px) 50vw, 100vw"
className="object-contain"
/>
<BoundingBoxOverlay
detections={overlayDetections}
videoWidth={videoWidth}
videoHeight={videoHeight}
/>
</>
) : null;
if (detection && objectUrl && enableLightbox) {
return (
<>
<button
type="button"
onClick={() => setIsPreviewOpen(true)}
aria-label={`Open ${detection.detection.display_name} detection image`}
className="group relative flex w-full cursor-zoom-in items-center justify-center overflow-hidden rounded-lg border bg-black focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
style={{ aspectRatio }}
>
<Image
src={objectUrl}
alt={`${detection.detection.display_name} detection`}
fill
unoptimized
sizes="(min-width: 1280px) 18rem, (min-width: 640px) 50vw, 100vw"
className="object-contain"
/>
<span className="pointer-events-none absolute inset-x-0 bottom-0 z-[3] bg-gradient-to-t from-black/70 to-transparent px-3 pb-2 pt-8 text-center text-xs font-medium text-white opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100">
Click to view annotated image
</span>
</button>
<Dialog open={isPreviewOpen} onOpenChange={setIsPreviewOpen}>
<DialogContent className="h-[calc(100vh-2rem)] bg-black/95 p-4 text-white sm:max-w-[calc(100vw-2rem)]">
<DialogTitle className="sr-only">
{detection.detection.display_name} annotated detection image
</DialogTitle>
<div className="relative min-h-0 w-full flex-1 overflow-hidden rounded-lg bg-black">
{annotatedMedia}
</div>
</DialogContent>
</Dialog>
</>
);
}
return (
<div
@@ -60,20 +123,7 @@ export default function AnnotatedDetectionImage({
<p>Failed to load the detection image.</p>
</div>
) : (
<>
<Image
src={objectUrl}
alt={`${detection.detection.display_name} detection`}
fill
unoptimized
className="object-contain"
/>
<BoundingBoxOverlay
detections={overlayDetections}
videoWidth={videoWidth}
videoHeight={videoHeight}
/>
</>
annotatedMedia
)}
</div>
);