diff --git a/frontend/src/pages/Identify.tsx b/frontend/src/pages/Identify.tsx index d4615e5..6c8e478 100644 --- a/frontend/src/pages/Identify.tsx +++ b/frontend/src/pages/Identify.tsx @@ -20,6 +20,7 @@ export default function Identify() { const [searchParams, setSearchParams] = useSearchParams() const [faces, setFaces] = useState([]) const [, setTotal] = useState(0) + const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(50) const [minQuality, setMinQuality] = useState(0.0) const [sortBy, setSortBy] = useState('quality') @@ -63,6 +64,7 @@ export default function Identify() { const [busy, setBusy] = useState(false) const [imageLoading, setImageLoading] = useState(false) const [filtersCollapsed, setFiltersCollapsed] = useState(true) + const [setupAreaCollapsed, setSetupAreaCollapsed] = useState(false) const [loadingFaces, setLoadingFaces] = useState(false) const [availableTags, setAvailableTags] = useState([]) const [selectedTags, setSelectedTags] = useState([]) @@ -134,8 +136,13 @@ export default function Identify() { return Boolean(firstName.trim() && lastName.trim()) }, [personId, firstName, lastName, currentFace]) - const loadFaces = async (clearState: boolean = false, ignorePhotoIds: boolean = false) => { + const loadFaces = async ( + clearState: boolean = false, + ignorePhotoIds: boolean = false, + targetPage?: number + ) => { setLoadingFaces(true) + const pageToLoad = targetPage ?? page try { // Clear saved state if explicitly requested (Refresh button) @@ -144,7 +151,7 @@ export default function Identify() { } const res = await facesApi.getUnidentified({ - page: 1, + page: pageToLoad, page_size: pageSize, min_quality: minQuality, date_taken_from: dateFrom || undefined, @@ -167,6 +174,7 @@ export default function Identify() { setFaces(filtered) setTotal(filtered.length) + setPage(pageToLoad) setCurrentIdx(0) // Clear form data when refreshing if (clearState) { @@ -408,6 +416,9 @@ export default function Identify() { if (state.selectedSimilar && typeof state.selectedSimilar === 'object') { setSelectedSimilar(state.selectedSimilar) } + if (state.page !== undefined) { + setPage(state.page) + } // Mark that we restored state, so we don't reload initialLoadRef.current = true // Mark restoration as complete after state is restored @@ -436,12 +447,13 @@ export default function Identify() { similar, faceFormData, selectedSimilar, + page, } sessionStorage.setItem(STATE_KEY, JSON.stringify(state)) } catch (error) { console.error('Error saving state to sessionStorage:', error) } - }, [faces, currentIdx, similar, faceFormData, selectedSimilar, stateRestored]) + }, [faces, currentIdx, similar, faceFormData, selectedSimilar, page, stateRestored]) // Save state on unmount (when navigating away) - use refs to capture latest values const facesRef = useRef(faces) @@ -449,6 +461,7 @@ export default function Identify() { const similarRef = useRef(similar) const faceFormDataRef = useRef(faceFormData) const selectedSimilarRef = useRef(selectedSimilar) + const pageRef = useRef(page) // Update refs whenever state changes useEffect(() => { @@ -457,7 +470,8 @@ export default function Identify() { similarRef.current = similar faceFormDataRef.current = faceFormData selectedSimilarRef.current = selectedSimilar - }, [faces, currentIdx, similar, faceFormData, selectedSimilar]) + pageRef.current = page + }, [faces, currentIdx, similar, faceFormData, selectedSimilar, page]) // Save state on unmount (when navigating away) useEffect(() => { @@ -469,6 +483,7 @@ export default function Identify() { similar: similarRef.current, faceFormData: faceFormDataRef.current, selectedSimilar: selectedSimilarRef.current, + page: pageRef.current, } sessionStorage.setItem(STATE_KEY, JSON.stringify(state)) } catch (error) { @@ -503,7 +518,8 @@ export default function Identify() { // Reload faces when includeExcludedFaces changes useEffect(() => { if (settingsLoaded && !photoIds) { - loadFaces(false) + setPage(1) + loadFaces(false, false, 1) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [includeExcludedFaces]) @@ -558,7 +574,8 @@ export default function Identify() { // But only if restoration is complete (prevents reload during initial restoration) useEffect(() => { if (initialLoadRef.current && restorationCompleteRef.current) { - loadFaces() + setPage(1) + loadFaces(false, false, 1) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [uniqueFacesOnly]) @@ -567,7 +584,8 @@ export default function Identify() { // But only if restoration is complete (prevents reload during initial restoration) useEffect(() => { if (initialLoadRef.current && restorationCompleteRef.current) { - loadFaces() + setPage(1) + loadFaces(false, false, 1) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [pageSize]) @@ -733,6 +751,19 @@ export default function Identify() { return `Face ${currentIdx + 1} of ${faces.length}` }, [currentFace, currentIdx, faces.length]) + const handleNextPage = () => { + if (loadingFaces) return + const nextPage = page + 1 + setPage(nextPage) + loadFaces(false, false, nextPage) + } + const handlePrevPage = () => { + if (loadingFaces || page <= 1) return + const prevPage = Math.max(1, page - 1) + setPage(prevPage) + loadFaces(false, false, prevPage) + } + // Toggle excluded status for current face const toggleBlockFace = useCallback(async () => { if (!currentFace) return @@ -957,6 +988,7 @@ export default function Identify() { {/* Left: Controls and current face */}
{/* Unique Faces Checkbox and Batch Size - Outside Filters */} + {!setupAreaCollapsed && (
@@ -983,8 +1015,29 @@ export default function Identify() {
+
+
Page {page}
+
+ + +
+
+ )} + {!setupAreaCollapsed && (
setFiltersCollapsed(!filtersCollapsed)}>

Filters

@@ -1108,7 +1161,10 @@ export default function Identify() {
)} + {/* Collapse button at bottom of filters section */} +
+ +
+ )} + + {/* Show expand button when collapsed */} + {setupAreaCollapsed && ( +
+ +
+ )}