diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 46030c0..0252a70 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -10,6 +10,7 @@ import Dashboard from './components/Dashboard/Dashboard'; import UploadPage from './pages/UploadPage'; import DocumentsPage from './pages/DocumentsPage'; import SearchPage from './pages/SearchPage'; +import DocumentDetailsPage from './pages/DocumentDetailsPage'; function App() { const { user, loading } = useAuth(); @@ -59,6 +60,7 @@ function App() { } /> } /> } /> + } /> } /> Watch Folder Page - Coming Soon} /> Settings Page - Coming Soon} /> diff --git a/frontend/src/pages/DocumentDetailsPage.jsx b/frontend/src/pages/DocumentDetailsPage.jsx new file mode 100644 index 0000000..a59755c --- /dev/null +++ b/frontend/src/pages/DocumentDetailsPage.jsx @@ -0,0 +1,412 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + Box, + Typography, + Card, + CardContent, + Button, + Chip, + Stack, + Grid, + Divider, + IconButton, + Paper, + Alert, + CircularProgress, + Tooltip, + Dialog, + DialogContent, + DialogTitle, + DialogActions, +} from '@mui/material'; +import { + ArrowBack as BackIcon, + Download as DownloadIcon, + PictureAsPdf as PdfIcon, + Image as ImageIcon, + Description as DocIcon, + TextSnippet as TextIcon, + CalendarToday as DateIcon, + Storage as SizeIcon, + Tag as TagIcon, + Visibility as ViewIcon, + Search as SearchIcon, + Edit as EditIcon, +} from '@mui/icons-material'; +import { documentService } from '../services/api'; + +const DocumentDetailsPage = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const [document, setDocument] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [ocrText, setOcrText] = useState(''); + const [showOcrDialog, setShowOcrDialog] = useState(false); + + useEffect(() => { + if (id) { + fetchDocumentDetails(); + } + }, [id]); + + const fetchDocumentDetails = async () => { + try { + setLoading(true); + setError(null); + + // Since we don't have a direct document details endpoint, + // we'll fetch the document from the list and find the matching one + const response = await documentService.list(1000, 0); + const foundDoc = response.data.find(doc => doc.id === id); + + if (foundDoc) { + setDocument(foundDoc); + // If the document has OCR text, we could fetch it here + // For now, we'll show a placeholder + if (foundDoc.has_ocr_text) { + setOcrText('OCR text extraction feature would be available here. The document has been processed and text content is available for search.'); + } + } else { + setError('Document not found'); + } + } catch (err) { + setError('Failed to load document details'); + console.error(err); + } finally { + setLoading(false); + } + }; + + const handleDownload = async () => { + try { + const response = await documentService.download(document.id); + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', document.original_filename); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + } catch (err) { + console.error('Download failed:', err); + } + }; + + const getFileIcon = (mimeType) => { + if (mimeType?.includes('pdf')) return ; + if (mimeType?.includes('image')) return ; + if (mimeType?.includes('text')) return ; + return ; + }; + + const formatFileSize = (bytes) => { + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + if (bytes === 0) return '0 Bytes'; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; + }; + + const formatDate = (dateString) => { + return new Date(dateString).toLocaleString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + if (loading) { + return ( + + + + ); + } + + if (error || !document) { + return ( + + + + {error || 'Document not found'} + + + ); + } + + return ( + + {/* Header */} + + + + + Document Details + + + View and manage document information + + + + + {/* Document Preview */} + + + + + {getFileIcon(document.mime_type)} + + + + {document.original_filename} + + + + + {document.has_ocr_text && ( + + )} + + + {document.has_ocr_text && ( + } + /> + )} + + + + + {/* Document Information */} + + + + + Document Information + + + + + + + + + Filename + + + + {document.original_filename} + + + + + + + + + + File Size + + + + {formatFileSize(document.file_size)} + + + + + + + + + + Upload Date + + + + {formatDate(document.created_at)} + + + + + + + + + + File Type + + + + {document.mime_type} + + + + + {document.tags && document.tags.length > 0 && ( + + + + + + Tags + + + + {document.tags.map((tag, index) => ( + + ))} + + + + )} + + + + + + Processing Status + + + + + + + + Document uploaded successfully + + + + + + + + {document.has_ocr_text ? 'OCR processing completed' : 'OCR processing pending'} + + + + + + + + + + {/* OCR Text Dialog */} + setShowOcrDialog(false)} + maxWidth="md" + fullWidth + > + + + Extracted Text (OCR) + + + + + + {ocrText || 'OCR text extraction is not yet implemented in the frontend. The backend processes documents and stores extracted text for search functionality.'} + + + + + + + + + ); +}; + +export default DocumentDetailsPage; \ No newline at end of file diff --git a/frontend/src/pages/DocumentsPage.jsx b/frontend/src/pages/DocumentsPage.jsx index 9050bfb..72d856f 100644 --- a/frontend/src/pages/DocumentsPage.jsx +++ b/frontend/src/pages/DocumentsPage.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Box, Typography, @@ -41,6 +42,7 @@ import { import { documentService } from '../services/api'; const DocumentsPage = () => { + const navigate = useNavigate(); const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -263,7 +265,10 @@ const DocumentsPage = () => { Download - setDocMenuAnchor(null)}> + { + navigate(`/documents/${selectedDoc.id}`); + setDocMenuAnchor(null); + }}> View Details diff --git a/frontend/src/pages/SearchPage.jsx b/frontend/src/pages/SearchPage.jsx index 2c8ffb1..c5965fc 100644 --- a/frontend/src/pages/SearchPage.jsx +++ b/frontend/src/pages/SearchPage.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Box, Typography, @@ -48,6 +49,7 @@ import { import { documentService } from '../services/api'; const SearchPage = () => { + const navigate = useNavigate(); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [loading, setLoading] = useState(false); @@ -609,6 +611,14 @@ const SearchPage = () => { )} + + navigate(`/documents/${doc.id}`)} + > + + +