"use client"; import { FileText, Paperclip, Trash2, Upload, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Dialog, DialogClose, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, } from "@/components/ui/dialog"; import { type Attachment, attachmentUrl, deleteAttachment, formatBytes, listAttachments, } from "@/lib/attachments"; import { notify } from "@/lib/toast"; // A pick-and-stage field: files chosen here are held in `value` until the // parent uploads them (after the patient/lab record is saved). Used in the // patient form and the lab "add result" dialog. export function StagedFilesField({ value, onChange, label, }: { value: File[]; onChange: (files: File[]) => void; label?: string; }) { const { t } = useTranslation(); const inputRef = useRef(null); return (
{label ?? t("patientFiles.title")} { const picked = Array.from(e.target.files ?? []); if (picked.length) onChange([...value, ...picked]); e.target.value = ""; }} ref={inputRef} type="file" />
{value.length > 0 && (
{value.map((file, index) => (
{file.name} {formatBytes(file.size)}
))}
)}
); } // A dialog that previews an attachment: images inline, everything else as a // download link. function FilePreviewDialog({ attachment, onClose, }: { attachment: Attachment | null; onClose: () => void; }) { const { t } = useTranslation(); const isImage = attachment?.mimeType.startsWith("image/"); return ( !o && onClose()} open={attachment !== null}> {attachment?.filename} {attachment && isImage ? ( // eslint-disable-next-line @next/next/no-img-element {attachment.filename} ) : (

{t("patientFiles.noPreview")}

)}
}> {t("patientFiles.close")} {attachment && ( )}
); } // The sheet's "Files" section: lists a patient's uploaded attachments, opens // one in a preview dialog, and lets a clinician delete it. `reloadKey` bumps to // refetch after a new upload elsewhere. export function AttachmentsSection({ fileNumber, reloadKey = 0, canDelete = true, }: { fileNumber: string; reloadKey?: number; canDelete?: boolean; }) { const { t } = useTranslation(); const [items, setItems] = useState([]); const [preview, setPreview] = useState(null); useEffect(() => { let active = true; listAttachments(fileNumber) .then((rows) => active && setItems(rows)) .catch(() => { /* missing permission / none — leave empty */ }); return () => { active = false; }; }, [fileNumber, reloadKey]); const remove = async (attachment: Attachment) => { try { await deleteAttachment(attachment.id); setItems((prev) => prev.filter((a) => a.id !== attachment.id)); notify.success(t("patientFiles.deletedTitle"), attachment.filename); } catch { notify.error( t("patientFiles.deleteFailedTitle"), t("patientFiles.deleteFailedBody"), ); } }; return (

{t("patientFiles.title")}

{items.length === 0 ? (

{t("patientFiles.empty")}

) : (
{items.map((attachment) => (
{canDelete && ( )}
))}
)} setPreview(null)} />
); }