import { useEffect, useRef, useState } from 'react' import { FolderOpen, Loader2, Check, AlertCircle, X } from 'lucide-react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { library } from '../services/api' import clsx from 'clsx' interface ScanStatus { is_scanning: boolean current_folder?: string processed_files: number total_files: number errors: string[] } 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({ queryKey: ['scan-status'], queryFn: async () => { const response = await library.scanStatus() return response }, refetchInterval: (query) => { // Poll every 2 seconds if scanning, otherwise every 10 seconds return query.state.data?.is_scanning ? 2000 : 10000 }, enabled: true, }) useEffect(() => { const isScanning = scanStatus?.is_scanning ?? false if (isScanning) { setIsVisible(true) setIsMinimized(false) 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, queryClient]) if (!isVisible || !scanStatus) return null const progress = scanStatus.total_files > 0 ? (scanStatus.processed_files / scanStatus.total_files) * 100 : 0 const isComplete = !scanStatus.is_scanning && scanStatus.processed_files > 0 const hasErrors = scanStatus.errors && scanStatus.errors.length > 0 return (
{/* Header */}
setIsMinimized(!isMinimized)} >
{scanStatus.is_scanning ? ( ) : isComplete && !hasErrors ? ( ) : hasErrors ? ( ) : ( )} {!isMinimized && ( {scanStatus.is_scanning ? 'Scanning Folders' : isComplete ? 'Scan Complete' : 'Scan Status'} )}
{!isMinimized && ( )}
{/* Content */} {!isMinimized && (
{/* Current folder */} {scanStatus.current_folder && (
{scanStatus.current_folder}
)} {/* Progress bar */}
{/* Stats */}
{scanStatus.processed_files} / {scanStatus.total_files || '?'} files {scanStatus.is_scanning ? `${Math.round(progress)}%` : isComplete ? 'Done' : 'Idle'}
{/* Errors */} {hasErrors && (
{scanStatus.errors.slice(0, 3).map((error, i) => (
• {error}
))} {scanStatus.errors.length > 3 && (
+{scanStatus.errors.length - 3} more errors
)}
)}
)}
) }