perf(ticket): paginate and cache detection previews

This commit is contained in:
2026-08-07 16:38:38 +05:30
parent d3069e1f25
commit 3d04995f97
5 changed files with 32 additions and 11 deletions

View File

@@ -262,6 +262,7 @@ export function TicketAssignmentsCard({
{videoId ? ( {videoId ? (
<div className={'mt-4 border-t pt-4'}> <div className={'mt-4 border-t pt-4'}>
<TicketDetectionPreview <TicketDetectionPreview
key={`${videoId}:${selectedAssignment.id}`}
ticketId={ticketId} ticketId={ticketId}
videoId={videoId} videoId={videoId}
assignmentId={selectedAssignment.id} assignmentId={selectedAssignment.id}

View File

@@ -24,6 +24,8 @@ interface TicketDetectionPreviewProps {
onDetectionCountChange?: (count: number | undefined) => void; onDetectionCountChange?: (count: number | undefined) => void;
} }
const DETECTION_PAGE_SIZE = 10;
function DetectionMetadataBar({ function DetectionMetadataBar({
items, items,
}: { }: {
@@ -102,6 +104,8 @@ export function TicketDetectionPreview({
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [isDiscardDialogOpen, setIsDiscardDialogOpen] = useState(false); const [isDiscardDialogOpen, setIsDiscardDialogOpen] = useState(false);
const isUnassignedPreview = assignmentStatus === 'unassigned'; const isUnassignedPreview = assignmentStatus === 'unassigned';
const pageStart =
Math.floor(currentIndex / DETECTION_PAGE_SIZE) * DETECTION_PAGE_SIZE;
useEffect(() => { useEffect(() => {
setCurrentIndex(0); setCurrentIndex(0);
@@ -109,23 +113,26 @@ export function TicketDetectionPreview({
const queryParams = useMemo( const queryParams = useMemo(
() => ({ () => ({
skip: currentIndex, skip: pageStart,
limit: 1, limit: DETECTION_PAGE_SIZE,
assignment_id: isUnassignedPreview ? undefined : assignmentId, assignment_id: isUnassignedPreview ? undefined : assignmentId,
assignment_status: isUnassignedPreview assignment_status: isUnassignedPreview
? ('unassigned' as const) ? ('unassigned' as const)
: undefined, : undefined,
sort: 'timestamp_asc', sort: 'timestamp_asc',
}), }),
[assignmentId, currentIndex, isUnassignedPreview], [assignmentId, isUnassignedPreview, pageStart],
); );
const detectionsQuery = useTicketDetectionsQuery( const detectionsQuery = useTicketDetectionsQuery(
videoId ?? undefined, videoId ?? undefined,
queryParams, queryParams,
{ keepPreviousPage: true },
); );
const detectionResult = detectionsQuery.data; const detectionResult = detectionsQuery.data;
const activeDetection = detectionResult?.items[0]; const activeDetection = detectionsQuery.isPlaceholderData
? undefined
: detectionResult?.items[currentIndex - pageStart];
const confirmedDetectionCount = detectionResult?.total; const confirmedDetectionCount = detectionResult?.total;
const detectionCount = confirmedDetectionCount ?? 0; const detectionCount = confirmedDetectionCount ?? 0;
const activeVisual = getDefectVisual( const activeVisual = getDefectVisual(
@@ -156,12 +163,15 @@ export function TicketDetectionPreview({
}, [confirmedDetectionCount, currentIndex]); }, [confirmedDetectionCount, currentIndex]);
useEffect(() => { useEffect(() => {
onDetectionCountChange?.(confirmedDetectionCount); onDetectionCountChange?.(detectionCount);
}, [confirmedDetectionCount, onDetectionCountChange]); }, [detectionCount, onDetectionCountChange]);
const isNavigationDisabled = const isNavigationDisabled =
detectionCount === 0 || detectionsQuery.isLoading; detectionCount === 0 ||
const isInitialLoading = detectionsQuery.isLoading; detectionsQuery.isLoading ||
detectionsQuery.isPlaceholderData;
const isInitialLoading =
detectionsQuery.isLoading || detectionsQuery.isPlaceholderData;
const isError = detectionsQuery.isError; const isError = detectionsQuery.isError;
const retry = () => void detectionsQuery.refetch(); const retry = () => void detectionsQuery.refetch();
@@ -221,7 +231,7 @@ export function TicketDetectionPreview({
</div> </div>
</div> </div>
{isInitialLoading && !activeDetection ? ( {isInitialLoading ? (
<TicketDetectionPreviewSkeleton /> <TicketDetectionPreviewSkeleton />
) : isError ? ( ) : isError ? (
<div className="rounded-lg border border-dashed border-destructive/40 bg-destructive/5 px-4 py-8 text-center"> <div className="rounded-lg border border-dashed border-destructive/40 bg-destructive/5 px-4 py-8 text-center">

View File

@@ -22,6 +22,7 @@ export function TicketDetectionPreviewCard({
<CardContent className="p-5"> <CardContent className="p-5">
<div className="min-w-0"> <div className="min-w-0">
<TicketDetectionPreview <TicketDetectionPreview
key={`${videoId}:${assignmentId}`}
ticketId={ticketId} ticketId={ticketId}
videoId={videoId} videoId={videoId}
assignmentId={assignmentId} assignmentId={assignmentId}

View File

@@ -47,6 +47,7 @@ export function TicketUnassignedIssuesCard({
<CardContent className={'p-5'}> <CardContent className={'p-5'}>
<div className={'min-w-0'}> <div className={'min-w-0'}>
<TicketDetectionPreview <TicketDetectionPreview
key={`${videoId}:unassigned`}
ticketId={ticketId} ticketId={ticketId}
videoId={videoId} videoId={videoId}
assignmentStatus={'unassigned'} assignmentStatus={'unassigned'}

View File

@@ -2,6 +2,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { import {
keepPreviousData,
useInfiniteQuery, useInfiniteQuery,
useMutation, useMutation,
useQuery, useQuery,
@@ -28,6 +29,11 @@ import { extensionRequestKeys } from '../../extension-requests/queries/extension
const TICKET_TABLE_REFRESH_MS = 5 * 60 * 1000; const TICKET_TABLE_REFRESH_MS = 5 * 60 * 1000;
interface TicketDetectionsQueryOptions {
enabled?: boolean;
keepPreviousPage?: boolean;
}
interface UseTicketsQueryParams { interface UseTicketsQueryParams {
skip: number; skip: number;
limit: number; limit: number;
@@ -113,8 +119,9 @@ export function useTicketAssignmentsQuery(
export function useTicketDetectionsQuery( export function useTicketDetectionsQuery(
videoId: string | undefined, videoId: string | undefined,
params: VideoDetectionsParams, params: VideoDetectionsParams,
enabled = true, options: TicketDetectionsQueryOptions = {},
) { ) {
const { enabled = true, keepPreviousPage = false } = options;
const queryParams = useMemo( const queryParams = useMemo(
() => ({ () => ({
skip: params.skip ?? 0, skip: params.skip ?? 0,
@@ -143,6 +150,7 @@ export function useTicketDetectionsQuery(
queryFn: () => queryFn: () =>
videoService.getVideoDetections(videoId as string, queryParams), videoService.getVideoDetections(videoId as string, queryParams),
enabled: Boolean(enabled && videoId), enabled: Boolean(enabled && videoId),
placeholderData: keepPreviousPage ? keepPreviousData : undefined,
staleTime: Infinity, staleTime: Infinity,
}); });
} }