feat(ticket): add paginated class detection preview with image and map
This commit is contained in:
@@ -2,18 +2,17 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { CalendarCheck2, Component, FileVideo, ListChecks } from 'lucide-react';
|
||||
import { Component, ListChecks } from 'lucide-react';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { DEFECT_CLASS_STATUS_CONFIG } from '@/constants/defectClassStatus';
|
||||
import { getDefectVisual } from '@/constants/defectVisualConfig';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { TicketOverviewDetail } from '@/types';
|
||||
import { formatDate } from '@/utils/date';
|
||||
|
||||
import { useTicketDefectClassesQuery } from '../../hooks/useTicketQueries';
|
||||
import { TicketClassDetectionPreview } from './TicketClassDetectionPreview';
|
||||
|
||||
interface TicketDefectClassTabsProps {
|
||||
ticketId: string;
|
||||
@@ -64,10 +63,15 @@ export function TicketDefectClassTabs({
|
||||
const selectedIssue = ticket?.ai_result.detections_by_class.find(
|
||||
(item) => item.class_name === selectedClass,
|
||||
);
|
||||
const selectedIssueVisual = getDefectVisual(selectedIssue?.class_name ?? '');
|
||||
const SelectedIssueIcon = selectedIssueVisual.icon;
|
||||
const completedAt = ticket?.ai_result.completed_at;
|
||||
const videoName = ticket?.video?.name;
|
||||
const selectedClassItem = defectClasses.find(
|
||||
(item) => item.class_name === selectedClass,
|
||||
);
|
||||
const previewDisplayName =
|
||||
selectedIssue?.display_name ??
|
||||
selectedClassItem?.display_name ??
|
||||
selectedClass ??
|
||||
'Detection';
|
||||
const previewVideoId = ticket?.video_id ?? defectClassesQuery.data?.video_id;
|
||||
|
||||
if (defectClassesQuery.isLoading) {
|
||||
return (
|
||||
@@ -75,12 +79,12 @@ export function TicketDefectClassTabs({
|
||||
<CardContent className="space-y-4 p-5">
|
||||
<div className="h-5 w-24 animate-pulse rounded bg-muted" />
|
||||
<div className="flex flex-col gap-2">
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="h-11 w-full animate-pulse rounded-lg bg-muted"
|
||||
/>
|
||||
))}
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="h-11 w-full animate-pulse rounded-lg bg-muted"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -145,89 +149,12 @@ export function TicketDefectClassTabs({
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 p-5">
|
||||
{selectedIssue ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span
|
||||
className={cn(
|
||||
'flex size-10 shrink-0 items-center justify-center rounded-full',
|
||||
selectedIssueVisual.cardClassName,
|
||||
)}
|
||||
>
|
||||
<SelectedIssueIcon
|
||||
className={cn(
|
||||
'size-5',
|
||||
selectedIssueVisual.colorClassName,
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
|
||||
<h3 className="truncate text-lg font-semibold text-foreground">
|
||||
{selectedIssue.display_name}
|
||||
</h3>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
'shrink-0 rounded-full border-0 px-2.5 py-1 text-xs font-semibold',
|
||||
selectedIssueVisual.cardClassName,
|
||||
selectedIssueVisual.colorClassName,
|
||||
)}
|
||||
>
|
||||
{selectedIssue.count} Detections
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">
|
||||
Current Issue
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-foreground">
|
||||
{selectedIssue.display_name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">
|
||||
Current Issue Count
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-violet-600">
|
||||
{selectedIssue.count}
|
||||
</p>
|
||||
</div>
|
||||
{completedAt ? (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
|
||||
<CalendarCheck2 className="size-4" />
|
||||
<span>AI Completed At</span>
|
||||
</div>
|
||||
<p className="text-sm font-semibold text-foreground">
|
||||
{formatDate(completedAt)}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{videoName ? (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
|
||||
<FileVideo className="size-4" />
|
||||
<span>Video</span>
|
||||
</div>
|
||||
<p className="truncate text-sm font-semibold text-foreground">
|
||||
{videoName}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-48 items-center justify-center text-center">
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
Select an issue to view details.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<TicketClassDetectionPreview
|
||||
videoId={previewVideoId}
|
||||
defectClassName={selectedClass}
|
||||
displayName={previewDisplayName}
|
||||
totalCount={selectedIssue?.count}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user