Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| Download, | |
| X, | |
| Maximize, | |
| Minimize, | |
| AlertCircle, | |
| Loader2, | |
| Info | |
| } from "lucide-react"; | |
| import { toast } from "sonner"; | |
| import { Attachment, attachmentApi } from '@/services/attachmentApi'; | |
| import { cn } from '@/lib/utils'; | |
| import CustomDialog from '@/components/CustomDialog'; | |
| interface AttachmentViewerDialogProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| attachment: Attachment | null; | |
| onDownload: (attachment: Attachment) => void; | |
| } | |
| const AttachmentViewerDialog: React.FC<AttachmentViewerDialogProps> = ({ | |
| isOpen, | |
| onClose, | |
| attachment, | |
| onDownload | |
| }) => { | |
| const [fileUrl, setFileUrl] = useState<string | null>(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| const [isFullscreen, setIsFullscreen] = useState(false); | |
| useEffect(() => { | |
| const loadFile = async () => { | |
| if (!attachment || !isOpen) return; | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| // Use the attachmentApi directly to download the file | |
| const result = await attachmentApi.download(attachment.attachmentId); | |
| const objectUrl = URL.createObjectURL(result.blob); | |
| setFileUrl(objectUrl); | |
| } catch (err) { | |
| console.error('Error loading file:', err); | |
| setError('Failed to load the file for preview. Please try downloading instead.'); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| loadFile(); | |
| // Cleanup function to revoke the object URL when component unmounts or dialog closes | |
| return () => { | |
| if (fileUrl) { | |
| URL.revokeObjectURL(fileUrl); | |
| setFileUrl(null); | |
| } | |
| }; | |
| }, [attachment, isOpen]); | |
| const toggleFullscreen = () => { | |
| setIsFullscreen(!isFullscreen); | |
| }; | |
| const handleDownload = () => { | |
| if (attachment) { | |
| onDownload(attachment); | |
| } | |
| }; | |
| const isImage = attachment?.fileType?.startsWith('image/') || attachment?.fileName?.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/i); | |
| const isPdf = attachment?.fileType === 'application/pdf' || attachment?.fileName?.endsWith('.pdf'); | |
| const isText = attachment?.fileType?.startsWith('text/') || attachment?.fileName?.match(/\.(txt|md|rtf|json|xml|html|css|js|ts)$/i); | |
| return ( | |
| <CustomDialog | |
| isOpen={isOpen} | |
| onClose={onClose} | |
| title={attachment?.fileName || 'File Preview'} | |
| description={attachment?.fileType || ''} | |
| allowClose={true} | |
| > | |
| <div className={cn("flex flex-col", isFullscreen && "w-full h-full")}> | |
| <div className="flex flex-col mb-3"> | |
| <div className="flex justify-between items-center"> | |
| <div className="text-xs text-muted-foreground"> | |
| {attachment?.createdBy && <span>Added by {attachment.createdBy}</span>} | |
| {attachment?.createdAt && attachment?.createdBy && <span> · </span>} | |
| {attachment?.createdAt && | |
| <span> | |
| {new Date(attachment.createdAt).toLocaleDateString(undefined, { | |
| year: 'numeric', | |
| month: 'short', | |
| day: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| })} | |
| </span> | |
| } | |
| </div> | |
| <Button | |
| variant="outline" | |
| size="icon" | |
| onClick={toggleFullscreen} | |
| className="h-8 w-8" | |
| > | |
| {isFullscreen ? <Minimize className="h-4 w-4" /> : <Maximize className="h-4 w-4" />} | |
| </Button> | |
| </div> | |
| {/* File description */} | |
| {attachment?.description && ( | |
| <div className="mt-2 text-sm flex items-start gap-2 bg-accent/50 p-2 rounded-md"> | |
| <Info className="h-4 w-4 text-muted-foreground mt-0.5 flex-shrink-0" /> | |
| <p className="text-sm text-muted-foreground">{attachment.description}</p> | |
| </div> | |
| )} | |
| </div> | |
| <div className={cn( | |
| "flex-1 overflow-auto flex items-center justify-center rounded-md border bg-muted", | |
| isFullscreen ? "w-full h-[85vh]" : "h-[70vh]" | |
| )}> | |
| {isLoading ? ( | |
| <div className="flex flex-col items-center justify-center p-8 text-center"> | |
| <Loader2 className="h-8 w-8 animate-spin text-primary mb-4" /> | |
| <p className="text-sm text-muted-foreground">Loading preview...</p> | |
| </div> | |
| ) : error ? ( | |
| <div className="flex flex-col items-center justify-center p-8 text-center"> | |
| <AlertCircle className="h-8 w-8 text-destructive mb-4" /> | |
| <p className="text-sm text-destructive">{error}</p> | |
| </div> | |
| ) : fileUrl && isImage ? ( | |
| <img | |
| src={fileUrl} | |
| alt={attachment?.fileName || 'Image preview'} | |
| className="max-w-full max-h-full object-contain" | |
| /> | |
| ) : fileUrl && isPdf ? ( | |
| <iframe | |
| src={fileUrl} | |
| title={attachment?.fileName || 'PDF preview'} | |
| className="w-full h-full" | |
| style={{ minHeight: isFullscreen ? '85vh' : '70vh' }} | |
| /> | |
| ) : fileUrl && isText ? ( | |
| <iframe | |
| src={fileUrl} | |
| title={attachment?.fileName || 'Text preview'} | |
| className="w-full h-full bg-white" | |
| /> | |
| ) : ( | |
| <div className="flex flex-col items-center justify-center p-8 text-center"> | |
| <AlertCircle className="h-8 w-8 text-amber-500 mb-4" /> | |
| <p className="text-sm text-muted-foreground">Preview not available for this file type</p> | |
| <Button | |
| onClick={handleDownload} | |
| variant="outline" | |
| className="mt-4 flex items-center gap-2" | |
| > | |
| <Download className="h-4 w-4" /> | |
| Download to view | |
| </Button> | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex justify-end gap-2 mt-4"> | |
| <Button | |
| onClick={handleDownload} | |
| className="flex items-center gap-2" | |
| > | |
| <Download className="h-4 w-4" /> | |
| Download | |
| </Button> | |
| <Button | |
| variant="outline" | |
| onClick={onClose} | |
| > | |
| Close | |
| </Button> | |
| </div> | |
| </div> | |
| </CustomDialog> | |
| ); | |
| }; | |
| export default AttachmentViewerDialog; |