(() => { /* Print-ready dashboard recap for the selected period. Mirrors the on-screen layout: KPI tiles, capsule bars, category share and the task table. */ const MON = ["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des"]; const pretty = (iso) => { const [y, m, d] = String(iso).split("-").map(Number); return `${d} ${MON[m - 1]} ${y}`; }; function exportDashboardPdf(tasks, range) { const rows = tasks.filter((t) => inRange(t.assigned, range.from, range.to)); const count = (s) => rows.filter((t) => t.status === s).length; const late = rows.filter((t) => isOverdue(t.target, t.done)).length; const buckets = []; for (let d = 1; d <= 30; d += 3) { buckets.push({ label: String(d).padStart(2, "0"), value: rows.filter((t) => { const n = parseId(t.assigned).getDate(); return n >= d && n < d + 3; }).length }); } const peak = Math.max(1, ...buckets.map((b) => b.value)); const bars = buckets.map((b) => `
${b.value}
${b.label}
`).join(""); const CATS = ["Dokumen", "Kegiatan", "Sarana & Prasarana", "Kepegawaian", "Operasional"]; const COLORS = { "Dokumen": "#13A88C", "Kegiatan": "#E0A81C", "Sarana & Prasarana": "#2DB67D", "Kepegawaian": "#F0764B", "Operasional": "#7DC242" }; const cats = CATS.map((c) => ({ c, n: rows.filter((t) => t.category === c).length })).filter((x) => x.n); const share = cats.map((x) => ``).join(""); const legend = cats.map((x) => `● ${x.c} ${Math.round((x.n / Math.max(1, rows.length)) * 100)}%`).join(""); const kpi = (label, value, color) => `
${label}
${value}
`; const body = rows.map((t) => ` ${t.type}
${t.category}
${userById(t.user).name} ${t.assigned} ${t.target} ${t.progress}% ${String(t.status).replace(/_/g, " ")} `).join(""); const doneList = rows.filter((t) => t.status === "done"); const onTime = doneList.filter((t) => t.done && !isOverdue(t.target, t.done)).length; const overdueList = rows.filter((t) => (t.done ? isOverdue(t.target, t.done) : t.status !== "done" && isOverdue(t.target))); const respDays = rows.filter((t) => t.started && t.assigned) .map((t) => Math.max(0, Math.round((parseId(t.started) - parseId(t.assigned)) / 86400000))); const perf = { completion: rows.length ? Math.round((doneList.length / rows.length) * 100) : 0, onTimeRate: doneList.length ? Math.round((onTime / doneList.length) * 100) : 0, overdue: overdueList.length, overdueRate: rows.length ? Math.round((overdueList.length / rows.length) * 100) : 0, respAvg: respDays.length ? Math.round((respDays.reduce((s, v) => s + v, 0) / respDays.length) * 10) / 10 : null, respFast: respDays.length ? Math.round((respDays.filter((v) => v <= 1).length / respDays.length) * 100) : 0, respMax: respDays.length ? Math.max(...respDays) : null, }; const perfCell = (label, value, sub, color, pct) => `
${label}
${value}
${sub}
`; const perfRow = ` ${perfCell("Task Completion Rate", perf.completion + "%", `${doneList.length} selesai dari ${rows.length} task`, perf.completion >= 80 ? "#63A32E" : perf.completion >= 50 ? "#0FA582" : "#C08A0F", perf.completion)} ${perfCell("On-Time Completion", perf.onTimeRate + "%", doneList.length ? `${onTime} tepat waktu · ${doneList.length - onTime} lewat deadline` : "Belum ada task selesai", perf.onTimeRate >= 80 ? "#63A32E" : perf.onTimeRate >= 60 ? "#C08A0F" : "#DC2626", perf.onTimeRate)} ${perfCell("Overdue Task", String(perf.overdue), `${perf.overdueRate}% dari total task periode ini`, perf.overdue === 0 ? "#63A32E" : perf.overdueRate >= 25 ? "#DC2626" : "#C08A0F", perf.overdueRate)} ${perfCell("Response Time", perf.respAvg == null ? "—" : perf.respAvg + " hari", perf.respAvg == null ? "Belum ada task yang dimulai" : `${perf.respFast}% mulai ≤1 hari · terlama ${perf.respMax} hari`, perf.respAvg == null ? "#748079" : perf.respAvg <= 1 ? "#63A32E" : perf.respAvg <= 3 ? "#0FA582" : "#DC2626", perf.respFast)}
`; const rank = rankUsers(tasks, range); const rankRows = rank.map((r, i) => ` ${i === 0 ? "🏆 1" : i + 1} ${r.name}
${r.role}
${r.done}/${r.total} ${r.onTimePct}% ${r.score}% `).join(""); const doc = `Dashboard ${range.from} - ${range.to}

Laporan Dashboard

Periode ${pretty(range.from)} s/d ${pretty(range.to)} · dicetak ${TODAY}
${kpi("Total task", rows.length, "#14201B")}${kpi("In progress", count("in_progress"), "#0FA582")} ${kpi("On verification", count("on_verification"), "#C08A0F")}${kpi("Accepted", count("done"), "#63A32E")}${kpi("Terlambat", late, "#DC2626")}
Indikator kinerja periode ini
${perfRow}
Beban task per 3 hari
${bars}
Sebaran kategori
${share}
${legend}
Peringkat Karyawan · skor = 50% penyelesaian + 30% ketepatan waktu + 20% progress · periode ${pretty(range.from)} – ${pretty(range.to)}
${rankRows}
#KaryawanSelesaiTepat waktuSkor
${body || ''}
Jenis TaskUserDiberikanTargetProgressStatus
Tidak ada task pada periode ini.
Klinik BBMC — Task Management System · Powered By Klinik BBMC 2026
`; const frame = document.createElement("iframe"); frame.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0"; document.body.appendChild(frame); frame.srcdoc = doc; frame.onload = () => setTimeout(() => { frame.contentWindow.focus(); frame.contentWindow.print(); setTimeout(() => frame.remove(), 1500); }, 500); } Object.assign(window, { exportDashboardPdf }); })();