fix: refresh ticket detail data with the active defect class, select the emitted defect class after analysis completes, and animate the processing loader icon.

This commit is contained in:
2026-06-26 16:31:50 +05:30
parent 57461a05f2
commit e805cdee09
5 changed files with 42 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { AlertCircle, Loader2 } from 'lucide-react'; import { AlertCircle, Loader } from 'lucide-react';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import type { TicketProcessingProgress } from '../../../hooks/useTicketDetailEvents'; import type { TicketProcessingProgress } from '../../../hooks/useTicketDetailEvents';
@@ -28,7 +28,11 @@ export function ProcessingTicketAction({
const hasProgress = Boolean(progress); const hasProgress = Boolean(progress);
return ( return (
<TicketActionCard icon={Loader2} title="Analyzing Video"> <TicketActionCard
icon={Loader}
iconClassName="animate-spin"
title="Analyzing Video"
>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between gap-3 text-sm"> <div className="flex items-center justify-between gap-3 text-sm">

View File

@@ -3,15 +3,18 @@
import type { LucideIcon } from 'lucide-react'; import type { LucideIcon } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { cn } from '@/lib/utils';
interface TicketActionCardProps { interface TicketActionCardProps {
icon: LucideIcon; icon: LucideIcon;
iconClassName?: string;
title: string; title: string;
children: React.ReactNode; children: React.ReactNode;
} }
export function TicketActionCard({ export function TicketActionCard({
icon: Icon, icon: Icon,
iconClassName,
title, title,
children, children,
}: TicketActionCardProps) { }: TicketActionCardProps) {
@@ -19,7 +22,7 @@ export function TicketActionCard({
<Card> <Card>
<CardHeader className="pb-4"> <CardHeader className="pb-4">
<CardTitle className="flex items-center gap-2 text-base"> <CardTitle className="flex items-center gap-2 text-base">
<Icon className="size-4 text-primary" /> <Icon className={cn('size-4 text-primary', iconClassName)} />
{title} {title}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>

View File

@@ -26,6 +26,7 @@ export default function TicketDetailPage() {
const liveState = useTicketDetailEvents( const liveState = useTicketDetailEvents(
ticketId, ticketId,
defectClass,
Boolean(ticketId && ticket && ticket.status !== 'closed'), Boolean(ticketId && ticket && ticket.status !== 'closed'),
); );

View File

@@ -1,7 +1,8 @@
'use client'; 'use client';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { API_ROUTES } from '@/constants/apiRoutes'; import { API_ROUTES } from '@/constants/apiRoutes';
import { useSseWithToken } from '@/hooks/sse/useSseWithToken'; import { useSseWithToken } from '@/hooks/sse/useSseWithToken';
@@ -32,21 +33,45 @@ function isHeartbeatEvent(event: { type?: string } | null | undefined) {
export function useTicketDetailEvents( export function useTicketDetailEvents(
ticketId: string | undefined, ticketId: string | undefined,
defectClass: string | undefined,
enabled = true, enabled = true,
): TicketDetailLiveState { ): TicketDetailLiveState {
const router = useRouter();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const defectClassRef = useRef(defectClass);
const [progress, setProgress] = useState<TicketProcessingProgress | null>( const [progress, setProgress] = useState<TicketProcessingProgress | null>(
null, null,
); );
const [errorMessage, setErrorMessage] = useState<string | null>(null); const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
defectClassRef.current = defectClass;
}, [defectClass]);
const refetchTicket = useCallback(() => { const refetchTicket = useCallback(() => {
if (!ticketId) return; if (!ticketId) return;
queryClient.refetchQueries({ queryKey: ticketKeys.detail(ticketId) }); queryClient.refetchQueries({
queryKey: ticketKeys.detail(ticketId, defectClassRef.current),
});
queryClient.invalidateQueries({
queryKey: ticketKeys.defectClasses(ticketId),
});
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
}, [queryClient, ticketId]); }, [queryClient, ticketId]);
const selectDefectClass = useCallback(
(nextDefectClass: string | null | undefined) => {
if (!ticketId || !nextDefectClass || defectClassRef.current) return;
router.replace(
`/ticket/${ticketId}?defect_class=${encodeURIComponent(nextDefectClass)}`,
{ scroll: false },
);
},
[router, ticketId],
);
const getToken = useCallback( const getToken = useCallback(
() => ticketService.createTicketEventsToken(ticketId as string), () => ticketService.createTicketEventsToken(ticketId as string),
[ticketId], [ticketId],
@@ -77,6 +102,7 @@ export function useTicketDetailEvents(
progress: 100, progress: 100,
message: event.message, message: event.message,
}); });
selectDefectClass(event.defect_class);
refetchTicket(); refetchTicket();
}, },
error: (event: TicketStreamErrorEvent) => { error: (event: TicketStreamErrorEvent) => {
@@ -86,7 +112,7 @@ export function useTicketDetailEvents(
}, },
heartbeat: () => undefined, heartbeat: () => undefined,
}), }),
[refetchTicket], [refetchTicket, selectDefectClass],
); );
const shouldStop = useCallback( const shouldStop = useCallback(
@@ -103,7 +129,7 @@ export function useTicketDetailEvents(
useEffect(() => { useEffect(() => {
setProgress(null); setProgress(null);
setErrorMessage(null); setErrorMessage(null);
}, [ticketId]); }, [defectClass, ticketId]);
useSseWithToken({ useSseWithToken({
enabled: Boolean(enabled && ticketId), enabled: Boolean(enabled && ticketId),

View File

@@ -36,6 +36,7 @@ export interface TicketProgressEvent {
status: string; status: string;
progress: number; progress: number;
message: string; message: string;
defect_class?: string | null;
} }
export interface TicketStreamErrorEvent { export interface TicketStreamErrorEvent {