(() => {
/* 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) => ``;
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) => `
| `;
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}
Sebaran kategori
${share}
${legend}
Peringkat Karyawan · skor = 50% penyelesaian + 30% ketepatan waktu + 20% progress · periode ${pretty(range.from)} – ${pretty(range.to)}
| # | Karyawan | Selesai | Tepat waktu | Skor |
${rankRows}
| Jenis Task | User | Diberikan | Target | Progress | Status |
${body || '| 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 });
})();