diff --git a/frontend/src/pages/SequencesPage.tsx b/frontend/src/pages/SequencesPage.tsx index 0e37df5..feac060 100644 --- a/frontend/src/pages/SequencesPage.tsx +++ b/frontend/src/pages/SequencesPage.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { ReactNode, useEffect, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { apiClient } from '@/services/api'; @@ -9,6 +9,7 @@ import { } from '@/types/api'; import { QUERY_KEYS } from '@/utils/constants'; import { analyzeSequenceAccuracy } from '@/utils/modelAccuracy'; +import { getProcessingStageLabel } from '@/utils/processingStage'; import TabbedFilters from '@/components/filters/TabbedFilters'; import { SequencesTableHeader, @@ -26,19 +27,20 @@ import { hasActiveUserFilters } from '@/utils/filterHelpers'; interface SequencesPageProps { defaultProcessingStage?: ProcessingStageStatus; + isReviewPage?: boolean; + stageSelector?: ReactNode; } export default function SequencesPage({ defaultProcessingStage = 'ready_to_annotate', + isReviewPage = false, + stageSelector, }: SequencesPageProps = {}) { const navigate = useNavigate(); const { startAnnotationWorkflow } = useSequenceStore(); - // Determine storage key based on processing stage to separate annotate vs review filters - const storageKey = - defaultProcessingStage === 'annotated' - ? 'filters-sequences-review' - : 'filters-sequences-annotate'; + // Storage key separates review vs annotate filters; review filters are shared across stages. + const storageKey = isReviewPage ? 'filters-sequences-review' : 'filters-sequences-annotate'; // Use persisted filters hook const { @@ -59,6 +61,15 @@ export default function SequencesPage({ resetFilters, } = usePersistedFilters(storageKey, createDefaultFilterState(defaultProcessingStage)); + // Keep filters.processing_stage in sync with the parent-controlled stage prop + // (used by the review page stage selector). Reset to page 1 on stage change. + useEffect(() => { + if (filters.processing_stage !== defaultProcessingStage) { + setFilters({ ...filters, processing_stage: defaultProcessingStage, page: 1 }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultProcessingStage]); + // Fetch cameras, organizations, and source APIs for dropdown options const { data: cameras = [], isLoading: camerasLoading } = useCameras(); const { data: organizations = [], isLoading: organizationsLoading } = useOrganizations(); @@ -152,7 +163,7 @@ export default function SequencesPage({ } // Navigate to annotation interface with context about source page - const queryParam = defaultProcessingStage === 'annotated' ? '?from=review' : ''; + const queryParam = isReviewPage ? '?from=review' : ''; navigate(`/sequences/${clickedSequence.id}/annotate${queryParam}`); }; @@ -200,6 +211,7 @@ export default function SequencesPage({
Manage and annotate wildfire detection sequences
+ {stageSelector} {/* Filters */} @@ -243,16 +255,15 @@ export default function SequencesPage({- {defaultProcessingStage === 'annotated' - ? 'No completed sequences match your current filters.' - : 'No sequences match your current filters.'} -
+No sequences match your current filters.
Try adjusting your search criteria above.
> - ) : defaultProcessingStage === 'annotated' ? ( - // Review page - simple message without celebration -No completed sequences to review at the moment.
+ ) : isReviewPage ? ( + // Review page - simple message scoped to the selected stage ++ No sequences in "{getProcessingStageLabel(defaultProcessingStage)}" at the + moment. +
) : ( // Annotation page - celebratory message <> @@ -277,6 +288,7 @@ export default function SequencesPage({Manage and annotate wildfire detection sequences
+ {stageSelector} {/* Filters */} diff --git a/frontend/src/pages/SequencesPageWrapper.tsx b/frontend/src/pages/SequencesPageWrapper.tsx index c0b00e6..5b07af0 100644 --- a/frontend/src/pages/SequencesPageWrapper.tsx +++ b/frontend/src/pages/SequencesPageWrapper.tsx @@ -1,12 +1,56 @@ import SequencesPage from './SequencesPage'; -import { ProcessingStageStatus } from '@/types/api'; +import { ProcessingStage, ProcessingStageStatus } from '@/types/api'; +import { usePersistedTabState } from '@/hooks/usePersistedTabState'; +import { getProcessingStageLabel } from '@/utils/processingStage'; interface SequencesPageWrapperProps { defaultProcessingStage?: ProcessingStageStatus; } +const REVIEW_STAGES: ProcessingStage[] = [ + 'seq_annotation_done', + 'in_review', + 'annotated', + 'needs_manual', +]; + export default function SequencesPageWrapper({ defaultProcessingStage, }: SequencesPageWrapperProps) { - return