import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Pencil, RotateCcw, Check, X, Download, Trash2 } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useBackupManager } from "@/hooks/useBackupManager"; import { extractErrorMessage } from "@/utils/errorUtils"; interface BackupListSectionProps { backupIntervalHours?: number; backupRetainCount?: number; onSettingsChange: (updates: { backupIntervalHours?: number; backupRetainCount?: number; }) => void; } function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } function formatBackupDate(isoString: string): string { try { const date = new Date(isoString); return date.toLocaleString(); } catch { return isoString; } } /** Parse display name from backup filename */ function getDisplayName(filename: string): string { // Try to parse db_backup_YYYYMMDD_HHMMSS format const match = filename.match( /^db_backup_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})(?:_\d+)?\.db$/, ); if (match) { const [, y, m, d, hh, mm, ss] = match; return `${y}-${m}-${d} ${hh}:${mm}:${ss}`; } // Otherwise show filename without .db suffix return filename.replace(/\.db$/, ""); } export function BackupListSection({ backupIntervalHours, backupRetainCount, onSettingsChange, }: BackupListSectionProps) { const { t } = useTranslation(); const { backups, isLoading, create, isCreating, restore, isRestoring, rename, isRenaming, remove, isDeleting, } = useBackupManager(); const [confirmFilename, setConfirmFilename] = useState(null); const [deleteFilename, setDeleteFilename] = useState(null); const [editingFilename, setEditingFilename] = useState(null); const [editValue, setEditValue] = useState(""); const handleRestore = async () => { if (!confirmFilename) return; try { const safetyId = await restore(confirmFilename); setConfirmFilename(null); toast.success( t("settings.backupManager.restoreSuccess", { defaultValue: "Restore successful! Safety backup created", }), { description: safetyId ? `${t("settings.backupManager.safetyBackupId", { defaultValue: "Safety Backup ID" })}: ${safetyId}` : undefined, duration: 6000, closeButton: true, }, ); } catch (error) { const detail = extractErrorMessage(error) || t("settings.backupManager.restoreFailed", { defaultValue: "Restore failed", }); toast.error(detail); } }; const handleStartRename = (filename: string) => { setEditingFilename(filename); setEditValue(getDisplayName(filename)); }; const handleCancelRename = () => { setEditingFilename(null); setEditValue(""); }; const handleDelete = async () => { if (!deleteFilename) return; try { await remove(deleteFilename); setDeleteFilename(null); toast.success( t("settings.backupManager.deleteSuccess", { defaultValue: "Backup deleted", }), ); } catch (error) { const detail = extractErrorMessage(error) || t("settings.backupManager.deleteFailed", { defaultValue: "Delete failed", }); toast.error(detail); } }; const handleConfirmRename = async () => { if (!editingFilename || !editValue.trim()) return; try { await rename({ oldFilename: editingFilename, newName: editValue.trim() }); setEditingFilename(null); setEditValue(""); toast.success( t("settings.backupManager.renameSuccess", { defaultValue: "Backup renamed", }), ); } catch (error) { const detail = extractErrorMessage(error) || t("settings.backupManager.renameFailed", { defaultValue: "Rename failed", }); toast.error(detail); } }; const intervalValue = String(backupIntervalHours ?? 24); const retainValue = String(backupRetainCount ?? 10); return (
{/* Backup policy settings */}
{/* Backup list */}

{t("settings.backupManager.title", { defaultValue: "Database Backups", })}

{isLoading ? (
Loading...
) : backups.length === 0 ? (
{t("settings.backupManager.empty", { defaultValue: "No backups yet", })}
) : (
{backups.map((backup) => (
{editingFilename === backup.filename ? (
setEditValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleConfirmRename(); if (e.key === "Escape") handleCancelRename(); }} className="h-7 text-xs" placeholder={t( "settings.backupManager.namePlaceholder", { defaultValue: "Enter new name" }, )} autoFocus disabled={isRenaming} />
) : ( <>
{getDisplayName(backup.filename)}
{formatBackupDate(backup.createdAt)} ·{" "} {formatBytes(backup.sizeBytes)}
)}
{editingFilename !== backup.filename && (
)}
))}
)}
{/* Restore Confirmation Dialog */} !open && setConfirmFilename(null)} > {t("settings.backupManager.confirmTitle", { defaultValue: "Confirm Restore", })} {t("settings.backupManager.confirmMessage", { defaultValue: "Restoring this backup will overwrite the current database. A safety backup will be created first.", })} {/* Delete Confirmation Dialog */} !open && setDeleteFilename(null)} > {t("settings.backupManager.deleteConfirmTitle", { defaultValue: "Confirm Delete", })} {t("settings.backupManager.deleteConfirmMessage", { defaultValue: "This backup will be permanently deleted. This action cannot be undone.", })}
); }