Added Eye preview dialog
X-Lovable-Edit-ID: edt-0a438a9c-221b-4e3e-a795-688652c30acc Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -14,9 +14,17 @@ import {
|
||||
FolderPlus,
|
||||
ChevronRight,
|
||||
Home,
|
||||
Eye,
|
||||
ExternalLink,
|
||||
} from "lucide-react";
|
||||
import { formatDate } from "@/lib/format";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
const MAX_BYTES = 50 * 1024 * 1024;
|
||||
|
||||
@@ -29,6 +37,9 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) {
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const [creatingFolder, setCreatingFolder] = useState(false);
|
||||
const [newFolderName, setNewFolderName] = useState("");
|
||||
const [previewDoc, setPreviewDoc] = useState<any | null>(null);
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [previewLoading, setPreviewLoading] = useState(false);
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
@@ -205,6 +216,23 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) {
|
||||
window.open(data.signedUrl, "_blank");
|
||||
};
|
||||
|
||||
const openPreview = async (doc: any) => {
|
||||
setPreviewDoc(doc);
|
||||
setPreviewUrl(null);
|
||||
setPreviewLoading(true);
|
||||
const { data, error } = await supabase.storage
|
||||
.from("case-documents")
|
||||
.createSignedUrl(doc.storage_path, 300);
|
||||
setPreviewLoading(false);
|
||||
if (error) { toast.error(error.message); setPreviewDoc(null); return; }
|
||||
setPreviewUrl(data.signedUrl);
|
||||
};
|
||||
|
||||
const closePreview = () => {
|
||||
setPreviewDoc(null);
|
||||
setPreviewUrl(null);
|
||||
};
|
||||
|
||||
const del = async (doc: any) => {
|
||||
if (!confirm(`Delete ${doc.name}?`)) return;
|
||||
await supabase.storage.from("case-documents").remove([doc.storage_path]);
|
||||
@@ -341,8 +369,9 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) {
|
||||
{d.size_bytes ? `${(d.size_bytes / 1024).toFixed(1)} KB` : "—"}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
<Button variant="ghost" size="icon" onClick={() => download(d)}><Download className="h-4 w-4" /></Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => del(d)}><Trash2 className="h-4 w-4 text-destructive" /></Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => openPreview(d)} title="Quick preview"><Eye className="h-4 w-4" /></Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => download(d)} title="Download"><Download className="h-4 w-4" /></Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => del(d)} title="Delete"><Trash2 className="h-4 w-4 text-destructive" /></Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -351,6 +380,80 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Dialog open={!!previewDoc} onOpenChange={(o) => !o && closePreview()}>
|
||||
<DialogContent className="max-w-5xl w-[95vw] h-[85vh] flex flex-col p-0 gap-0">
|
||||
<DialogHeader className="px-4 py-3 border-b flex-row items-center justify-between space-y-0">
|
||||
<DialogTitle className="truncate pr-4">{previewDoc?.name}</DialogTitle>
|
||||
{previewUrl && (
|
||||
<div className="flex items-center gap-1 mr-6">
|
||||
<Button variant="ghost" size="sm" onClick={() => window.open(previewUrl, "_blank")}>
|
||||
<ExternalLink className="h-4 w-4 mr-1.5" /> Open
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => previewDoc && download(previewDoc)}>
|
||||
<Download className="h-4 w-4 mr-1.5" /> Download
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogHeader>
|
||||
<div className="flex-1 min-h-0 bg-muted/30">
|
||||
{previewLoading ? (
|
||||
<div className="h-full flex items-center justify-center text-muted-foreground">
|
||||
<Loader2 className="h-5 w-5 animate-spin mr-2" /> Loading preview…
|
||||
</div>
|
||||
) : previewUrl && previewDoc ? (
|
||||
<PreviewBody url={previewUrl} doc={previewDoc} />
|
||||
) : null}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewBody({ url, doc }: { url: string; doc: any }) {
|
||||
const mime = (doc.mime_type || "").toLowerCase();
|
||||
const name = (doc.name || "").toLowerCase();
|
||||
const isImage = mime.startsWith("image/") || /\.(png|jpe?g|gif|webp|svg|bmp)$/i.test(name);
|
||||
const isPdf = mime === "application/pdf" || name.endsWith(".pdf");
|
||||
const isText =
|
||||
mime.startsWith("text/") ||
|
||||
/\.(txt|md|csv|json|log|xml|yml|yaml)$/i.test(name);
|
||||
const isAudio = mime.startsWith("audio/") || /\.(mp3|wav|m4a|ogg)$/i.test(name);
|
||||
const isVideo = mime.startsWith("video/") || /\.(mp4|webm|mov)$/i.test(name);
|
||||
|
||||
if (isImage) {
|
||||
return (
|
||||
<div className="h-full w-full flex items-center justify-center overflow-auto p-4">
|
||||
<img src={url} alt={doc.name} className="max-h-full max-w-full object-contain" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isPdf || isText) {
|
||||
return <iframe src={url} title={doc.name} className="h-full w-full border-0 bg-white" />;
|
||||
}
|
||||
if (isAudio) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center p-4">
|
||||
<audio controls src={url} className="w-full max-w-xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isVideo) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center p-4 bg-black">
|
||||
<video controls src={url} className="max-h-full max-w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center text-center p-8 text-muted-foreground">
|
||||
<FileText className="h-10 w-10 mb-3 opacity-60" />
|
||||
<p className="mb-1">No inline preview available for this file type.</p>
|
||||
<p className="text-xs mb-4">{doc.mime_type || "Unknown type"}</p>
|
||||
<Button onClick={() => window.open(url, "_blank")}>
|
||||
<ExternalLink className="h-4 w-4 mr-1.5" /> Open in new tab
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user