// ═══════════════════════════════════════════════════════════════════ // PROGRAMS PAGE — Detail per program with stats and province breakdown // ═══════════════════════════════════════════════════════════════════ let _activeProgTab = 'mbg'; function renderProgramsPage() { renderProgTabs(); renderProgBody(_activeProgTab); } function renderProgTabs() { document.getElementById('prog-tabs').innerHTML = PROGRAMS.map(p => ` `).join(''); } function switchProgTab(id) { _activeProgTab = id; renderProgTabs(); renderProgBody(id); } function renderProgBody(pid) { const p = getProgramById(pid); if (!p) return; const pct = Math.round((p.capaian / p.target) * 100); const budgetPct = Math.round((p.realisasi / p.budget) * 100); const activeIssues = APP_STATE.issues.filter(i => i.programId === pid && i.status !== 'resolved' && i.status !== 'closed'); const critCount = activeIssues.filter(i => i.severity === 'critical').length; const highCount = activeIssues.filter(i => i.severity === 'high').length; // Location data for this program const progLocs = APP_STATE.locs.filter(l => l.programs.includes(pid)); // Generate province stats const provinceData = {}; progLocs.forEach(l => { const prov = l.province; if (!provinceData[prov]) provinceData[prov] = { locs: 0, issues: 0 }; provinceData[prov].locs++; provinceData[prov].issues += l.issueCount; }); const provinceRows = Object.entries(provinceData) .sort((a,b) => b[1].issues - a[1].issues) .slice(0, 10) .map(([prov, data]) => { const locList = progLocs.filter(l => l.province === prov); let prog = 90; // default coverage if (pid === 'mbg') prog = Math.round(locList.reduce((s,l) => s + (l.mbg?.coverage || 0), 0) / locList.length); else if (pid === 'koperasi') prog = 75; return ` ${prov} ${data.locs} titik
${prog}%
${data.issues > 0 ? `${data.issues} issue` : ''} `; }).join(''); document.getElementById('prog-bodies').innerHTML = `
${p.icon}
${p.name}
📋 ${p.ministry} · Mulai ${p.startDate}
${p.description}
${pct}%
Capaian Target
🟢 Aktif
Realisasi: ${p.capaian.toLocaleString()} ${p.targetUnit}
Target: ${p.target.toLocaleString()} ${p.targetUnit}
${p.kpiLabel.map((lbl, i) => `
${p.kpiValue[i]}
${lbl}
`).join('')}
Rp ${p.realisasi.toLocaleString()}M
Realisasi Anggaran
${budgetPct}%
Serapan Anggaran
${activeIssues.length}
Issue Aktif
${activeIssues.length > 0 ? `
⚠️ Issue Aktif (${activeIssues.length})
${activeIssues.slice(0,3).map(i => `
${getSevBadge(i.severity)} ${getStatBadge(i.status)}
${i.judul}
📍 ${i.daerah}, ${i.provinsi}
`).join('')}
` : ''}
Distribusi per Provinsi (${progLocs.length} titik pemantauan)
${provinceRows || ''}
Provinsi Titik Aktif Capaian Status Issue
Tidak ada data
Lokasi Pemantauan (${progLocs.length} titik)
${progLocs.length === 0 ? `
📍
Belum ada titik pemantauan
Data lokasi untuk program ${p.shortName} belum tersedia
` : `
${progLocs.slice(0, 12).map(loc => { const d = loc[pid]; const hasIssue = loc.issueCount > 0; const relIssue = APP_STATE.issues.find(i => i.daerah === loc.name && i.programId === pid && i.status !== 'resolved'); const onclickAction = (hasIssue && relIssue) ? `openIssueDetail('${relIssue.id}')` : `showToast('${loc.name.replace(/'/g,"\\'")}', '${loc.province.replace(/'/g,"\\'")} — kondisi normal', 'info')`; return `
${loc.name} ${hasIssue ? '⚠ Issue' : ''}
${loc.province}
${pid === 'mbg' && d ? `
Porsi/Hari ${(d.meals||0).toLocaleString()}
` : ''} ${pid === 'koperasi' && d ? `
Anggota ${(d.members||0).toLocaleString()}
${d.status} ` : ''} ${pid === 'psn' && d ? `
${(d.projectName||'—').slice(0,22)}… ${d.progress}%
` : ''} ${pid === 'hilirisasi' && d ? `
${d.commodity||'—'} ${d.progress}%
${d.stage||''}
` : ''} ${pid === 'rumah' && d ? `
Unit Selesai ${(d.completed||0).toLocaleString()}
Target: ${(d.units||0).toLocaleString()} unit
` : ''}
`; }).join('')}
` }
`; } function filterIssuesByProgram(pid) { APP_STATE.issueFilter.program = pid; navigateTo('issues'); setTimeout(() => { const sel = document.getElementById('issues-filter-program'); if (sel) { sel.value = pid; applyIssueFilters(); } }, 100); } function filterIssuesByProvince(prov, pid) { APP_STATE.issueFilter.program = pid; navigateTo('issues'); setTimeout(() => { const sel = document.getElementById('issues-filter-program'); const search = document.getElementById('issues-search'); if (sel) sel.value = pid; if (search) search.value = prov; applyIssueFilters(); }, 100); }