(() => { const { Modal, Button, Textarea, Avatar, Badge, EmptyState, Icon, AvatarStack, FileUpload } = window.TaskFlowDesignSystem_dd53c2; const fmtKb = (b) => (b >= 1048576 ? `${(b / 1048576).toFixed(2)} MB` : `${Math.max(1, Math.round(b / 1024))} KB`); /* Demo download: the real app streams the stored file. Here we emit a small valid PDF carrying the file's metadata so the download itself is testable. */ const downloadFile = (file, subject) => { // Berkas asli hasil upload (data URL) diunduh apa adanya. const src = file && (file.url || file.src); if (src) { const a = document.createElement("a"); a.href = src; a.download = file.name; document.body.appendChild(a); a.click(); a.remove(); return; } // Tanpa berkas tersimpan: unduh ringkasan metadata agar tetap ada jejak. const lines = [`Klinik BBMC - Task Management System`, `Lampiran: ${file.name}`, `Konteks: ${subject}`, `Ukuran setelah kompresi: ${fmtKb(file.compressed || file.size)}`]; const text = lines.map((l, i) => `BT /F1 12 Tf 40 ${740 - i * 22} Td (${l.replace(/[()\\]/g, "")}) Tj ET`).join("\n"); const content = `4 0 obj<>stream\n${text}\nendstream endobj`; const pdf = `%PDF-1.4\n1 0 obj<>endobj\n2 0 obj<>endobj\n3 0 obj<>>>/Contents 4 0 R>>endobj\n${content}\n5 0 obj<>endobj\ntrailer<>`; const url = URL.createObjectURL(new Blob([pdf], { type: "application/pdf" })); const a = document.createElement("a"); a.href = url; a.download = file.name.replace(/\.[^.]*$/, "") + ".pdf"; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1500); }; /* Threaded discussion for a task or a project. Not live chat — messages are posted and read on page load, mirroring the PRD's AJAX-polling approach. For a project only its members (plus the Admin) may read or reply. */ /* @mention: klik chip untuk menyisipkan, teks @Nama disorot di bubble pesan. */ const renderMentions = (text, mine) => String(text || "").split(/(@[A-Za-z]+)/g).map((part, i) => ( /^@[A-Za-z]+$/.test(part) ? {part} : {part} )); function DiscussionModal({ open, subject, messages = [], me, allowed, onClose, onSend }) { const [draft, setDraft] = React.useState(""); const [attach, setAttach] = React.useState(null); const [viewing, setViewing] = React.useState(null); const isAdmin = me.id === 0; const canRead = !allowed || isAdmin || allowed.includes(me.id); if (!open) return null; const send = () => { if (!draft.trim() && !(attach && attach.ok)) return; onSend(draft.trim() || (attach ? `Lampiran: ${attach.name}` : ""), attach && attach.ok ? attach : undefined); setDraft(""); setAttach(null); }; return (
{subject.members && (
({ name: userById(id).name }))} size={26} max={5} /> Anggota project + Admin {messages.length} pesan
)} {!canRead ? ( ) : ( <>
{messages.length === 0 && ( Belum ada diskusi. Mulai percakapan di bawah. )} {messages.map((m, i) => { const mine = m.by === me.id; return (
{mine ? "Anda" : m.name} {m.at} {renderMentions(m.text, mine)} {m.file && ( {m.file.name} {fmtKb(m.file.compressed || m.file.size)} )} {m.role && {m.role}}
); })}