pmtool / src /components /tasks /AttachmentViewerDialog.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
6.83 kB
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;