diff --git a/src/components/archivos/DetailDialog.tsx b/src/components/archivos/DetailDialog.tsx new file mode 100644 index 0000000..c0adbad --- /dev/null +++ b/src/components/archivos/DetailDialog.tsx @@ -0,0 +1,171 @@ +import { useEffect, useState } from "react" +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Badge } from "@/components/ui/badge" +import { Label } from "@/components/ui/label" +import { Button } from "@/components/ui/button" +import * as Icons from "lucide-react" +import type { RefRow } from "@/types/RefRow" + +// POST -> recibe blob PDF y (opcional) Content-Disposition +async function fetchPdfBlob(url: string, body: { s3_file_path: string }) { + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }) + if (!res.ok) throw new Error(await res.text()) + const blob = await res.blob() + return { blob, disposition: res.headers.get("content-disposition") ?? undefined } +} + +function filenameFromDisposition(disposition?: string, fallback = "archivo.pdf") { + if (!disposition) return fallback + const star = /filename\*\=UTF-8''([^;]+)/i.exec(disposition)?.[1] + if (star) return decodeURIComponent(star) + const simple = /filename\=\"?([^\";]+)\"?/i.exec(disposition)?.[1] + return simple ?? fallback +} + +export function DetailDialog({ + row, + onClose, + pdfUrl = "/api/get/documento", // ← permite inyectar el endpoint +}: { + row: RefRow | null + onClose: () => void + pdfUrl?: string +}) { + const [viewerUrl, setViewerUrl] = useState(null) + const [currentBlob, setCurrentBlob] = useState(null) + const [filename, setFilename] = useState("archivo.pdf") + const open = !!row + + useEffect(() => { + if (!open) return + + let revoked = false + let urlToRevoke: string | null = null + const ctrl = new AbortController() + + async function load() { + if (!row?.s3_file_path) { + setViewerUrl(null) + setCurrentBlob(null) + return + } + try { + const { blob, disposition } = await fetchPdfBlob(`${import.meta.env.VITE_BACK_ORIGIN}/api/get/documento`, { s3_file_path: row.s3_file_path }) + if (ctrl.signal.aborted) return + const name = row.titulo_archivo ? `${row.titulo_archivo}.pdf` : filenameFromDisposition(disposition) + setFilename(name) + const url = URL.createObjectURL(blob) + urlToRevoke = url + if (!revoked) { + setViewerUrl(url) + setCurrentBlob(blob) + } + } catch (e) { + console.error("Carga de PDF falló:", e) + setViewerUrl(null) + setCurrentBlob(null) + } + } + + load() + + return () => { + revoked = true + ctrl.abort() + if (urlToRevoke) URL.revokeObjectURL(urlToRevoke) + } + }, [open, row?.s3_file_path, row?.titulo_archivo, pdfUrl]) + + async function downloadFile() { + try { + // Si ya tenemos el blob, úsalo + if (currentBlob) { + const link = document.createElement("a") + const href = URL.createObjectURL(currentBlob) + link.href = href + link.download = filename + link.click() + URL.revokeObjectURL(href) + return + } + + // Si no, vuelve a pedirlo (p. ej., si el user abre y descarga sin render previo) + if (!row?.s3_file_path) throw new Error("No hay contenido para descargar.") + const { blob, disposition } = await fetchPdfBlob(`${import.meta.env.VITE_BACK_ORIGIN}/api/get/documento`, { s3_file_path: row.s3_file_path }) + const name = row.titulo_archivo ? `${row.titulo_archivo}.pdf` : filenameFromDisposition(disposition) + const link = document.createElement("a") + const href = URL.createObjectURL(blob) + link.href = href + link.download = name + link.click() + URL.revokeObjectURL(href) + } catch (error) { + console.error("Error al descargar el archivo:", error) + alert("No se pudo descargar el archivo.") + } + } + + return ( + !o && onClose()}> + + + {row?.titulo_archivo ?? "(Sin título)"} + {row?.descripcion || "Sin descripción"} + + + {row && ( +
+
+ {row.tipo_contenido ?? "—"} + {row.interno ? "Interno" : "Externo"} + {row.procesado ? "Procesado" : "Pendiente"} + {row.fuente_autoridad && {row.fuente_autoridad}} + {row.fecha_subida && ( + + + {new Date(row.fecha_subida).toLocaleString()} + + )} +
+ + {row.tags?.length ? ( +
+ Tags: + {row.tags.join(", ")} +
+ ) : null} + +
+ +
+ {row.instrucciones || "—"} +
+
+ +
+ + {viewerUrl ? ( +