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({

Sequences

Manage and annotate wildfire detection sequences

+ {stageSelector} {/* Filters */} @@ -243,16 +255,15 @@ export default function SequencesPage({

No matching sequences found

-

- {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({

Sequences

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 ; + const isReview = defaultProcessingStage === 'annotated'; + + const [stage, setStage] = usePersistedTabState( + 'sequences-review-stage', + 'seq_annotation_done' + ); + + if (!isReview) { + return ; + } + + return ( + + + + + } + /> + ); }