crm finish

This commit is contained in:
2026-03-14 18:58:23 -05:00
parent c0cc546e33
commit df3f1412f6
16 changed files with 679 additions and 38 deletions

View File

@@ -8,6 +8,7 @@ import { api, ApiError } from "../../lib/api";
interface CrmAttachmentsPanelProps {
ownerType: string;
ownerId: string;
onAttachmentCountChange?: (count: number) => void;
}
function formatFileSize(sizeBytes: number) {
@@ -22,11 +23,12 @@ function formatFileSize(sizeBytes: number) {
return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
}
export function CrmAttachmentsPanel({ ownerType, ownerId }: CrmAttachmentsPanelProps) {
export function CrmAttachmentsPanel({ ownerType, ownerId, onAttachmentCountChange }: CrmAttachmentsPanelProps) {
const { token, user } = useAuth();
const [attachments, setAttachments] = useState<FileAttachmentDto[]>([]);
const [status, setStatus] = useState("Loading attachments...");
const [isUploading, setIsUploading] = useState(false);
const [deletingAttachmentId, setDeletingAttachmentId] = useState<string | null>(null);
const canReadFiles = user?.permissions.includes(permissions.filesRead) ?? false;
const canWriteFiles = user?.permissions.includes(permissions.filesWrite) ?? false;
@@ -40,6 +42,7 @@ export function CrmAttachmentsPanel({ ownerType, ownerId }: CrmAttachmentsPanelP
.getAttachments(token, ownerType, ownerId)
.then((nextAttachments) => {
setAttachments(nextAttachments);
onAttachmentCountChange?.(nextAttachments.length);
setStatus(
nextAttachments.length === 0 ? "No attachments uploaded yet." : `${nextAttachments.length} attachment(s) available.`
);
@@ -48,7 +51,7 @@ export function CrmAttachmentsPanel({ ownerType, ownerId }: CrmAttachmentsPanelP
const message = error instanceof ApiError ? error.message : "Unable to load attachments.";
setStatus(message);
});
}, [canReadFiles, ownerId, ownerType, token]);
}, [canReadFiles, onAttachmentCountChange, ownerId, ownerType, token]);
async function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
@@ -61,7 +64,11 @@ export function CrmAttachmentsPanel({ ownerType, ownerId }: CrmAttachmentsPanelP
try {
const attachment = await api.uploadFile(token, file, ownerType, ownerId);
setAttachments((current) => [attachment, ...current]);
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.";
@@ -88,6 +95,30 @@ export function CrmAttachmentsPanel({ ownerType, ownerId }: CrmAttachmentsPanelP
}
}
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 (
<article className="min-w-0 rounded-[28px] border border-line/70 bg-surface/90 p-6 shadow-panel 2xl:p-7">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
@@ -135,6 +166,16 @@ export function CrmAttachmentsPanel({ ownerType, ownerId }: CrmAttachmentsPanelP
>
Open
</button>
{canWriteFiles ? (
<button
type="button"
onClick={() => handleDelete(attachment)}
disabled={deletingAttachmentId === attachment.id}
className="rounded-2xl border border-rose-400/40 px-4 py-2 text-sm font-semibold text-rose-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-rose-300"
>
{deletingAttachmentId === attachment.id ? "Deleting..." : "Delete"}
</button>
) : null}
</div>
</div>
))}