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:
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user