import React, { useState, useEffect } from 'react'; import { Box, Card, CardContent, Typography, LinearProgress, Chip, Avatar, List, ListItem, ListItemAvatar, ListItemText, ListItemSecondaryAction, IconButton, Fab, Paper, useTheme, alpha, } from '@mui/material'; import Grid from '@mui/material/GridLegacy'; import { CloudUpload as UploadIcon, Article as DocumentIcon, Search as SearchIcon, TrendingUp as TrendingUpIcon, CloudDone as StorageIcon, AutoAwesome as OcrIcon, FindInPage as SearchableIcon, Add as AddIcon, GetApp as DownloadIcon, Visibility as ViewIcon, Delete as DeleteIcon, InsertDriveFile as FileIcon, PictureAsPdf as PdfIcon, Image as ImageIcon, TextSnippet as TextIcon, } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; import api from '../../services/api'; interface Document { id: string; original_filename?: string; filename?: string; file_size?: number; mime_type?: string; created_at?: string; ocr_text?: string; has_ocr_text?: boolean; } interface DashboardStats { totalDocuments: number; totalSize: number; ocrProcessed: number; searchablePages: number; } interface StatsCardProps { title: string; value: string | number; subtitle: string; icon: React.ComponentType; color: string; trend?: string; } interface RecentDocumentsProps { documents: Document[]; } interface QuickAction { title: string; description: string; icon: React.ComponentType; color: string; path: string; } // Stats Card Component const StatsCard: React.FC = ({ title, value, subtitle, icon: Icon, color, trend }) => { const theme = useTheme(); return ( {value} {title} {subtitle} {trend && ( {trend} )} ); }; // Recent Documents Component const RecentDocuments: React.FC = ({ documents = [] }) => { const navigate = useNavigate(); const theme = useTheme(); // Ensure documents is always an array const safeDocuments = Array.isArray(documents) ? documents : []; const getFileIcon = (mimeType?: string): React.ComponentType => { if (mimeType?.includes('pdf')) return PdfIcon; if (mimeType?.includes('image')) return ImageIcon; if (mimeType?.includes('text')) return TextIcon; return FileIcon; }; const formatFileSize = (bytes?: number): string => { if (!bytes) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; }; const formatDate = (dateString?: string): string => { if (!dateString) return 'Unknown'; return new Date(dateString).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); }; return ( Recent Documents navigate('/documents')} sx={{ cursor: 'pointer', background: 'linear-gradient(135deg, rgba(99,102,241,0.1) 0%, rgba(139,92,246,0.1) 100%)', border: '1px solid rgba(99,102,241,0.3)', fontWeight: 600, transition: 'all 0.2s ease-in-out', '&:hover': { background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', color: 'white', transform: 'translateY(-2px)', boxShadow: '0 8px 24px rgba(99,102,241,0.2)', }, }} /> {safeDocuments.length === 0 ? ( No documents yet Upload your first document to get started ) : ( {safeDocuments.slice(0, 5).map((doc, index) => { const FileIconComponent = getFileIcon(doc.mime_type); return ( {doc.original_filename || doc.filename || 'Unknown Document'} } secondary={ {formatFileSize(doc.file_size)} {formatDate(doc.created_at)} } /> navigate(`/documents/${doc.id}`)}> { const downloadUrl = `/api/documents/${doc.id}/download`; window.open(downloadUrl, '_blank'); }} > ); })} )} ); }; // Quick Actions Component const QuickActions: React.FC = () => { const navigate = useNavigate(); const theme = useTheme(); const actions: QuickAction[] = [ { title: 'Upload Documents', description: 'Add new files for OCR processing', icon: UploadIcon, color: '#6366f1', path: '/upload', }, { title: 'Search Library', description: 'Find documents by content or metadata', icon: SearchIcon, color: '#10b981', path: '/search', }, { title: 'Browse Documents', description: 'View and manage your document library', icon: SearchableIcon, color: '#f59e0b', path: '/documents', }, ]; return ( Quick Actions {actions.map((action) => ( navigate(action.path)} > {action.title} {action.description} ))} ); }; const Dashboard: React.FC = () => { const theme = useTheme(); const navigate = useNavigate(); const { user } = useAuth(); const [documents, setDocuments] = useState([]); const [stats, setStats] = useState({ totalDocuments: 0, totalSize: 0, ocrProcessed: 0, searchablePages: 0, }); const [loading, setLoading] = useState(true); const [metrics, setMetrics] = useState(null); useEffect(() => { const fetchDashboardData = async (): Promise => { try { // Fetch documents with better error handling let docs: Document[] = []; try { const docsResponse = await api.get('/documents'); docs = Array.isArray(docsResponse.data) ? docsResponse.data : []; } catch (docError) { console.error('Failed to fetch documents:', docError); // Continue with empty documents array } setDocuments(docs); // Fetch metrics with better error handling let metricsData: any = null; try { const metricsResponse = await api.get('/metrics'); metricsData = metricsResponse.data; setMetrics(metricsData); } catch (metricsError) { console.error('Failed to fetch metrics:', metricsError); // Continue with null metrics - will fall back to client calculation } // Use backend metrics if available, otherwise fall back to client calculation if (metricsData?.documents) { setStats({ totalDocuments: metricsData.documents.total_documents || 0, totalSize: metricsData.documents.total_storage_bytes || 0, ocrProcessed: metricsData.documents.documents_with_ocr || 0, searchablePages: metricsData.documents.documents_with_ocr || 0, }); } else { // Fallback to client-side calculation const totalSize = docs.reduce((sum, doc) => sum + (doc.file_size || 0), 0); const ocrProcessed = docs.filter(doc => doc.has_ocr_text || doc.ocr_text).length; setStats({ totalDocuments: docs.length, totalSize, ocrProcessed, searchablePages: docs.length, }); } } catch (error) { console.error('Unexpected error in dashboard data fetch:', error); // Set default empty state setDocuments([]); setStats({ totalDocuments: 0, totalSize: 0, ocrProcessed: 0, searchablePages: 0, }); } finally { setLoading(false); } }; fetchDashboardData(); }, []); const formatBytes = (bytes: number): string => { 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(1)) + ' ' + sizes[i]; }; return ( {/* Welcome Header */} Welcome back, {user?.username}! 👋 Here's what's happening with your documents today. {/* Stats Cards */} 0 ? `${Math.round((stats.ocrProcessed / stats.totalDocuments) * 100)}% completion` : '0% completion'} /> {/* Main Content */} {/* Floating Action Button */} navigate('/upload')} > ); }; export default Dashboard;