877 lines
47 KiB
PHP
877 lines
47 KiB
PHP
<?php
|
|
require_once '../includes/config.php';
|
|
requireLogin(['pemerintah','admin']);
|
|
$user = currentUser();
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dashboard Pemerintah — WebGIS Pontianak</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<style>
|
|
:root{
|
|
--bg:#05080e;--surface:#0a0f1a;--card:#0f1625;--card2:#131d2e;
|
|
--border:rgba(99,179,237,0.09);--accent:#3b82f6;--accent2:#06b6d4;
|
|
--green:#10b981;--red:#ef4444;--amber:#f59e0b;--purple:#8b5cf6;--pink:#ec4899;
|
|
--text:#dde6f0;--text2:#607694;--glow:rgba(59,130,246,0.15);
|
|
}
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{font-family:'Sora',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column}
|
|
|
|
/* TOP BAR */
|
|
.topbar{
|
|
background:var(--surface);
|
|
border-bottom:1px solid var(--border);
|
|
padding:0 24px;height:60px;
|
|
display:flex;align-items:center;justify-content:space-between;
|
|
position:sticky;top:0;z-index:200;
|
|
box-shadow:0 2px 24px rgba(0,0,0,0.5);
|
|
}
|
|
.brand{display:flex;align-items:center;gap:12px}
|
|
.brand-icon{
|
|
width:38px;height:38px;
|
|
background:linear-gradient(135deg,#1d4ed8,#0369a1);
|
|
border-radius:11px;display:flex;align-items:center;justify-content:center;font-size:20px;
|
|
box-shadow:0 4px 16px rgba(29,78,216,0.3);
|
|
}
|
|
.brand-name{font-size:15px;font-weight:800;letter-spacing:-0.3px}
|
|
.brand-sub{font-size:10px;color:var(--text2);letter-spacing:0.3px}
|
|
.ro-badge{
|
|
padding:4px 10px;
|
|
background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.25);
|
|
color:#fca5a5;font-size:10px;font-weight:800;letter-spacing:0.5px;
|
|
border-radius:6px;
|
|
}
|
|
.topbar-nav{display:flex;gap:2px}
|
|
.nt{padding:6px 14px;border-radius:8px;border:none;background:transparent;color:var(--text2);font-family:'Sora',sans-serif;font-size:11px;font-weight:700;cursor:pointer;transition:all 0.2s;letter-spacing:0.3px}
|
|
.nt.active{background:rgba(59,130,246,0.12);color:var(--accent)}
|
|
.nt:hover:not(.active){background:rgba(255,255,255,0.04);color:var(--text)}
|
|
.topbar-right{display:flex;align-items:center;gap:10px}
|
|
.uc{display:flex;align-items:center;gap:8px;background:rgba(255,255,255,0.04);border:1px solid var(--border);padding:6px 14px;border-radius:20px;font-size:12px;font-weight:600}
|
|
.logout{background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.2);color:var(--red);padding:5px 12px;border-radius:8px;font-family:'Sora',sans-serif;font-size:11px;font-weight:700;cursor:pointer;transition:all 0.2s}
|
|
.logout:hover{background:rgba(239,68,68,0.2)}
|
|
|
|
/* PAGES */
|
|
.page{display:none;flex:1;height:calc(100vh - 60px)}
|
|
.page.active{display:flex}
|
|
|
|
/* === OVERVIEW === */
|
|
.overview{width:100%;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:20px}
|
|
|
|
/* KPI ROW */
|
|
.kpi-row{display:grid;grid-template-columns:repeat(6,1fr);gap:12px}
|
|
.kpi{
|
|
background:var(--card2);border:1px solid var(--border);
|
|
border-radius:14px;padding:16px 14px;
|
|
position:relative;overflow:hidden;
|
|
transition:border-color 0.2s,transform 0.2s;
|
|
}
|
|
.kpi:hover{border-color:var(--accent);transform:translateY(-2px)}
|
|
.kpi::before{
|
|
content:'';position:absolute;top:0;left:0;right:0;height:2px;
|
|
background:var(--kpi-color,var(--accent));
|
|
}
|
|
.kpi-icon{font-size:22px;margin-bottom:8px}
|
|
.kpi-num{font-size:26px;font-weight:800;line-height:1;margin-bottom:4px;color:var(--kpi-color,var(--text))}
|
|
.kpi-label{font-size:9px;font-weight:800;color:var(--text2);text-transform:uppercase;letter-spacing:0.6px}
|
|
.kpi-sub{font-size:10px;color:var(--text2);margin-top:3px}
|
|
|
|
/* CHARTS GRID */
|
|
.charts-row{display:grid;grid-template-columns:2fr 1fr;gap:16px}
|
|
.charts-row-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}
|
|
.chart-card{
|
|
background:var(--card2);border:1px solid var(--border);
|
|
border-radius:16px;padding:20px;
|
|
}
|
|
.cc-title{font-size:13px;font-weight:800;margin-bottom:4px}
|
|
.cc-sub{font-size:11px;color:var(--text2);margin-bottom:16px}
|
|
.chart-wrap{position:relative}
|
|
|
|
/* KECAMATAN TABLE */
|
|
.kec-table{width:100%;border-collapse:collapse}
|
|
.kec-table th{padding:8px 12px;text-align:left;font-size:9px;font-weight:800;color:var(--text2);letter-spacing:0.5px;border-bottom:1px solid var(--border);background:rgba(255,255,255,0.02)}
|
|
.kec-table td{padding:10px 12px;font-size:11px;border-bottom:1px solid rgba(255,255,255,0.04)}
|
|
.kec-table tr:hover td{background:rgba(59,130,246,0.04)}
|
|
.bar-cell{display:flex;align-items:center;gap:8px}
|
|
.bar{height:6px;border-radius:3px;background:linear-gradient(90deg,var(--accent),var(--accent2));min-width:4px;transition:width 0.5s}
|
|
|
|
/* BANTUAN STREAM */
|
|
.stream{display:flex;flex-direction:column;gap:8px;max-height:320px;overflow-y:auto}
|
|
.stream-item{
|
|
background:rgba(16,185,129,0.05);border:1px solid rgba(16,185,129,0.12);
|
|
border-radius:10px;padding:10px 12px;
|
|
display:flex;align-items:flex-start;gap:10px;
|
|
}
|
|
.si-dot{width:8px;height:8px;border-radius:50%;background:var(--green);margin-top:3px;flex-shrink:0}
|
|
.si-body{flex:1}
|
|
.si-title{font-size:11px;font-weight:700;margin-bottom:2px}
|
|
.si-meta{font-size:10px;color:var(--text2)}
|
|
|
|
/* BADGE */
|
|
.badge{padding:2px 8px;border-radius:5px;font-size:9px;font-weight:800;letter-spacing:0.3px}
|
|
.bb{background:rgba(59,130,246,.15);color:#93c5fd}
|
|
.bg{background:rgba(16,185,129,.15);color:#6ee7b7}
|
|
.ba{background:rgba(245,158,11,.15);color:#fcd34d}
|
|
.br{background:rgba(239,68,68,.15);color:#fca5a5}
|
|
.bp{background:rgba(139,92,246,.15);color:#c4b5fd}
|
|
.bgr{background:rgba(255,255,255,.07);color:var(--text2)}
|
|
|
|
/* === MAP PAGE === */
|
|
.map-layout{display:flex;width:100%}
|
|
.map-panel{
|
|
width:320px;min-width:320px;
|
|
background:var(--surface);border-right:1px solid var(--border);
|
|
display:flex;flex-direction:column;overflow-y:auto;
|
|
}
|
|
.map-main{flex:1;position:relative}
|
|
#map{width:100%;height:100%}
|
|
.map-filter{padding:14px;border-bottom:1px solid var(--border)}
|
|
.map-filter label{font-size:10px;font-weight:800;color:var(--text2);display:block;margin-bottom:5px;letter-spacing:0.5px}
|
|
.map-filter select{width:100%;background:rgba(255,255,255,0.04);border:1px solid var(--border);color:var(--text);padding:8px 10px;border-radius:9px;font-family:'Sora',sans-serif;font-size:11px;outline:none;margin-bottom:8px}
|
|
|
|
/* === LAPORAN PAGE === */
|
|
.laporan-overview{width:100%;overflow-y:auto;padding:24px}
|
|
|
|
/* === PENDUDUK PAGE === */
|
|
.penduduk-page{width:100%;overflow-y:auto;padding:24px}
|
|
.ptable-wrap{background:var(--card2);border:1px solid var(--border);border-radius:14px;overflow:hidden;margin-top:16px}
|
|
.ptable{width:100%;border-collapse:collapse}
|
|
.ptable th{padding:10px 14px;text-align:left;font-size:9px;font-weight:800;color:var(--text2);letter-spacing:0.5px;border-bottom:1px solid var(--border);background:rgba(255,255,255,0.02)}
|
|
.ptable td{padding:10px 14px;font-size:11px;border-bottom:1px solid rgba(255,255,255,0.04);color:var(--text2)}
|
|
.ptable td:first-child{font-weight:700;color:var(--text)}
|
|
.ptable tr:hover td{background:rgba(59,130,246,0.03)}
|
|
|
|
/* FILTER ROW */
|
|
.filter-row{display:flex;gap:10px;flex-wrap:wrap}
|
|
.fi{background:rgba(255,255,255,0.04);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:9px;font-family:'Sora',sans-serif;font-size:11px;outline:none}
|
|
.fi:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--glow)}
|
|
.search-fi{flex:1;min-width:200px}
|
|
|
|
/* PULSE DOT */
|
|
.pulse{
|
|
display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--green);
|
|
box-shadow:0 0 0 0 rgba(16,185,129,0.4);
|
|
animation:pulse 2s infinite;
|
|
}
|
|
@keyframes pulse{
|
|
0%{box-shadow:0 0 0 0 rgba(16,185,129,0.4)}
|
|
70%{box-shadow:0 0 0 8px rgba(16,185,129,0)}
|
|
100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}
|
|
}
|
|
|
|
/* SCROLL */
|
|
::-webkit-scrollbar{width:4px;height:4px}
|
|
::-webkit-scrollbar-track{background:transparent}
|
|
::-webkit-scrollbar-thumb{background:rgba(99,179,237,0.2);border-radius:4px}
|
|
|
|
@media(max-width:1100px){
|
|
.kpi-row{grid-template-columns:repeat(3,1fr)}
|
|
.charts-row{grid-template-columns:1fr}
|
|
.charts-row-3{grid-template-columns:1fr 1fr}
|
|
}
|
|
@keyframes mi{from{opacity:0;transform:scale(.96)}}
|
|
@media(max-width:700px){
|
|
.kpi-row{grid-template-columns:repeat(2,1fr)}
|
|
.topbar-nav{display:none}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- TOP BAR -->
|
|
<div class="topbar">
|
|
<div class="brand">
|
|
<div class="brand-icon">🏛️</div>
|
|
<div>
|
|
<div class="brand-name">Dashboard Pemerintah</div>
|
|
<div class="brand-sub">KOTA PONTIANAK · SISTEM PEMANTAUAN KEMISKINAN</div>
|
|
</div>
|
|
<div class="ro-badge">READ ONLY</div>
|
|
</div>
|
|
<div class="topbar-nav">
|
|
<button class="nt active" onclick="showPage('overview',this)">📊 Overview</button>
|
|
<button class="nt" onclick="showPage('peta',this)">🗺️ Peta GIS</button>
|
|
<button class="nt" onclick="showPage('penduduk',this)">👥 Penduduk</button>
|
|
<button class="nt" onclick="showPage('bantuan',this)">🤝 Bantuan</button>
|
|
<button class="nt" onclick="showPage('laporan',this)">📢 Laporan</button>
|
|
</div>
|
|
<div class="topbar-right">
|
|
<div style="display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text2)">
|
|
<div class="pulse"></div> Live Update
|
|
</div>
|
|
<div id="clock" style="font-size:11px;color:var(--text2);font-weight:700;background:rgba(255,255,255,0.04);padding:5px 12px;border-radius:8px;border:1px solid var(--border)"></div>
|
|
<div class="uc">🏛️ <?= htmlspecialchars($user['nama']) ?></div>
|
|
<button class="logout" onclick="location.href='../api/auth.php?action=logout'">Keluar</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======== PAGE: OVERVIEW ======== -->
|
|
<div class="page active" id="page-overview">
|
|
<div class="overview">
|
|
|
|
<!-- KPI ROW -->
|
|
<div class="kpi-row">
|
|
<div class="kpi" style="--kpi-color:var(--accent)">
|
|
<div class="kpi-icon">🏘️</div>
|
|
<div class="kpi-num" id="k-kk">—</div>
|
|
<div class="kpi-label">Total KK Terdaftar</div>
|
|
<div class="kpi-sub" id="k-kk-sub">memuat...</div>
|
|
</div>
|
|
<div class="kpi" style="--kpi-color:var(--accent2)">
|
|
<div class="kpi-icon">👥</div>
|
|
<div class="kpi-num" id="k-jiwa">—</div>
|
|
<div class="kpi-label">Total Jiwa</div>
|
|
<div class="kpi-sub">dalam database</div>
|
|
</div>
|
|
<div class="kpi" style="--kpi-color:var(--red)">
|
|
<div class="kpi-icon">⚠️</div>
|
|
<div class="kpi-num" id="k-sm">—</div>
|
|
<div class="kpi-label">Sangat Miskin</div>
|
|
<div class="kpi-sub" id="k-sm-pct">memuat...</div>
|
|
</div>
|
|
<div class="kpi" style="--kpi-color:var(--amber)">
|
|
<div class="kpi-icon">🟠</div>
|
|
<div class="kpi-num" id="k-miskin">—</div>
|
|
<div class="kpi-label">Miskin</div>
|
|
<div class="kpi-sub" id="k-miskin-pct">—</div>
|
|
</div>
|
|
<div class="kpi" style="--kpi-color:var(--green)">
|
|
<div class="kpi-icon">🤝</div>
|
|
<div class="kpi-num" id="k-bantuan">—</div>
|
|
<div class="kpi-label">Total Bantuan</div>
|
|
<div class="kpi-sub" id="k-bantuan-sub">memuat...</div>
|
|
</div>
|
|
<div class="kpi" style="--kpi-color:var(--purple)">
|
|
<div class="kpi-icon">🕌</div>
|
|
<div class="kpi-num" id="k-ibadah">—</div>
|
|
<div class="kpi-label">Rumah Ibadah Aktif</div>
|
|
<div class="kpi-sub">berkontribusi</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CHART ROW 1 -->
|
|
<div class="charts-row">
|
|
<div class="chart-card">
|
|
<div class="cc-title">📊 Distribusi Status Ekonomi per Kecamatan</div>
|
|
<div class="cc-sub">Jumlah KK berdasarkan tingkat kemiskinan di tiap kecamatan</div>
|
|
<div class="chart-wrap"><canvas id="chart-kecamatan" height="160"></canvas></div>
|
|
</div>
|
|
<div class="chart-card">
|
|
<div class="cc-title">🥧 Komposisi Status Ekonomi</div>
|
|
<div class="cc-sub">Persentase seluruh KK terdaftar</div>
|
|
<div class="chart-wrap"><canvas id="chart-donut" height="180"></canvas></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CHART ROW 2 -->
|
|
<div class="charts-row-3">
|
|
<div class="chart-card">
|
|
<div class="cc-title">🤝 Bantuan per Jenis</div>
|
|
<div class="cc-sub">Distribusi berdasarkan kategori</div>
|
|
<canvas id="chart-bantuan-jenis" height="180"></canvas>
|
|
</div>
|
|
<div class="chart-card">
|
|
<div class="cc-title">🕌 Kontribusi Rumah Ibadah</div>
|
|
<div class="cc-sub">Jumlah bantuan per rumah ibadah</div>
|
|
<canvas id="chart-ibadah-kontrib" height="180"></canvas>
|
|
</div>
|
|
<div class="chart-card">
|
|
<div class="cc-title">📢 Status Laporan Masyarakat</div>
|
|
<div class="cc-sub">Laporan masuk dan penyelesaian</div>
|
|
<canvas id="chart-laporan" height="180"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BOTTOM ROW -->
|
|
<div class="charts-row">
|
|
<!-- Per Kecamatan Table -->
|
|
<div class="chart-card">
|
|
<div class="cc-title">📍 Rekap per Kecamatan</div>
|
|
<div class="cc-sub">Perbandingan data antar kecamatan</div>
|
|
<div style="overflow-x:auto">
|
|
<table class="kec-table" id="kec-table">
|
|
<thead><tr>
|
|
<th>KECAMATAN</th><th>KK</th><th>JIWA</th>
|
|
<th>SANGAT MISKIN</th><th>MISKIN</th><th>RENTAN</th><th>BANTUAN</th>
|
|
</tr></thead>
|
|
<tbody id="kec-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<!-- Live stream bantuan -->
|
|
<div class="chart-card">
|
|
<div class="cc-title">⚡ Bantuan Terbaru</div>
|
|
<div class="cc-sub">Real-time penyaluran bantuan</div>
|
|
<div class="stream" id="live-stream">
|
|
<div style="text-align:center;padding:20px;color:var(--text2);font-size:12px">⏳ Memuat...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======== PAGE: PETA ======== -->
|
|
<div class="page" id="page-peta">
|
|
<div class="map-panel">
|
|
<div class="map-filter">
|
|
<label>STATUS EKONOMI</label>
|
|
<select id="mp-ekonomi" onchange="loadPetaPenduduk()">
|
|
<option value="all">Semua</option>
|
|
<option value="sangat_miskin">🔴 Sangat Miskin</option>
|
|
<option value="miskin">🟠 Miskin</option>
|
|
<option value="rentan">🟡 Rentan</option>
|
|
</select>
|
|
<label>KECAMATAN</label>
|
|
<select id="mp-kecamatan" onchange="loadPetaPenduduk()">
|
|
<option value="">Semua Kecamatan</option>
|
|
</select>
|
|
<label>TAMPILAN</label>
|
|
<div style="display:flex;gap:6px">
|
|
<button onclick="toggleML('ibadah')" style="flex:1;padding:6px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text2);border-radius:8px;cursor:pointer;font-family:'Sora',sans-serif;font-size:10px;font-weight:700">🕌 Ibadah</button>
|
|
<button onclick="toggleML('radius')" style="flex:1;padding:6px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text2);border-radius:8px;cursor:pointer;font-family:'Sora',sans-serif;font-size:10px;font-weight:700">⭕ Radius</button>
|
|
<button onclick="map.setView([-0.0263,109.3425],12)" style="flex:1;padding:6px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text2);border-radius:8px;cursor:pointer;font-family:'Sora',sans-serif;font-size:10px;font-weight:700">🔄 Reset</button>
|
|
</div>
|
|
</div>
|
|
<div style="padding:12px 14px;border-bottom:1px solid var(--border)">
|
|
<div style="font-size:10px;font-weight:800;color:var(--text2);margin-bottom:8px;letter-spacing:0.5px">📍 LEGENDA</div>
|
|
<div style="font-size:10px;color:var(--text2);line-height:2">
|
|
<div>⚠️ Sangat Miskin 🟠 Miskin 🟡 Rentan</div>
|
|
<div>🕌 Masjid ⛪ Gereja 🏯 Vihara 🛕 Pura</div>
|
|
<div>⭕ Radius layanan binaan</div>
|
|
</div>
|
|
</div>
|
|
<div style="padding:12px 14px;overflow-y:auto;flex:1">
|
|
<div style="font-size:10px;font-weight:800;color:var(--text2);margin-bottom:8px;letter-spacing:0.5px">🕌 RUMAH IBADAH</div>
|
|
<div id="map-ibadah-list" style="display:flex;flex-direction:column;gap:6px"></div>
|
|
</div>
|
|
</div>
|
|
<div class="map-main"><div id="map"></div></div>
|
|
</div>
|
|
|
|
<!-- ======== PAGE: PENDUDUK ======== -->
|
|
<div class="page" id="page-penduduk">
|
|
<div class="penduduk-page">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
<div>
|
|
<div style="font-size:18px;font-weight:800">👥 Data Penduduk</div>
|
|
<div style="font-size:12px;color:var(--text2);margin-top:3px">Semua KK terdaftar dalam sistem (read-only)</div>
|
|
</div>
|
|
<button onclick="exportCSV()" style="padding:8px 16px;background:linear-gradient(135deg,var(--green),#059669);border:none;color:#fff;border-radius:9px;font-family:'Sora',sans-serif;font-size:11px;font-weight:700;cursor:pointer">📥 Export CSV</button>
|
|
</div>
|
|
<div class="filter-row" style="margin-bottom:12px">
|
|
<input type="text" id="q-penduduk" class="fi search-fi" placeholder="🔍 Cari nama / alamat / kelurahan..." oninput="filterTable()">
|
|
<select id="q-ekonomi" class="fi" onchange="loadPendudukFull()">
|
|
<option value="">Semua Ekonomi</option>
|
|
<option value="sangat_miskin">Sangat Miskin</option>
|
|
<option value="miskin">Miskin</option>
|
|
<option value="rentan">Rentan</option>
|
|
<option value="mampu">Mampu</option>
|
|
</select>
|
|
<select id="q-kecamatan" class="fi" onchange="loadPendudukFull()">
|
|
<option value="">Semua Kecamatan</option>
|
|
</select>
|
|
</div>
|
|
<div class="ptable-wrap">
|
|
<table class="ptable">
|
|
<thead><tr>
|
|
<th>#</th><th>KEPALA KELUARGA</th><th>NO. KK</th>
|
|
<th>ALAMAT</th><th>KECAMATAN</th>
|
|
<th>JIWA</th><th>STATUS EKONOMI</th>
|
|
<th>RUMAH IBADAH</th><th>BANTUAN</th><th>AKSI</th>
|
|
</tr></thead>
|
|
<tbody id="p-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div id="p-paging" style="display:flex;align-items:center;justify-content:space-between;padding:12px 0;font-size:11px;color:var(--text2)"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======== PAGE: BANTUAN ======== -->
|
|
<div class="page" id="page-bantuan">
|
|
<div class="overview">
|
|
<div style="font-size:18px;font-weight:800;margin-bottom:4px">🤝 Penyaluran Bantuan</div>
|
|
<div style="font-size:12px;color:var(--text2);margin-bottom:16px">Transparansi bantuan yang disalurkan oleh rumah ibadah dan pemerintah</div>
|
|
|
|
<!-- Stats -->
|
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px">
|
|
<div class="kpi" style="--kpi-color:var(--green)"><div class="kpi-icon">🤝</div><div class="kpi-num" id="bt-total">—</div><div class="kpi-label">Total Penyaluran</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--accent)"><div class="kpi-icon">💰</div><div class="kpi-num" id="bt-nominal">—</div><div class="kpi-label">Total Nominal (Rp)</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--amber)"><div class="kpi-icon">🏛️</div><div class="kpi-num" id="bt-pem">—</div><div class="kpi-label">Dari Pemerintah</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--purple)"><div class="kpi-icon">🕌</div><div class="kpi-num" id="bt-ibadah">—</div><div class="kpi-label">Dari Rumah Ibadah</div></div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div style="background:var(--card2);border:1px solid var(--border);border-radius:14px;overflow:hidden">
|
|
<div style="padding:14px 18px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between">
|
|
<div style="font-size:13px;font-weight:800">📋 Histori Lengkap Bantuan</div>
|
|
<div style="display:flex;gap:8px">
|
|
<select id="bt-filter-sumber" onchange="loadBantuanFull()" class="fi" style="font-size:10px">
|
|
<option value="">Semua Sumber</option>
|
|
<option value="pemerintah">Pemerintah</option>
|
|
<option value="rumah_ibadah">Rumah Ibadah</option>
|
|
<option value="donatur">Donatur</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<table class="ptable">
|
|
<thead><tr>
|
|
<th>PENERIMA</th><th>JENIS BANTUAN</th><th>SUMBER</th>
|
|
<th>RUMAH IBADAH</th><th>MITRA</th><th>TANGGAL</th>
|
|
<th>BENTUK</th><th>NOMINAL</th><th>STATUS</th>
|
|
</tr></thead>
|
|
<tbody id="bt-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======== PAGE: LAPORAN ======== -->
|
|
<div class="page" id="page-laporan">
|
|
<div class="overview">
|
|
<div style="font-size:18px;font-weight:800;margin-bottom:4px">📢 Laporan Masyarakat</div>
|
|
<div style="font-size:12px;color:var(--text2);margin-bottom:16px">Semua laporan yang dikirim masyarakat beserta status penanganannya</div>
|
|
|
|
<!-- Stats laporan -->
|
|
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-bottom:20px">
|
|
<div class="kpi" style="--kpi-color:var(--text2)"><div class="kpi-icon">📢</div><div class="kpi-num" id="lp-total">—</div><div class="kpi-label">Total Laporan</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--amber)"><div class="kpi-icon">⏳</div><div class="kpi-num" id="lp-pending">—</div><div class="kpi-label">Pending</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--accent)"><div class="kpi-icon">🔄</div><div class="kpi-num" id="lp-proses">—</div><div class="kpi-label">Diproses</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--green)"><div class="kpi-icon">✅</div><div class="kpi-num" id="lp-setuju">—</div><div class="kpi-label">Disetujui</div></div>
|
|
<div class="kpi" style="--kpi-color:var(--red)"><div class="kpi-icon">❌</div><div class="kpi-num" id="lp-tolak">—</div><div class="kpi-label">Ditolak</div></div>
|
|
</div>
|
|
|
|
<div style="background:var(--card2);border:1px solid var(--border);border-radius:14px;overflow:hidden">
|
|
<div style="padding:14px 18px;border-bottom:1px solid var(--border);display:flex;gap:8px">
|
|
<select id="lp-filter-status" onchange="loadLaporanFull()" class="fi" style="font-size:10px">
|
|
<option value="">Semua Status</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="diproses">Diproses</option>
|
|
<option value="disetujui">Disetujui</option>
|
|
<option value="ditolak">Ditolak</option>
|
|
</select>
|
|
<select id="lp-filter-jenis" onchange="loadLaporanFull()" class="fi" style="font-size:10px">
|
|
<option value="">Semua Jenis</option>
|
|
<option value="kelahiran">Kelahiran</option>
|
|
<option value="kematian">Kematian</option>
|
|
<option value="pindah_masuk">Pindah Masuk</option>
|
|
<option value="pindah_keluar">Pindah Keluar</option>
|
|
</select>
|
|
</div>
|
|
<table class="ptable">
|
|
<thead><tr>
|
|
<th>JENIS</th><th>PELAPOR</th><th>TERKAIT</th>
|
|
<th>RUMAH IBADAH</th><th>TGL KEJADIAN</th>
|
|
<th>STATUS</th><th>DESKRIPSI</th>
|
|
</tr></thead>
|
|
<tbody id="lp-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Detail Keluarga (Anggota) -->
|
|
<div id="m-detail-kk-pem" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:500;align-items:center;justify-content:center;backdrop-filter:blur(4px)">
|
|
<div style="background:var(--card);border:1px solid var(--border);border-radius:18px;width:90%;max-width:680px;max-height:90vh;overflow-y:auto;box-shadow:0 24px 64px rgba(0,0,0,.6);animation:mi .3s cubic-bezier(.23,1,.32,1)">
|
|
<div style="padding:18px 22px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;background:var(--card);z-index:2">
|
|
<div style="font-size:15px;font-weight:800" id="pem-detail-title">Detail Keluarga</div>
|
|
<button onclick="document.getElementById('m-detail-kk-pem').style.display='none'" style="background:rgba(255,255,255,.04);border:none;color:var(--text2);width:28px;height:28px;border-radius:7px;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center">✕</button>
|
|
</div>
|
|
<div style="padding:18px 22px" id="pem-detail-body">
|
|
<div style="text-align:center;padding:30px;color:var(--text2)">⏳ Memuat data...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script>
|
|
// ── CLOCK ──
|
|
function updateClock(){
|
|
const now=new Date();
|
|
document.getElementById('clock').textContent=now.toLocaleString('id-ID',{weekday:'short',day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'});
|
|
}
|
|
updateClock();setInterval(updateClock,1000);
|
|
|
|
// ── PAGES ──
|
|
function showPage(name,el){
|
|
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
|
|
document.querySelectorAll('.nt').forEach(t=>t.classList.remove('active'));
|
|
document.getElementById('page-'+name).classList.add('active');
|
|
if(el)el.classList.add('active');
|
|
if(name==='peta'){loadMapData();initMap()}
|
|
if(name==='penduduk')loadPendudukFull();
|
|
if(name==='bantuan'){loadBantuanFull();loadBantuanStats()}
|
|
if(name==='laporan')loadLaporanFull();
|
|
}
|
|
|
|
// ── COLORS ──
|
|
const COLORS={
|
|
accent:'#3b82f6',accent2:'#06b6d4',green:'#10b981',
|
|
amber:'#f59e0b',red:'#ef4444',purple:'#8b5cf6',pink:'#ec4899'
|
|
};
|
|
const chartDefaults={
|
|
plugins:{legend:{labels:{color:'#7c93b5',font:{family:'Sora',size:10}}}},
|
|
scales:{
|
|
x:{ticks:{color:'#7c93b5',font:{family:'Sora',size:10}},grid:{color:'rgba(255,255,255,0.04)'}},
|
|
y:{ticks:{color:'#7c93b5',font:{family:'Sora',size:10}},grid:{color:'rgba(255,255,255,0.04)'}}
|
|
}
|
|
};
|
|
|
|
// ── LOAD OVERVIEW ──
|
|
let charts={};
|
|
async function loadOverview(){
|
|
const d=await fetch('../api/penduduk.php?action=dashboard').then(r=>r.json());
|
|
const bs=await fetch('../api/bantuan.php?action=statistik').then(r=>r.json());
|
|
|
|
const total=d.total_kk||0;
|
|
document.getElementById('k-kk').textContent=total.toLocaleString('id');
|
|
document.getElementById('k-kk-sub').textContent=`${d.total_jiwa||0} jiwa total`;
|
|
document.getElementById('k-jiwa').textContent=(d.total_jiwa||0).toLocaleString('id');
|
|
document.getElementById('k-sm').textContent=(d.sangat_miskin||0).toLocaleString('id');
|
|
document.getElementById('k-sm-pct').textContent=total?`${((d.sangat_miskin/total)*100).toFixed(1)}% dari total`:'';
|
|
document.getElementById('k-miskin').textContent=(d.miskin||0).toLocaleString('id');
|
|
document.getElementById('k-miskin-pct').textContent=total?`${((d.miskin/total)*100).toFixed(1)}% dari total`:'';
|
|
document.getElementById('k-bantuan').textContent=(bs.total_bantuan||0).toLocaleString('id');
|
|
document.getElementById('k-bantuan-sub').textContent=`Rp${((bs.total_nominal||0)/1e6).toFixed(1)}jt disalurkan`;
|
|
document.getElementById('k-ibadah').textContent=(d.total_ibadah||0);
|
|
|
|
// ── CHART: Per Kecamatan (stacked bar) ──
|
|
const kec=d.per_kecamatan||[];
|
|
if(charts.kecamatan)charts.kecamatan.destroy();
|
|
charts.kecamatan=new Chart(document.getElementById('chart-kecamatan'),{
|
|
type:'bar',
|
|
data:{
|
|
labels:kec.map(k=>k.kecamatan||'-'),
|
|
datasets:[
|
|
{label:'KK',data:kec.map(k=>k.jumlah_kk),backgroundColor:COLORS.accent+'cc',borderRadius:4},
|
|
{label:'Jiwa',data:kec.map(k=>k.jiwa),backgroundColor:COLORS.accent2+'99',borderRadius:4},
|
|
]
|
|
},
|
|
options:{...chartDefaults,responsive:true,plugins:{...chartDefaults.plugins,legend:{labels:{color:'#7c93b5',font:{family:'Sora',size:10}}}}}
|
|
});
|
|
|
|
// ── CHART: Donut status ekonomi ──
|
|
if(charts.donut)charts.donut.destroy();
|
|
charts.donut=new Chart(document.getElementById('chart-donut'),{
|
|
type:'doughnut',
|
|
data:{
|
|
labels:['Sangat Miskin','Miskin','Rentan'],
|
|
datasets:[{data:[d.sangat_miskin||0,d.miskin||0,d.rentan||0],backgroundColor:[COLORS.red+'dd',COLORS.amber+'dd',COLORS.purple+'dd'],borderWidth:0,hoverOffset:6}]
|
|
},
|
|
options:{responsive:true,cutout:'70%',plugins:{legend:{position:'bottom',labels:{color:'#7c93b5',font:{family:'Sora',size:10},padding:10}}}}
|
|
});
|
|
|
|
// ── CHART: Bantuan per jenis ──
|
|
const topJenis=(bs.per_jenis||[]).slice(0,6);
|
|
if(charts.bJenis)charts.bJenis.destroy();
|
|
charts.bJenis=new Chart(document.getElementById('chart-bantuan-jenis'),{
|
|
type:'bar',
|
|
data:{
|
|
labels:topJenis.map(j=>j.nama.substring(0,15)),
|
|
datasets:[{label:'Jumlah',data:topJenis.map(j=>j.jumlah),backgroundColor:COLORS.green+'cc',borderRadius:4}]
|
|
},
|
|
options:{...chartDefaults,indexAxis:'y',responsive:true,plugins:{legend:{display:false}}}
|
|
});
|
|
|
|
// ── CHART: Kontribusi ibadah ──
|
|
const topIbadah=(bs.per_ibadah||[]).slice(0,6);
|
|
if(charts.ibadah)charts.ibadah.destroy();
|
|
charts.ibadah=new Chart(document.getElementById('chart-ibadah-kontrib'),{
|
|
type:'bar',
|
|
data:{
|
|
labels:topIbadah.map(i=>i.nama.substring(0,18)),
|
|
datasets:[{label:'Bantuan',data:topIbadah.map(i=>i.jumlah),backgroundColor:COLORS.purple+'cc',borderRadius:4}]
|
|
},
|
|
options:{...chartDefaults,indexAxis:'y',responsive:true,plugins:{legend:{display:false}}}
|
|
});
|
|
|
|
// ── CHART: Laporan ──
|
|
const lp=await fetch('../api/laporan.php?action=list').then(r=>r.json()).catch(()=>[]);
|
|
const lpCount={pending:0,diproses:0,disetujui:0,ditolak:0};
|
|
lp.forEach(l=>{if(lpCount[l.status_verifikasi]!==undefined)lpCount[l.status_verifikasi]++});
|
|
if(charts.laporan)charts.laporan.destroy();
|
|
charts.laporan=new Chart(document.getElementById('chart-laporan'),{
|
|
type:'doughnut',
|
|
data:{
|
|
labels:['Pending','Diproses','Disetujui','Ditolak'],
|
|
datasets:[{data:Object.values(lpCount),backgroundColor:[COLORS.amber+'dd',COLORS.accent+'dd',COLORS.green+'dd',COLORS.red+'dd'],borderWidth:0}]
|
|
},
|
|
options:{responsive:true,cutout:'65%',plugins:{legend:{position:'bottom',labels:{color:'#7c93b5',font:{family:'Sora',size:10},padding:8}}}}
|
|
});
|
|
|
|
// ── KECAMATAN TABLE ──
|
|
const tbody=document.getElementById('kec-tbody');
|
|
const maxKK=Math.max(...kec.map(k=>k.jumlah_kk),1);
|
|
tbody.innerHTML=kec.map(k=>`<tr>
|
|
<td style="font-weight:700;color:var(--text)">${k.kecamatan}</td>
|
|
<td><div class="bar-cell"><div class="bar" style="width:${(k.jumlah_kk/maxKK*80)}px"></div>${k.jumlah_kk}</div></td>
|
|
<td>${k.jiwa}</td>
|
|
<td style="color:var(--red)">${k.sangat_miskin||0}</td>
|
|
<td style="color:var(--amber)">${k.miskin||0}</td>
|
|
<td style="color:var(--purple)">${k.rentan||0}</td>
|
|
<td style="color:var(--green)">${k.bantuan||0}</td>
|
|
</tr>`).join('');
|
|
|
|
// Populate kecamatan dropdowns
|
|
kec.forEach(k=>{
|
|
['mp-kecamatan','q-kecamatan'].forEach(id=>{
|
|
const sel=document.getElementById(id);
|
|
if(sel&&!Array.from(sel.options).find(o=>o.value===k.kecamatan))
|
|
sel.add(new Option(k.kecamatan,k.kecamatan));
|
|
});
|
|
});
|
|
|
|
// ── LIVE STREAM ──
|
|
loadLiveStream();
|
|
}
|
|
|
|
async function loadLiveStream(){
|
|
const data=await fetch('../api/bantuan.php?action=list').then(r=>r.json()).catch(()=>[]);
|
|
const el=document.getElementById('live-stream');
|
|
if(!data.length){el.innerHTML='<div style="text-align:center;padding:20px;color:var(--text2);font-size:11px">Belum ada bantuan</div>';return}
|
|
el.innerHTML=data.slice(0,10).map(b=>`
|
|
<div class="stream-item">
|
|
<div class="si-dot"></div>
|
|
<div class="si-body">
|
|
<div class="si-title">${b.nama_kepala_keluarga} · ${b.jenis_bantuan}</div>
|
|
<div class="si-meta">📦 ${b.bentuk_bantuan.substring(0,50)} · 🕌 ${b.nama_ibadah||b.sumber_bantuan} · ${fDate(b.tanggal_bantuan)}</div>
|
|
</div>
|
|
</div>`).join('');
|
|
}
|
|
|
|
// ── MAP ──
|
|
let mapInited=false;let mmap;
|
|
const MLG={ibadah:null,penduduk:null,radius:null};const MLV={ibadah:true,radius:true};
|
|
function initMap(){
|
|
if(mapInited)return;mapInited=true;
|
|
mmap=L.map('map',{center:[-0.0263,109.3425],zoom:12,zoomControl:false});
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:'© OpenStreetMap',maxZoom:19}).addTo(mmap);
|
|
L.control.zoom({position:'bottomright'}).addTo(mmap);
|
|
MLG.ibadah=L.layerGroup().addTo(mmap);
|
|
MLG.penduduk=L.layerGroup().addTo(mmap);
|
|
MLG.radius=L.layerGroup().addTo(mmap);
|
|
loadMapData();
|
|
}
|
|
function toggleML(k){if(MLV[k]){mmap.removeLayer(MLG[k]);MLV[k]=false}else{mmap.addLayer(MLG[k]);MLV[k]=true}}
|
|
function mkIco(c,e){return L.divIcon({className:'',html:`<div style="width:28px;height:28px;background:${c};border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;box-shadow:0 2px 8px rgba(0,0,0,.5);border:2px solid rgba(255,255,255,.2)">${e}</div>`,iconSize:[28,28],iconAnchor:[14,14],popupAnchor:[0,-16]})}
|
|
const MICO={masjid:mkIco('#3b82f6','🕌'),gereja:mkIco('#8b5cf6','⛪'),vihara:mkIco('#f59e0b','🏯'),pura:mkIco('#10b981','🛕'),lainnya:mkIco('#64748b','🏢'),sangat_miskin:mkIco('#ef4444','⚠️'),miskin:mkIco('#f97316','🟠'),rentan:mkIco('#eab308','🟡'),mampu:mkIco('#22c55e','🟢')};
|
|
function iico(j){return MICO[(j||'').toLowerCase()]||MICO.lainnya}
|
|
function pico(se){return MICO[se]||MICO.miskin}
|
|
|
|
async function loadMapData(){
|
|
if(!mapInited)return;
|
|
// Ibadah
|
|
const gj=await fetch('../api/rumah_ibadah.php?action=geojson').then(r=>r.json());
|
|
MLG.ibadah.clearLayers();MLG.radius.clearLayers();
|
|
const ilist=document.getElementById('map-ibadah-list');ilist.innerHTML='';
|
|
(gj.features||[]).forEach(f=>{
|
|
const p=f.properties,[lng,lat]=f.geometry.coordinates;
|
|
L.circle([lat,lng],{radius:p.radius,color:'#3b82f6',fillColor:'#3b82f6',fillOpacity:.04,weight:1,dashArray:'5,5'}).addTo(MLG.radius);
|
|
L.marker([lat,lng],{icon:iico(p.jenis)}).bindPopup(`<b>${p.nama}</b><br><small>${p.jenis} · KK: ${p.jumlah_kk} · Bantuan: ${p.jumlah_bantuan}</small>`).addTo(MLG.ibadah);
|
|
ilist.innerHTML+=`<div onclick="mmap.flyTo([${lat},${lng}],15)" style="padding:8px 10px;background:rgba(255,255,255,.03);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all .15s;margin-bottom:4px" onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<div style="font-size:11px;font-weight:700">${p.nama}</div>
|
|
<div style="font-size:10px;color:var(--text2)">${p.jenis} · ${p.jumlah_kk} KK · ${p.jumlah_bantuan} bantuan</div>
|
|
</div>`;
|
|
});
|
|
loadPetaPenduduk();
|
|
}
|
|
|
|
async function loadPetaPenduduk(){
|
|
if(!mapInited)return;
|
|
const se=document.getElementById('mp-ekonomi').value;
|
|
const kec=document.getElementById('mp-kecamatan').value;
|
|
let url=`../api/penduduk.php?action=geojson`;
|
|
if(se!=='all')url+=`&status_ekonomi=${se}`;
|
|
if(kec)url+=`&kecamatan=${encodeURIComponent(kec)}`;
|
|
const gj=await fetch(url).then(r=>r.json());
|
|
MLG.penduduk.clearLayers();
|
|
(gj.features||[]).forEach(f=>{
|
|
const p=f.properties,[lng,lat]=f.geometry.coordinates;
|
|
L.marker([lat,lng],{icon:pico(p.status_ekonomi)}).bindPopup(`
|
|
<b>${p.nama_kepala_keluarga}</b><br>
|
|
<span style="font-size:11px">📍 ${p.alamat||'-'}<br>👥 ${p.jumlah_anggota} jiwa · ${seBadge(p.status_ekonomi)}<br>🕌 ${p.nama_ibadah||'-'}<br>🤝 ${p.jumlah_bantuan||0}x bantuan</span>
|
|
`).addTo(MLG.penduduk);
|
|
});
|
|
}
|
|
|
|
// ── PENDUDUK FULL TABLE ──
|
|
let pAll=[];let pPage=1;const pPerPage=30;
|
|
async function loadPendudukFull(){
|
|
const se=document.getElementById('q-ekonomi').value;
|
|
const kec=document.getElementById('q-kecamatan').value;
|
|
let url=`../api/penduduk.php?action=geojson`;
|
|
if(se)url+=`&status_ekonomi=${se}`;
|
|
if(kec)url+=`&kecamatan=${encodeURIComponent(kec)}`;
|
|
const gj=await fetch(url).then(r=>r.json());
|
|
pAll=gj.features||[];pPage=1;
|
|
renderPTable(pAll);
|
|
}
|
|
function filterTable(){
|
|
const q=document.getElementById('q-penduduk').value.toLowerCase();
|
|
const filtered=pAll.filter(f=>{const p=f.properties;return(p.nama_kepala_keluarga||'').toLowerCase().includes(q)||(p.alamat||'').toLowerCase().includes(q)||(p.kelurahan||'').toLowerCase().includes(q)});
|
|
renderPTable(filtered);
|
|
}
|
|
function renderPTable(data){
|
|
const start=(pPage-1)*pPerPage;const slice=data.slice(start,start+pPerPage);
|
|
const tbody=document.getElementById('p-tbody');
|
|
tbody.innerHTML=slice.map((f,i)=>{const p=f.properties;return`<tr>
|
|
<td style="color:var(--text2)">${start+i+1}</td>
|
|
<td>${p.nama_kepala_keluarga}</td>
|
|
<td style="font-size:10px;color:var(--text2)">${p.no_kk||'-'}</td>
|
|
<td style="color:var(--text2)">${p.alamat||'-'}</td>
|
|
<td>${p.kecamatan||'-'}</td>
|
|
<td style="text-align:center">${p.jumlah_anggota}</td>
|
|
<td>${seBadge(p.status_ekonomi)}</td>
|
|
<td style="font-size:10px;color:var(--text2)">${p.nama_ibadah||'-'}</td>
|
|
<td style="text-align:center;color:var(--green)">${p.jumlah_bantuan||0}x</td>
|
|
<td style="text-align:center"><button onclick="openDetailKKPem(${p.id})" style="padding:4px 10px;background:rgba(59,130,246,0.12);border:1px solid rgba(59,130,246,0.25);color:#93c5fd;border-radius:7px;cursor:pointer;font-family:'Sora',sans-serif;font-size:10px;font-weight:700">👥 Detail</button></td>
|
|
</tr>`}).join('');
|
|
const total=data.length;const pages=Math.ceil(total/pPerPage);
|
|
document.getElementById('p-paging').innerHTML=`
|
|
Menampilkan ${start+1}-${Math.min(start+pPerPage,total)} dari ${total} data
|
|
<div style="display:flex;gap:6px">
|
|
${pPage>1?`<button onclick="pPage--;loadPendudukFull()" style="padding:4px 10px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text2);border-radius:7px;cursor:pointer;font-family:'Sora',sans-serif;font-size:10px">← Prev</button>`:''}
|
|
<span style="padding:4px 10px">Hal ${pPage}/${pages}</span>
|
|
${pPage<pages?`<button onclick="pPage++;loadPendudukFull()" style="padding:4px 10px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text2);border-radius:7px;cursor:pointer;font-family:'Sora',sans-serif;font-size:10px">Next →</button>`:''}
|
|
</div>`;
|
|
}
|
|
|
|
// ── BANTUAN FULL ──
|
|
async function loadBantuanStats(){
|
|
const s=await fetch('../api/bantuan.php?action=statistik').then(r=>r.json());
|
|
document.getElementById('bt-total').textContent=(s.total_bantuan||0).toLocaleString('id');
|
|
const nom=s.total_nominal||0;
|
|
document.getElementById('bt-nominal').textContent=nom>1e6?`${(nom/1e6).toFixed(1)}jt`:nom.toLocaleString('id');
|
|
document.getElementById('bt-pem').textContent=(s.per_jenis||[]).filter(j=>j.sumber==='pemerintah').reduce((a,j)=>a+parseInt(j.jumlah),0);
|
|
document.getElementById('bt-ibadah').textContent=(s.per_jenis||[]).filter(j=>j.sumber==='rumah_ibadah').reduce((a,j)=>a+parseInt(j.jumlah),0);
|
|
}
|
|
async function loadBantuanFull(){
|
|
const sumber=document.getElementById('bt-filter-sumber').value;
|
|
const data=await fetch('../api/bantuan.php?action=list').then(r=>r.json()).catch(()=>[]);
|
|
const filtered=sumber?data.filter(b=>b.sumber_bantuan===sumber):data;
|
|
const tbody=document.getElementById('bt-tbody');
|
|
tbody.innerHTML=filtered.slice(0,100).map(b=>`<tr>
|
|
<td>${b.nama_kepala_keluarga}<br><span style="font-size:9px;color:var(--text2)">${b.kelurahan||''}</span></td>
|
|
<td><span class="badge bg">${b.jenis_bantuan.substring(0,20)}</span></td>
|
|
<td style="font-size:10px;color:var(--text2)">${b.sumber_bantuan}</td>
|
|
<td style="font-size:10px;color:var(--text2)">${b.nama_ibadah||'-'}</td>
|
|
<td style="font-size:10px;color:var(--text2)">${b.nama_mitra||'-'}</td>
|
|
<td style="font-size:10px">${fDate(b.tanggal_bantuan)}</td>
|
|
<td style="font-size:10px;color:var(--text2)">${b.bentuk_bantuan.substring(0,40)}</td>
|
|
<td style="color:var(--green);font-size:10px">${b.jumlah_nominal?'Rp'+Number(b.jumlah_nominal).toLocaleString('id'):'-'}</td>
|
|
<td>${svBadge(b.status_bantuan)}</td>
|
|
</tr>`).join('');
|
|
}
|
|
|
|
// ── LAPORAN FULL ──
|
|
async function loadLaporanFull(){
|
|
const status=document.getElementById('lp-filter-status').value;
|
|
const jenis=document.getElementById('lp-filter-jenis').value;
|
|
let url=`../api/laporan.php?action=list`;
|
|
if(status)url+=`&status=${status}`;
|
|
if(jenis)url+=`&jenis=${jenis}`;
|
|
const data=await fetch(url).then(r=>r.json()).catch(()=>[]);
|
|
const cnt={pending:0,diproses:0,disetujui:0,ditolak:0};
|
|
data.forEach(l=>{if(cnt[l.status_verifikasi]!==undefined)cnt[l.status_verifikasi]++});
|
|
document.getElementById('lp-total').textContent=data.length;
|
|
document.getElementById('lp-pending').textContent=cnt.pending;
|
|
document.getElementById('lp-proses').textContent=cnt.diproses;
|
|
document.getElementById('lp-setuju').textContent=cnt.disetujui;
|
|
document.getElementById('lp-tolak').textContent=cnt.ditolak;
|
|
const tbody=document.getElementById('lp-tbody');
|
|
tbody.innerHTML=data.slice(0,100).map(l=>`<tr>
|
|
<td>${jenisL(l.jenis_laporan)}</td>
|
|
<td style="font-size:10px">${l.nama_pelapor||'Anonim'}<br><span style="color:var(--text2)">${l.hp_pelapor||'-'}</span></td>
|
|
<td style="font-size:10px">${l.nama_terkait||l.nama_kepala_keluarga||'-'}</td>
|
|
<td style="font-size:10px;color:var(--text2)">${l.nama_ibadah||'-'}</td>
|
|
<td style="font-size:10px">${fDate(l.tanggal_kejadian)}</td>
|
|
<td>${svBadge(l.status_verifikasi)}</td>
|
|
<td style="font-size:10px;color:var(--text2);max-width:200px">${(l.deskripsi||'').substring(0,60)}${l.deskripsi&&l.deskripsi.length>60?'...':''}</td>
|
|
</tr>`).join('');
|
|
}
|
|
|
|
// ── EXPORT CSV ──
|
|
function exportCSV(){
|
|
const rows=[['No','Kepala Keluarga','No KK','Alamat','Kecamatan','Jiwa','Status Ekonomi','Rumah Ibadah','Bantuan']];
|
|
pAll.forEach((f,i)=>{const p=f.properties;rows.push([i+1,p.nama_kepala_keluarga,p.no_kk||'',p.alamat||'',p.kecamatan||'',p.jumlah_anggota,p.status_ekonomi,p.nama_ibadah||'',p.jumlah_bantuan||0])});
|
|
const csv=rows.map(r=>r.map(v=>`"${String(v).replace(/"/g,'""')}"`).join(',')).join('\n');
|
|
const a=document.createElement('a');
|
|
a.href='data:text/csv;charset=utf-8,\uFEFF'+encodeURIComponent(csv);
|
|
a.download=`penduduk_pontianak_${new Date().toISOString().split('T')[0]}.csv`;
|
|
a.click();
|
|
}
|
|
|
|
// ── DETAIL KK (Pemerintah) ──
|
|
async function openDetailKKPem(id){
|
|
const modal=document.getElementById('m-detail-kk-pem');
|
|
modal.style.display='flex';
|
|
document.getElementById('pem-detail-title').textContent='Detail Keluarga';
|
|
document.getElementById('pem-detail-body').innerHTML='<div style="text-align:center;padding:30px;color:var(--text2)">⏳ Memuat data...</div>';
|
|
const f=await fetch(`../api/penduduk.php?action=get&id=${id}`).then(r=>r.json()).catch(()=>null);
|
|
if(!f||!f.properties){document.getElementById('pem-detail-body').innerHTML='<p style="text-align:center;color:var(--red)">Gagal memuat data</p>';return}
|
|
const p=f.properties;
|
|
const anggota=p.anggota||[];
|
|
const bantuan=p.histori_bantuan||[];
|
|
document.getElementById('pem-detail-title').textContent=p.nama_kepala_keluarga;
|
|
document.getElementById('pem-detail-body').innerHTML=`
|
|
<div style="margin-bottom:14px">
|
|
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap">
|
|
${seBadge(p.status_ekonomi)}
|
|
${p.nama_ibadah?`<span class="badge bb">🕌 ${p.nama_ibadah}</span>`:''}
|
|
<span class="badge bgr">👁️ View Only</span>
|
|
</div>
|
|
<div style="font-size:11px;color:var(--text2);line-height:1.9">
|
|
📋 No. KK: <b style="color:var(--text)">${p.no_kk||'-'}</b><br>
|
|
📍 ${p.alamat||'-'}${p.rt?` · RT${p.rt}/RW${p.rw}`:''}<br>
|
|
${p.kelurahan?`🏘️ Kel. ${p.kelurahan}, Kec. ${p.kecamatan||'-'}<br>`:''}
|
|
🏠 Hunian: <b style="color:var(--text)">${(p.status_hunian||'-').replace(/_/g,' ')}</b>
|
|
${p.jarak_ke_ibadah?`<br>📏 Jarak ke ibadah: ${p.jarak_ke_ibadah}m`:''}
|
|
</div>
|
|
</div>
|
|
<div style="font-size:10px;font-weight:800;color:var(--text2);letter-spacing:0.5px;text-transform:uppercase;padding-bottom:8px;border-bottom:1px solid var(--border);margin-bottom:8px">👥 Anggota Keluarga (${anggota.length} jiwa)</div>
|
|
${anggota.length?anggota.map(a=>`
|
|
<div style="background:rgba(255,255,255,.03);border:1px solid var(--border);border-radius:9px;padding:10px;margin-bottom:7px">
|
|
<div style="display:flex;gap:9px;align-items:flex-start">
|
|
<div style="width:36px;height:36px;border-radius:9px;background:${a.jenis_kelamin==='L'?'rgba(59,130,246,0.15)':'rgba(236,72,153,0.15)'};display:flex;align-items:center;justify-content:center;font-size:17px;flex-shrink:0">${a.jenis_kelamin==='L'?'👦':'👧'}</div>
|
|
<div style="flex:1">
|
|
<div style="font-size:12px;font-weight:700;color:var(--text)">${a.nama} ${a.status_hidup&&a.status_hidup!=='hidup'?`<span style="padding:2px 6px;background:rgba(239,68,68,0.15);color:#fca5a5;border-radius:4px;font-size:9px;font-weight:800">${a.status_hidup}</span>`:''}</div>
|
|
<div style="font-size:10px;color:var(--text2);margin-top:2px">${a.hubungan} · ${a.umur||0} tahun · ${a.agama||'-'} · ${a.jenis_kelamin==='L'?'Laki-laki':'Perempuan'}</div>
|
|
${a.pendidikan_terakhir?`<div style="font-size:10px;color:var(--accent);margin-top:3px">📚 ${a.pendidikan_terakhir.jenjang}${a.pendidikan_terakhir.nama_institusi?' — '+a.pendidikan_terakhir.nama_institusi:''}${a.pendidikan_terakhir.status_pendidikan==='sedang_berjalan'?' (aktif)':''}</div>`:''}
|
|
${a.pekerjaan?`<div style="font-size:10px;color:var(--text2);margin-top:2px">💼 ${a.pekerjaan}</div>`:''}
|
|
${a.nik?`<div style="font-size:10px;color:var(--text2);margin-top:1px">🪪 NIK: ${a.nik}</div>`:''}
|
|
</div>
|
|
</div>
|
|
</div>`).join(''):'<div style="text-align:center;padding:12px;color:var(--text2);font-size:12px">Belum ada data anggota</div>'}
|
|
<div style="font-size:10px;font-weight:800;color:var(--text2);letter-spacing:0.5px;text-transform:uppercase;padding:10px 0 8px;border-top:1px solid var(--border);margin-top:8px">🤝 Riwayat Bantuan (${bantuan.length}x)</div>
|
|
${bantuan.length?bantuan.slice(0,5).map(b=>`
|
|
<div style="background:rgba(16,185,129,.05);border:1px solid rgba(16,185,129,.15);border-radius:9px;padding:10px;margin-bottom:7px">
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:4px">
|
|
<div style="font-size:12px;font-weight:700;color:var(--green)">${b.jenis_bantuan}</div>
|
|
<div style="font-size:10px;color:var(--text2)">${fDate(b.tanggal_bantuan)}</div>
|
|
</div>
|
|
<div style="font-size:11px;color:var(--text2)">📦 ${b.bentuk_bantuan}${b.jumlah_nominal?`<br>💰 Rp${Number(b.jumlah_nominal).toLocaleString('id')}`:''}</div>
|
|
<div style="margin-top:5px;padding:4px 8px;background:rgba(59,130,246,.06);border-radius:6px;font-size:10px;color:var(--text2)">🕌 ${b.nama_ibadah||b.sumber_bantuan||'-'}${b.nama_mitra?' + mitra: '+b.nama_mitra:''}</div>
|
|
</div>`).join(''):'<div style="text-align:center;padding:12px;color:var(--text2);font-size:12px">Belum ada bantuan tercatat</div>'}
|
|
<div style="margin-top:14px">
|
|
<button onclick="document.getElementById('m-detail-kk-pem').style.display='none'" style="width:100%;padding:9px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text2);border-radius:9px;font-family:'Sora',sans-serif;font-size:12px;font-weight:700;cursor:pointer">Tutup</button>
|
|
</div>`;
|
|
// Tutup modal saat klik luar
|
|
modal.onclick=e=>{if(e.target===modal)modal.style.display='none'};
|
|
}
|
|
|
|
// ── HELPERS ──
|
|
function seBadge(se){const m={sangat_miskin:'br',miskin:'ba',rentan:'bp',mampu:'bg'};const l={sangat_miskin:'Sangat Miskin',miskin:'Miskin',rentan:'Rentan',mampu:'Mampu'};return`<span class="badge ${m[se]||'bb'}">${l[se]||se}</span>`}
|
|
function svBadge(s){const m={pending:'ba',diproses:'bb',disetujui:'bg',ditolak:'br',disalurkan:'bg',ditunda:'ba',dibatalkan:'br'};return`<span class="badge ${m[s]||'bb'}">${s}</span>`}
|
|
function jenisL(j){const m={kelahiran:'🍼 Kelahiran',kematian:'⚰️ Kematian',pindah_masuk:'📦 Pindah Masuk',pindah_keluar:'🚚 Pindah Keluar',pernikahan:'💒 Pernikahan',perubahan_data:'📝 Perubahan Data',lainnya:'📌 Lainnya'};return m[j]||j}
|
|
function fDate(d){if(!d)return'-';return new Date(d).toLocaleDateString('id-ID',{day:'numeric',month:'short',year:'numeric'})}
|
|
|
|
// ── INIT ──
|
|
loadOverview();
|
|
setInterval(()=>{loadLiveStream();},60000);
|
|
</script>
|
|
</body>
|
|
</html>
|