(() => { const { Modal, Button, Badge, Icon, Field, Input, Select, Textarea, DataTable, Tabs, Avatar } = window.TaskFlowDesignSystem_dd53c2; /* Import & template task — dua cara membuat banyak task sekaligus. Semua parsing dilakukan sekali saat tombol Proses ditekan (bukan per ketikan), supaya file besar tidak membuat halaman berat. */ const CATS = ["Dokumen", "Operasional", "Kepegawaian", "Kegiatan", "Sarana & Prasarana", "Keuangan"]; const HEADER = "jenis task;email user;kategori;prioritas;target;perlu ttd"; const SAMPLE = `Rekap Kunjungan Pasien;ayu@bbmc.id;Operasional;High;30 Agu 2026;tidak Laporan Stok Obat;rafi@bbmc.id;Operasional;Medium;31 Agu 2026;ya Audit Kebersihan Ruang;john@bbmc.id;Sarana & Prasarana;Low;28 Agu 2026;tidak`; const TEMPLATES = [ { id: "bulanan", name: "Laporan Bulanan Klinik", desc: "4 laporan rutin akhir bulan", items: [ { type: "Laporan Kunjungan Pasien", category: "Operasional", priority: "High", needSign: true }, { type: "Laporan Stok Obat", category: "Operasional", priority: "Medium", needSign: false }, { type: "Rekap Absensi Karyawan", category: "Kepegawaian", priority: "Medium", needSign: true }, { type: "Laporan Keuangan Bulanan", category: "Keuangan", priority: "High", needSign: true }, ] }, { id: "akreditasi", name: "Persiapan Akreditasi", desc: "5 dokumen wajib akreditasi", items: [ { type: "Pembaruan SOP Pelayanan", category: "Dokumen", priority: "High", needSign: true }, { type: "Kalibrasi Alat Medis", category: "Sarana & Prasarana", priority: "High", needSign: true }, { type: "Pelatihan Staf & Notulen", category: "Kepegawaian", priority: "Medium", needSign: false }, { type: "Audit Mutu Layanan", category: "Kegiatan", priority: "High", needSign: false }, { type: "Inventarisasi Aset", category: "Sarana & Prasarana", priority: "Medium", needSign: false }, ] }, { id: "mingguan", name: "Rutinitas Mingguan", desc: "3 task berulang tiap minggu", items: [ { type: "Kontrol Kebersihan Ruang", category: "Sarana & Prasarana", priority: "Medium", needSign: false, repeat: "weekly" }, { type: "Cek Stok Habis Pakai", category: "Operasional", priority: "Medium", needSign: false, repeat: "weekly" }, { type: "Rekap Keluhan Pasien", category: "Operasional", priority: "Low", needSign: false, repeat: "weekly" }, ] }, ]; const PRIOS = ["High", "Medium", "Low"]; const norm = (s) => String(s || "").trim(); function parseCsv(text, users) { const lines = String(text || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean) .filter((l) => l.toLowerCase().replace(/,/g, ";") !== HEADER); return lines.map((line, i) => { const c = line.split(/[;\t]|,(?=\s*[^ ])/).map(norm); const email = (c[1] || "").toLowerCase(); const u = users.find((x) => x.email.toLowerCase() === email); const errs = []; if (!c[0]) errs.push("jenis task kosong"); if (!u) errs.push("email user tidak dikenal"); if (c[2] && !CATS.includes(c[2])) errs.push("kategori tidak dikenal"); if (c[3] && !PRIOS.includes(c[3])) errs.push("prioritas tidak dikenal"); if (!/^\d{1,2}\s\w{3}\s\d{4}$/.test(c[4] || "")) errs.push("format target harus cth. 30 Agu 2026"); return { line: i + 1, type: c[0], user: u ? u.id : null, userName: u ? u.name : email || "—", category: CATS.includes(c[2]) ? c[2] : "Operasional", priority: PRIOS.includes(c[3]) ? c[3] : "Medium", target: c[4], needSign: /^(ya|yes|1|true)$/i.test(c[5] || ""), errs }; }); } function ImportTasksModal({ open, onClose, onCreate, users = USERS }) { const bp = useBreakpoint(); const narrow = bp !== "desktop"; const [mode, setMode] = React.useState("csv"); const [text, setText] = React.useState(""); const [rows, setRows] = React.useState(null); const [tpl, setTpl] = React.useState(TEMPLATES[0].id); const [tplTarget, setTplTarget] = React.useState(""); const [tplRows, setTplRows] = React.useState(null); const [note, setNote] = React.useState(""); if (!open) return null; const template = TEMPLATES.find((t) => t.id === tpl) || TEMPLATES[0]; const valid = rows ? rows.filter((r) => r.errs.length === 0) : []; const readFile = (e) => { const f = e.target.files && e.target.files[0]; if (!f) return; if (!/\.(csv|txt)$/i.test(f.name)) { setNote("Gunakan file .csv (Excel: Save As → CSV)."); return; } const rd = new FileReader(); rd.onload = () => { setText(String(rd.result || "")); setRows(null); setNote(`${f.name} dimuat — tekan Proses untuk memeriksa.`); }; rd.readAsText(f); }; const openTemplate = () => setTplRows(template.items.map((it, i) => ({ ...it, user: users[i % users.length].id, target: tplTarget }))); const createFromCsv = () => { valid.forEach((r) => onCreate({ type: r.type, category: r.category, priority: r.priority, needSign: r.needSign, users: [r.user], assigned: TODAY, target: r.target, isProject: false })); setNote(`${valid.length} task dibuat dari import.`); setRows(null); setText(""); onClose(); }; const createFromTemplate = () => { const list = (tplRows || []).filter((r) => r.target); list.forEach((r) => onCreate({ type: r.type, category: r.category, priority: r.priority, needSign: r.needSign, repeat: r.repeat, users: [r.user], assigned: TODAY, target: r.target, isProject: false })); onClose(); }; return ( {mode === "csv" ? (rows ? : ) : (tplRows ? : )}}>
{ setMode(id); setNote(""); }} tabs={[{ id: "csv", label: "Dari CSV / Excel" }, { id: "tpl", label: "Template Task", count: TEMPLATES.length }]} />
{mode === "csv" && !rows && (
Satu baris = satu task, dipisah titik koma: {HEADER}. Kolom kategori/prioritas boleh kosong (default Operasional · Medium).