import React, { useState } from 'react'; import { Download, Eye, Trash2, Search, Filter, CheckCircle, XCircle, Clock, AlertCircle, FileArchive, Database, Image, HardDrive, ChevronDown, ChevronUp, Calendar, RefreshCw, Loader2 } from 'lucide-react'; import { format, formatDistanceToNow } from 'date-fns'; import { toast } from 'react-toastify'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button, Card, Input, Loading } from '../common'; import { api } from '../../config/api'; const statusIcons = { completed: { icon: CheckCircle, color: 'text-green-500' }, failed: { icon: XCircle, color: 'text-red-500' }, running: { icon: Loader2, color: 'text-blue-500 animate-spin' }, partial: { icon: AlertCircle, color: 'text-amber-500' } }; const formatBytes = (bytes) => { if (!bytes) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; }; export const BackupHistory = () => { const [expandedRows, setExpandedRows] = useState(new Set()); const [searchTerm, setSearchTerm] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); const [currentPage, setCurrentPage] = useState(1); const queryClient = useQueryClient(); // Fetch backup history const { data, isLoading, refetch } = useQuery({ queryKey: ['backup-history', currentPage, searchTerm, filterStatus], queryFn: async () => { const params = new URLSearchParams({ page: currentPage, limit: 20, ...(searchTerm && { search: searchTerm }), ...(filterStatus !== 'all' && { status: filterStatus }) }); const response = await api.get(`/admin/backup/status?${params}`); return response.data; } }); // Delete backup mutation const deleteMutation = useMutation({ mutationFn: async (backupId) => { const response = await api.delete(`/admin/backup/runs/${backupId}`); return response.data; }, onSuccess: () => { toast.success('Backup deleted successfully'); queryClient.invalidateQueries({ queryKey: ['backup-history'] }); }, onError: (error) => { toast.error(error.response?.data?.error || 'Failed to delete backup'); } }); const toggleRowExpansion = (id) => { const newExpanded = new Set(expandedRows); if (newExpanded.has(id)) { newExpanded.delete(id); } else { newExpanded.add(id); } setExpandedRows(newExpanded); }; const handleDelete = (backup) => { if (window.confirm(`Are you sure you want to delete this backup from ${format(new Date(backup.created_at), 'PPP')}?`)) { deleteMutation.mutate(backup.id); } }; if (isLoading) { return ; } const backups = data?.recentBackups || []; const pagination = data?.pagination || {}; return (
{/* Search and Filters */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Backup History Table */}
{backups.length === 0 ? ( ) : ( backups.map((backup) => { const StatusIcon = statusIcons[backup.status]?.icon || AlertCircle; const statusColor = statusIcons[backup.status]?.color || 'text-gray-500'; const isExpanded = expandedRows.has(backup.id); const stats = backup.statistics || {}; return ( {/* Expanded Details Row */} {isExpanded && ( )} ); }) )}
Status Date & Time Type Size Duration Actions

No backups found

Backups will appear here once created

{backup.status}

{format(new Date(backup.created_at), 'PPP')}

{format(new Date(backup.created_at), 'p')} • {formatDistanceToNow(new Date(backup.created_at), { addSuffix: true })}

{backup.backup_type || 'Manual'}

{formatBytes(stats.total_size || 0)}

{stats.files_processed || 0} files

{backup.duration_seconds ? `${Math.round(backup.duration_seconds / 60)}m ${backup.duration_seconds % 60}s` : '-'}
{backup.manifest_path && ( )}
{/* Backup Details */}

Backup Details

Destination: {backup.destination_type || 'Unknown'}
Started: {format(new Date(backup.created_at), 'p')}
{backup.completed_at && (
Completed: {format(new Date(backup.completed_at), 'p')}
)}
{/* Content Backed Up */}

Content Backed Up

Database
0 ? 'text-green-500' : 'text-gray-300'}`} /> Photos ({stats.photos_backed_up || 0} of {stats.total_photos || 0})
0 ? 'text-green-500' : 'text-gray-300'}`} /> Archives ({stats.archives_backed_up || 0})
{/* Error Information */} {backup.error_message && (

Error Details

{backup.error_message}

)} {/* Manifest Path */} {backup.manifest_path && (

Manifest

{backup.manifest_path}

)}
{/* Pagination */} {pagination.pages > 1 && (

Showing {(currentPage - 1) * pagination.limit + 1} to{' '} {Math.min(currentPage * pagination.limit, pagination.total)} {' '} of {pagination.total} results

)}
); };