diff --git a/frontend/src/components/ScanProgress.tsx b/frontend/src/components/ScanProgress.tsx index b3f7991..3aa8434 100644 --- a/frontend/src/components/ScanProgress.tsx +++ b/frontend/src/components/ScanProgress.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { FolderOpen, Loader2, Check, AlertCircle, X } from 'lucide-react' -import { useQuery } from '@tanstack/react-query' +import { useQuery, useQueryClient } from '@tanstack/react-query' import { library } from '../services/api' import clsx from 'clsx' @@ -15,6 +15,8 @@ interface ScanStatus { export function ScanProgress() { const [isVisible, setIsVisible] = useState(false) const [isMinimized, setIsMinimized] = useState(false) + const queryClient = useQueryClient() + const wasScanningRef = useRef(false) // Poll scan status every 2 seconds when scanning const { data: scanStatus } = useQuery({ @@ -31,18 +33,34 @@ export function ScanProgress() { }) useEffect(() => { - if (scanStatus?.is_scanning) { + const isScanning = scanStatus?.is_scanning ?? false + + if (isScanning) { setIsVisible(true) setIsMinimized(false) - } else if (isVisible && !scanStatus?.is_scanning && (scanStatus?.processed_files ?? 0) > 0) { - // Keep showing for 3 seconds after scan completes - setTimeout(() => { - if (!scanStatus?.is_scanning) { - setIsVisible(false) - } - }, 3000) + wasScanningRef.current = true + } else if (wasScanningRef.current) { + // Just transitioned from scanning → done. THIS is the right moment + // to invalidate caches that might have new data: the photos query + // (new files indexed), the folder tree (new folders walked), the + // heap counts (in case a heap photo got reattached). + wasScanningRef.current = false + queryClient.invalidateQueries({ queryKey: ['photos'] }) + queryClient.invalidateQueries({ queryKey: ['folders'] }) + queryClient.invalidateQueries({ queryKey: ['folders', 'tree'] }) + queryClient.invalidateQueries({ queryKey: ['heaps'] }) + queryClient.invalidateQueries({ queryKey: ['tags'] }) + + if (isVisible && (scanStatus?.processed_files ?? 0) > 0) { + // Keep showing for 3 seconds after scan completes + setTimeout(() => { + if (!scanStatus?.is_scanning) { + setIsVisible(false) + } + }, 3000) + } } - }, [scanStatus?.is_scanning, scanStatus?.processed_files, isVisible]) + }, [scanStatus?.is_scanning, scanStatus?.processed_files, isVisible, queryClient]) if (!isVisible || !scanStatus) return null diff --git a/frontend/src/components/layout/LeftSidebar.tsx b/frontend/src/components/layout/LeftSidebar.tsx index 893c37c..ab24f15 100644 --- a/frontend/src/components/layout/LeftSidebar.tsx +++ b/frontend/src/components/layout/LeftSidebar.tsx @@ -305,7 +305,14 @@ export function LeftSidebar() { onClick={() => { if (renamingId === item.id) return setSelectedItem(item.id) - if (hasChildren) { + // Folder rows are always filterable, parent or leaf — clicking + // anywhere on the row applies the filter and the chevron + // (separate button below) handles expansion. Other group + // headers (Library, Folders) just toggle expansion since + // they have no associated filter. + if (item.id.startsWith('folder-')) { + applyLibraryNode(item.id) + } else if (hasChildren) { toggleExpanded(item.id) } else { applyLibraryNode(item.id)