diff --git a/src/App.tsx b/src/App.tsx index eb4dd05..4d35548 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import { Dashboard } from '@/pages/Dashboard'; import { OpenRequests } from '@/pages/OpenRequests'; import { ClosedRequests } from '@/pages/ClosedRequests'; import { RequestDetail } from '@/pages/RequestDetail'; -import { WorkNoteChat } from '@/components/workNote/WorkNoteChat'; +import { WorkNotes } from '@/pages/WorkNotes'; import { CreateRequest } from '@/pages/CreateRequest'; import { ClaimManagementWizard } from '@/components/workflow/ClaimManagementWizard'; import { MyRequests } from '@/pages/MyRequests'; @@ -547,17 +547,16 @@ function AppRoutes({ onLogout }: AppProps) { } /> - {/* Work Notes/Chat */} + {/* Work Notes - Dedicated Full-Screen Page */} } + element={} /> {/* New Request (Custom) */} diff --git a/src/components/common/FilePreview/FilePreview.css b/src/components/common/FilePreview/FilePreview.css new file mode 100644 index 0000000..a561657 --- /dev/null +++ b/src/components/common/FilePreview/FilePreview.css @@ -0,0 +1,32 @@ +.file-preview-dialog { + width: 90vw !important; + max-width: 90vw !important; + height: 90vh !important; + max-height: 90vh !important; +} + +/* Mobile responsive */ +@media (max-width: 640px) { + .file-preview-dialog { + width: 100vw !important; + max-width: 100vw !important; + height: 100vh !important; + max-height: 100vh !important; + border-radius: 0 !important; + margin: 0 !important; + } +} + +.file-preview-content { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.file-preview-body { + flex: 1; + overflow: auto; + min-height: 0; +} + diff --git a/src/components/common/FilePreview/FilePreview.tsx b/src/components/common/FilePreview/FilePreview.tsx new file mode 100644 index 0000000..342b7c8 --- /dev/null +++ b/src/components/common/FilePreview/FilePreview.tsx @@ -0,0 +1,248 @@ +import { useState, useEffect } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Download, X, Eye, ZoomIn, ZoomOut, RotateCw, Loader2 } from 'lucide-react'; +import './FilePreview.css'; + +interface FilePreviewProps { + fileName: string; + fileType: string; + fileUrl?: string; + fileSize?: number; + attachmentId?: string; + onDownload?: (attachmentId: string) => Promise; + open: boolean; + onClose: () => void; +} + +export function FilePreview({ + fileName, + fileType, + fileUrl, + fileSize, + attachmentId, + onDownload, + open, + onClose +}: FilePreviewProps) { + const [zoom, setZoom] = useState(100); + const [rotation, setRotation] = useState(0); + const [blobUrl, setBlobUrl] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const isImage = fileType.toLowerCase().includes('image') || + fileType.toLowerCase().includes('png') || + fileType.toLowerCase().includes('jpg') || + fileType.toLowerCase().includes('jpeg') || + fileType.toLowerCase().includes('gif') || + fileType.toLowerCase().includes('webp'); + + const isPDF = fileType.toLowerCase().includes('pdf'); + + const canPreview = isImage || isPDF; + + // Fetch file as blob for authenticated preview + useEffect(() => { + if (!open || !canPreview || !fileUrl) { + setBlobUrl(null); + return; + } + + const fetchFile = async () => { + setLoading(true); + setError(null); + + try { + const token = localStorage.getItem('accessToken'); + const response = await fetch(fileUrl, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) { + throw new Error('Failed to load file'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + setBlobUrl(url); + } catch (err) { + console.error('Failed to load file for preview:', err); + setError('Failed to load file for preview'); + } finally { + setLoading(false); + } + }; + + fetchFile(); + + // Cleanup blob URL when modal closes + return () => { + if (blobUrl) { + window.URL.revokeObjectURL(blobUrl); + } + }; + }, [open, fileUrl, canPreview]); + + const handleDownload = async () => { + if (onDownload && attachmentId) { + try { + await onDownload(attachmentId); + } catch (error) { + alert('Failed to download file'); + } + } + }; + + const handleZoomIn = () => setZoom(prev => Math.min(prev + 25, 200)); + const handleZoomOut = () => setZoom(prev => Math.max(prev - 25, 50)); + const handleRotate = () => setRotation(prev => (prev + 90) % 360); + + return ( + + +
+ +
+
+ +
+ + {fileName} + +

+ {fileType} {fileSize && `• ${(fileSize / 1024).toFixed(1)} KB`} +

+
+
+ +
+ {isImage && ( +
+ + {zoom}% + + +
+ )} + + {onDownload && attachmentId && ( + + )} +
+
+
+ +
+ {loading ? ( +
+ +

Loading preview...

+
+ ) : error ? ( +
+
+ +
+

Preview Failed

+

{error}

+ {onDownload && attachmentId && ( + + )} +
+ ) : canPreview && blobUrl ? ( + <> + {isImage && ( +
+ {fileName} +
+ )} + + {isPDF && ( +
+