'use client';
import { useEffect, useMemo, useState } from 'react';
import { AlertTriangle, ChevronLeft, ChevronRight, Trash2 } from 'lucide-react';
import DetectionLocationMap from '@/components/map/detectionLocationMap';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import AnnotatedDetectionImage from '@/components/video/annotatedDetectionImage';
import { getDefectVisual } from '@/constants/defectVisualConfig';
import { cn } from '@/lib/utils';
import {
useDiscardDetectionMutation,
useTicketDetectionsQuery,
} from '../../hooks/useTicketQueries';
import { DetectionDiscardDialog } from './DetectionDiscardDialog';
interface TicketDetectionPreviewProps {
ticketId: string;
videoId?: string | null;
assignmentId?: number;
assignmentStatus?: 'unassigned';
onDetectionCountChange?: (count: number | undefined) => void;
}
function DetectionMetadataBar({
items,
}: {
items: { label: string; value: string | number }[];
}) {
return (
{items.map((item, index) => (
{item.label}
{item.value}
))}
);
}
function TicketDetectionPreviewSkeleton() {
return (
<>
{Array.from({ length: 3 }).map((_, index) => (
))}
>
);
}
export function TicketDetectionPreview({
ticketId,
videoId,
assignmentId,
assignmentStatus,
onDetectionCountChange,
}: TicketDetectionPreviewProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [isDiscardDialogOpen, setIsDiscardDialogOpen] = useState(false);
const isUnassignedPreview = assignmentStatus === 'unassigned';
useEffect(() => {
setCurrentIndex(0);
}, [assignmentId, assignmentStatus, videoId]);
const queryParams = useMemo(
() => ({
skip: currentIndex,
limit: 1,
assignment_id: isUnassignedPreview ? undefined : assignmentId,
assignment_status: isUnassignedPreview
? ('unassigned' as const)
: undefined,
sort: 'timestamp_asc',
}),
[assignmentId, currentIndex, isUnassignedPreview],
);
const detectionsQuery = useTicketDetectionsQuery(
videoId ?? undefined,
queryParams,
);
const detectionResult = detectionsQuery.data;
const activeDetection = detectionResult?.items[0];
const confirmedDetectionCount = detectionResult?.total;
const detectionCount = confirmedDetectionCount ?? 0;
const activeVisual = getDefectVisual(
activeDetection?.detection.class_name ?? '',
);
const ActiveIssueIcon = activeVisual.icon;
const activeDisplayName =
activeDetection?.detection.display_name ?? 'Detection';
const discardMutation = useDiscardDetectionMutation(
ticketId,
videoId ?? undefined,
);
useEffect(() => {
if (confirmedDetectionCount === undefined) return;
if (confirmedDetectionCount === 0 && currentIndex !== 0) {
setCurrentIndex(0);
return;
}
if (
confirmedDetectionCount > 0 &&
currentIndex > confirmedDetectionCount - 1
) {
setCurrentIndex(confirmedDetectionCount - 1);
}
}, [confirmedDetectionCount, currentIndex]);
useEffect(() => {
onDetectionCountChange?.(confirmedDetectionCount);
}, [confirmedDetectionCount, onDetectionCountChange]);
const isNavigationDisabled =
detectionCount === 0 || detectionsQuery.isLoading;
const isInitialLoading = detectionsQuery.isLoading;
const isError = detectionsQuery.isError;
const retry = () => void detectionsQuery.refetch();
return (
setCurrentIndex((index) =>
detectionCount === 0
? 0
: (index - 1 + detectionCount) % detectionCount,
)
}
disabled={isNavigationDisabled}
aria-label="Previous detection"
>
{detectionCount === 0
? '0 of 0'
: `${currentIndex + 1} of ${detectionCount}`}
setCurrentIndex((index) =>
detectionCount === 0 ? 0 : (index + 1) % detectionCount,
)
}
disabled={isNavigationDisabled}
aria-label="Next detection"
>
{isInitialLoading && !activeDetection ? (
) : isError ? (
Failed to load detection preview.
Retry
) : !activeDetection || !detectionResult ? (
{detectionCount > 0
? 'The selected detection details are unavailable. Try refreshing the preview.'
: isUnassignedPreview
? 'No unassigned issues remain on this ticket.'
: 'No detections are available for this issue.'}
) : (
<>
{isUnassignedPreview ? (
<>
setIsDiscardDialogOpen(true)}
>
Discard detection
{
await discardMutation.mutateAsync({
detectionId: activeDetection.detection.id,
payload,
});
setIsDiscardDialogOpen(false);
}}
/>
>
) : null}
>
)}
);
}