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 ? (
<div className={'mt-4 border-t pt-4'}>
<TicketDetectionPreview
key={`${videoId}:${selectedAssignment.id}`}
ticketId={ticketId}
videoId={videoId}
assignmentId={selectedAssignment.id}

View File

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

View File

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

View File

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