import type { FileAttachmentDto } from "@mrp/shared"; import { permissions } from "@mrp/shared"; import { useEffect, useState } from "react"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; interface CrmAttachmentsPanelProps { ownerType: string; ownerId: string; onAttachmentCountChange?: (count: number) => void; } function formatFileSize(sizeBytes: number) { if (sizeBytes < 1024) { return `${sizeBytes} B`; } if (sizeBytes < 1024 * 1024) { return `${(sizeBytes / 1024).toFixed(1)} KB`; } return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`; } export function CrmAttachmentsPanel({ ownerType, ownerId, onAttachmentCountChange }: CrmAttachmentsPanelProps) { const { token, user } = useAuth(); const [attachments, setAttachments] = useState([]); const [status, setStatus] = useState("Loading attachments..."); const [isUploading, setIsUploading] = useState(false); const [deletingAttachmentId, setDeletingAttachmentId] = useState(null); const canReadFiles = user?.permissions.includes(permissions.filesRead) ?? false; const canWriteFiles = user?.permissions.includes(permissions.filesWrite) ?? false; useEffect(() => { if (!token || !canReadFiles) { return; } api .getAttachments(token, ownerType, ownerId) .then((nextAttachments) => { setAttachments(nextAttachments); onAttachmentCountChange?.(nextAttachments.length); setStatus( nextAttachments.length === 0 ? "No attachments uploaded yet." : `${nextAttachments.length} attachment(s) available.` ); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load attachments."; setStatus(message); }); }, [canReadFiles, onAttachmentCountChange, ownerId, ownerType, token]); async function handleUpload(event: React.ChangeEvent) { const file = event.target.files?.[0]; if (!file || !token || !canWriteFiles) { return; } setIsUploading(true); setStatus("Uploading attachment..."); try { const attachment = await api.uploadFile(token, file, ownerType, ownerId); setAttachments((current) => { const nextAttachments = [attachment, ...current]; onAttachmentCountChange?.(nextAttachments.length); return nextAttachments; }); setStatus("Attachment uploaded."); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to upload attachment."; setStatus(message); } finally { setIsUploading(false); event.target.value = ""; } } async function handleOpen(attachment: FileAttachmentDto) { if (!token) { return; } try { const blob = await api.getFileContentBlob(token, attachment.id); const objectUrl = window.URL.createObjectURL(blob); window.open(objectUrl, "_blank", "noopener,noreferrer"); window.setTimeout(() => window.URL.revokeObjectURL(objectUrl), 60_000); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to open attachment."; setStatus(message); } } async function handleDelete(attachment: FileAttachmentDto) { if (!token || !canWriteFiles) { return; } setDeletingAttachmentId(attachment.id); setStatus(`Deleting ${attachment.originalName}...`); try { await api.deleteAttachment(token, attachment.id); setAttachments((current) => { const nextAttachments = current.filter((item) => item.id !== attachment.id); onAttachmentCountChange?.(nextAttachments.length); return nextAttachments; }); setStatus("Attachment deleted."); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to delete attachment."; setStatus(message); } finally { setDeletingAttachmentId(null); } } return (

Attachments

Shared files

Drawings, customer markups, vendor documents, and other reference files linked to this record.

{canWriteFiles ? ( ) : null}
{status}
{!canReadFiles ? (
You do not have permission to view file attachments.
) : attachments.length === 0 ? (
No attachments have been added to this record yet.
) : (
{attachments.map((attachment) => (

{attachment.originalName}

{attachment.mimeType} · {formatFileSize(attachment.sizeBytes)} · {new Date(attachment.createdAt).toLocaleString()}

{canWriteFiles ? ( ) : null}
))}
)}
); }