// ===== Dashboard Feature ===== // Dashboard informatif untuk Admin dan Pengelola Rumah Ibadah (function () { 'use strict'; // ── State ────────────────────────────────────────────────────────────── let dashboardData = null; let chartInstances = {}; // ── Modal Element ────────────────────────────────────────────────────── const modal = document.getElementById('dashboardModal'); if (!modal) return; // ── Open / Close ─────────────────────────────────────────────────────── window.openDashboard = function () { modal.classList.add('show'); loadDashboard(); }; window.closeDashboard = function () { modal.classList.remove('show'); }; document.getElementById('closeDashboardModal')?.addEventListener('click', window.closeDashboard); modal.addEventListener('click', function (e) { if (e.target === modal) window.closeDashboard(); }); // ── Load Data ────────────────────────────────────────────────────────── function loadDashboard() { const body = document.getElementById('dashboardBody'); body.innerHTML = `
Memuat data dashboard…
`; fetch('api/dashboard/stats.php') .then(r => r.json()) .then(data => { if (data.status !== 'success') throw new Error(data.message); dashboardData = data; renderDashboard(data); }) .catch(err => { body.innerHTML = `
❌ Gagal memuat data: ${err.message}
`; }); } // ── Render ───────────────────────────────────────────────────────────── function renderDashboard(data) { const { stats, distribusi, ibadah_list, ibadah_performa, recent_logs, role } = data; const isPengelola = role === 'pengelola'; const ibadah = ibadah_list?.[0] ?? null; const body = document.getElementById('dashboardBody'); // ── Header info ────────────────────────────────────────────────── let headerHtml = ''; if (isPengelola && ibadah) { const emoji = (window.IBADAH_EMOJI || {})[ibadah.jenis] || '🕌'; headerHtml = `
${emoji}
${esc(ibadah.nama)}
${esc(ibadah.jenis)}  ·  Radius ${ibadah.radius} m  ·  ${esc(ibadah.alamat || '-')}
`; } // ── KPI Cards ──────────────────────────────────────────────────── const pctTerurus = stats.pct_terurus; const pctColor = pctTerurus >= 70 ? '#22c55e' : pctTerurus >= 40 ? '#f59e0b' : '#ef4444'; let kpiCards = `
${isPengelola ? '' : `
🕌
${stats.total_ibadah}
Rumah Ibadah
`}
🏠
${stats.total_miskin}
KK Miskin${isPengelola ? ' (radius)' : ''}
👥
${stats.total_jiwa}
Total Jiwa
📋
${stats.total_log}
Log Bantuan
`; // ── Coverage bar ────────────────────────────────────────────────── const coverageHtml = stats.total_miskin > 0 ? `
📊 Cakupan Penanganan
✅ Terurus: ${stats.terurus} ⚠️ Tidak Terurus: ${stats.tidak_terurus} ${pctTerurus}%
` : ''; // ── Charts area ─────────────────────────────────────────────────── const chartsHtml = `
📦 Kategori Bantuan
🎁 Tipe Bantuan (Log)
${!isPengelola ? `
🕌 Jenis Rumah Ibadah
` : ''}
📅 Aktivitas Bantuan (6 Bulan)
`; // ── Recent logs ─────────────────────────────────────────────────── let logsHtml = `
🕐 Bantuan Terbaru
`; if (recent_logs.length === 0) { logsHtml += `
Belum ada log bantuan
`; } else { logsHtml += `
`; recent_logs.forEach(log => { const tgl = new Date(log.tanggal).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }); const emoji = (window.IBADAH_EMOJI || {})[log.jenis_ibadah] || '🕌'; logsHtml += `
${esc(log.tipe_bantuan)} → ${esc(log.nama_kk)}
${emoji} ${esc(log.nama_ibadah)}  ·  ${tgl}${log.keterangan ? `  ·  ${esc(log.keterangan)}` : ''}
`; }); logsHtml += `
`; } logsHtml += `
`; // ── Ibadah performa table (admin only) ──────────────────────────── let perfHtml = ''; if (!isPengelola && ibadah_performa.length > 0) { perfHtml = `
🏆 Performa Rumah Ibadah
${ibadah_performa.map((ib, i) => ` `).join('')}
# Nama Jenis Total Log Penerima Unik
${i + 1} ${esc(ib.nama)} ${esc(ib.jenis)} ${ib.total_log} ${ib.total_penerima}
`; } // ── Assemble ────────────────────────────────────────────────────── body.innerHTML = ` ${headerHtml} ${kpiCards} ${coverageHtml} ${chartsHtml} ${logsHtml} ${perfHtml} `; // ── Draw Charts ─────────────────────────────────────────────────── setTimeout(() => { drawPieChart('chartKategori', distribusi.kategori, ['#6366f1', '#f59e0b', '#22c55e', '#ef4444', '#06b6d4']); drawPieChart('chartTipe', distribusi.tipe_bantuan, ['#4f46e5', '#16a34a', '#dc2626', '#d97706', '#0891b2', '#7c3aed']); if (!isPengelola) drawPieChart('chartJenis', distribusi.jenis_ibadah, ['#f97316', '#6366f1', '#22c55e', '#ec4899', '#14b8a6']); drawBarChart('chartBulan', distribusi.per_bulan); }, 50); } // ── Chart helpers ────────────────────────────────────────────────────── function drawPieChart(canvasId, dataObj, colors) { const canvas = document.getElementById(canvasId); if (!canvas) return; const entries = Object.entries(dataObj || {}); if (entries.length === 0) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#aaa'; ctx.font = '13px Inter'; ctx.textAlign = 'center'; ctx.fillText('Belum ada data', canvas.width / 2, canvas.height / 2); return; } const labels = entries.map(([k]) => k); const values = entries.map(([, v]) => v); const total = values.reduce((a, b) => a + b, 0); const ctx = canvas.getContext('2d'); const cx = canvas.width / 2; const cy = canvas.height * 0.45; const r = Math.min(cx, cy) * 0.75; ctx.clearRect(0, 0, canvas.width, canvas.height); let startAngle = -Math.PI / 2; values.forEach((v, i) => { const slice = (v / total) * 2 * Math.PI; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, r, startAngle, startAngle + slice); ctx.closePath(); ctx.fillStyle = colors[i % colors.length]; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); startAngle += slice; }); // Center text ctx.fillStyle = '#333'; ctx.font = 'bold 16px Inter'; ctx.textAlign = 'center'; ctx.fillText(total, cx, cy + 5); // Legend const legY = cy + r + 16; const legItemW = 90; const perRow = Math.floor(canvas.width / legItemW); labels.forEach((lbl, i) => { const row = Math.floor(i / perRow); const col = i % perRow; const lx = (canvas.width - Math.min(labels.length, perRow) * legItemW) / 2 + col * legItemW; const ly = legY + row * 18; ctx.fillStyle = colors[i % colors.length]; ctx.fillRect(lx, ly, 10, 10); ctx.fillStyle = '#555'; ctx.font = '10px Inter'; ctx.textAlign = 'left'; const shortLbl = lbl.length > 10 ? lbl.slice(0, 9) + '…' : lbl; ctx.fillText(`${shortLbl} (${values[i]})`, lx + 13, ly + 9); }); } function drawBarChart(canvasId, perBulan) { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); if (!perBulan || perBulan.length === 0) { ctx.fillStyle = '#aaa'; ctx.font = '13px Inter'; ctx.textAlign = 'center'; ctx.fillText('Belum ada data bantuan', canvas.width / 2, canvas.height / 2); return; } const padL = 36, padR = 12, padT = 12, padB = 28; const W = canvas.width - padL - padR; const H = canvas.height - padT - padB; const values = perBulan.map(d => parseInt(d.cnt)); const maxVal = Math.max(...values, 1); const barW = W / perBulan.length * 0.6; const gap = W / perBulan.length; // Grid lines ctx.strokeStyle = '#e5e7eb'; ctx.lineWidth = 1; for (let i = 0; i <= 4; i++) { const y = padT + H - (i / 4) * H; ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(padL + W, y); ctx.stroke(); ctx.fillStyle = '#9ca3af'; ctx.font = '9px Inter'; ctx.textAlign = 'right'; ctx.fillText(Math.round((i / 4) * maxVal), padL - 4, y + 3); } // Bars perBulan.forEach((d, i) => { const bh = (parseInt(d.cnt) / maxVal) * H; const bx = padL + i * gap + (gap - barW) / 2; const by = padT + H - bh; const grad = ctx.createLinearGradient(0, by, 0, padT + H); grad.addColorStop(0, '#6366f1'); grad.addColorStop(1, '#a5b4fc'); ctx.fillStyle = grad; ctx.beginPath(); ctx.roundRect ? ctx.roundRect(bx, by, barW, bh, [4, 4, 0, 0]) : ctx.rect(bx, by, barW, bh); ctx.fill(); // Value label ctx.fillStyle = '#4f46e5'; ctx.font = 'bold 10px Inter'; ctx.textAlign = 'center'; ctx.fillText(d.cnt, bx + barW / 2, by - 4); // Month label const monthLabel = d.bulan ? d.bulan.slice(5) + '/' + d.bulan.slice(2, 4) : ''; ctx.fillStyle = '#6b7280'; ctx.font = '9px Inter'; ctx.fillText(monthLabel, bx + barW / 2, padT + H + 14); }); } // ── Utility ─────────────────────────────────────────────────────────── function esc(str) { if (!str) return ''; return String(str).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } })();