import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { AlertTriangle, Loader2, Trash2, X } from 'lucide-react'; import { api } from '../api/client'; import { Button } from './Button'; import { useToast } from '../contexts/ToastContext'; import { formatFileSize } from '../utils/file'; interface PurgeOldFilesModalProps { onClose: () => void; } const DEFAULT_DAYS = 90; export function PurgeOldFilesModal({ onClose }: PurgeOldFilesModalProps) { const { t } = useTranslation(); const queryClient = useQueryClient(); const { showToast } = useToast(); const [days, setDays] = useState(DEFAULT_DAYS); const [includeNeverPrinted, setIncludeNeverPrinted] = useState(true); // Debounce the preview query so dragging a slider isn't a DoS. const [debouncedDays, setDebouncedDays] = useState(days); useEffect(() => { const handle = window.setTimeout(() => setDebouncedDays(days), 300); return () => window.clearTimeout(handle); }, [days]); const previewQuery = useQuery({ queryKey: ['library-purge-preview', debouncedDays, includeNeverPrinted], queryFn: () => api.previewLibraryPurge(debouncedDays, includeNeverPrinted), enabled: debouncedDays >= 1, }); const purgeMutation = useMutation({ mutationFn: () => api.executeLibraryPurge(days, includeNeverPrinted), onSuccess: (res) => { showToast(t('libraryPurge.toast.success', { count: res.moved_to_trash }), 'success'); queryClient.invalidateQueries({ queryKey: ['library-files'] }); queryClient.invalidateQueries({ queryKey: ['library-folders'] }); queryClient.invalidateQueries({ queryKey: ['library-trash'] }); queryClient.invalidateQueries({ queryKey: ['library-trash-count'] }); onClose(); }, onError: (e: Error) => showToast(e.message || t('libraryPurge.toast.failed'), 'error'), }); useEffect(() => { const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !purgeMutation.isPending) onClose(); }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [onClose, purgeMutation.isPending]); const preview = previewQuery.data; const count = preview?.count ?? 0; const totalBytes = preview?.total_bytes ?? 0; const canConfirm = count > 0 && !purgeMutation.isPending; return (

{t('libraryPurge.title')}

{t('libraryPurge.description')}

setDays(Math.max(1, Math.min(3650, parseInt(e.target.value || '0', 10) || 0)))} className="w-24 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm px-2 py-1 text-gray-900 dark:text-gray-100" /> {t('libraryPurge.days')}
{t('libraryPurge.effectsTitle')}
  • {t('libraryPurge.effect1')}
  • {t('libraryPurge.effect2')}
  • {t('libraryPurge.effect3')}
  • {t('libraryPurge.effect4')}
{previewQuery.isLoading || previewQuery.isFetching ? (
{t('libraryPurge.previewLoading')}
) : previewQuery.isError ? (
{(previewQuery.error as Error | null)?.message ?? t('libraryPurge.previewFailed')}
) : (
{t('libraryPurge.previewSummary', { count, size: formatFileSize(totalBytes) })}
{preview?.sample_filenames && preview.sample_filenames.length > 0 && (
    {preview.sample_filenames.map((name) => (
  • {name}
  • ))} {count > preview.sample_filenames.length && (
  • {t('libraryPurge.andMore', { count: count - preview.sample_filenames.length })}
  • )}
)}
)}
{t('libraryPurge.warning')}
); }