Files
project-webgis/webgisdatakemiskinan/dashboard.php
T
2026-06-11 10:16:53 +07:00

1828 lines
118 KiB
PHP

<?php
session_start();
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['role'], ['admin', 'walikota'])) {
header("Location: login.php");
exit;
}
$admin_name = $_SESSION['username'] ?? 'User';
$role = $_SESSION['role'] ?? 'warga';
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin Dashboard — WebGIS Pemetaan Sosial</title>
<meta name="description" content="Panel administrasi WebGIS untuk manajemen data kemiskinan, distribusi bantuan, dan donasi.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@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://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
/* ─── DESIGN TOKENS (selaras dengan index.php) ─── */
:root {
--blue: #2563EB;
--blue-d: #1D4ED8;
--blue-l: #EFF6FF;
--blue-m: rgba(37,99,235,.09);
--navy: #0F172A;
--green: #10B981;
--green-d: #059669;
--green-l: #ECFDF5;
--red: #EF4444;
--red-l: #FEF2F2;
--amber: #F59E0B;
--amber-l: #FFFBEB;
--purple: #8B5CF6;
--purple-l:#F5F3FF;
--teal: #0891B2;
--teal-l: #ECFEFF;
--bg: #EBF0F7;
--surface: rgba(255,255,255,0.96);
--surface-s:#FFFFFF;
--border: #E2E8F0;
--border-l:#F1F5F9;
--text: #0F172A;
--text-2: #475569;
--text-3: #94A3B8;
--sh-sm: 0 1px 4px rgba(15,23,42,.06);
--sh: 0 4px 20px rgba(15,23,42,.08);
--sh-md: 0 8px 32px rgba(15,23,42,.12);
--r: 16px;
--r2: 12px;
--r3: 8px;
--sidebar-w: 64px;
--topbar-h: 58px;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;font-family:'Plus Jakarta Sans',sans-serif;overflow:hidden;background:var(--bg)}
#app{display:flex;height:100vh}
/* ── SIDEBAR ── */
#sidebar{
width:var(--sidebar-w);flex-shrink:0;
background:var(--surface-s);
display:flex;flex-direction:column;align-items:center;
padding:14px 0 12px;
border-right:1px solid var(--border);
z-index:100;gap:2px;
box-shadow:1px 0 12px rgba(15,23,42,0.06);
}
.sb-logo{
width:38px;height:38px;border-radius:11px;
background:linear-gradient(135deg,#2563EB,#1D4ED8);
display:flex;align-items:center;justify-content:center;
margin-bottom:16px;flex-shrink:0;
box-shadow:0 2px 8px rgba(37,99,235,0.35);
}
.sb-logo svg{display:block;}
.sb-divider{width:28px;height:1px;background:var(--border);margin:5px 0}
.nav-item{
position:relative;width:42px;height:42px;border-radius:var(--r3);
display:flex;align-items:center;justify-content:center;
cursor:pointer;transition:background .15s,color .15s;
color:var(--text-3);text-decoration:none;border:none;background:transparent;
}
.nav-item svg{display:block;flex-shrink:0;}
.nav-item:hover{background:var(--blue-m);color:var(--blue)}
.nav-item.active{background:var(--blue-m);color:var(--blue)}
.nav-item.active::before{
content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);
width:3px;height:20px;border-radius:0 3px 3px 0;background:var(--blue)
}
.nav-item.danger:hover{background:var(--red-l);color:var(--red)}
.ni-badge{
position:absolute;top:5px;right:5px;min-width:14px;height:14px;
border-radius:8px;background:var(--red);color:white;
font-size:8px;font-weight:700;display:flex;align-items:center;justify-content:center;
padding:0 3px;border:1.5px solid var(--surface-s)
}
.ni-badge.teal{background:var(--teal)}
.sb-spacer{flex:1}
.sb-user-av{
width:34px;height:34px;border-radius:50%;
background:linear-gradient(135deg,#2563EB,#1D4ED8);
display:flex;align-items:center;justify-content:center;
font-size:12px;font-weight:800;color:white;cursor:pointer;
transition:opacity .15s;letter-spacing:-0.5px;
}
.sb-user-av:hover{opacity:.8}
/* ── MAIN ── */
#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
#topbar{
height:var(--topbar-h);flex-shrink:0;
background:var(--surface-s);
border-bottom:1px solid var(--border);
display:flex;align-items:center;padding:0 20px;gap:10px;
box-shadow:0 1px 10px rgba(15,23,42,0.06);
}
.tb-breadcrumb{flex:1}
.tb-page{font-size:15px;font-weight:800;color:var(--text);letter-spacing:-0.3px}
.tb-sub{font-size:11px;color:var(--text-3);margin-top:1px}
.tb-search{position:relative;width:220px}
.tb-search input{
width:100%;padding:8px 12px 8px 34px;
border-radius:var(--r2);border:1.5px solid var(--border);
background:var(--bg);font-family:inherit;font-size:12px;color:var(--text);
transition:border-color .15s,background .15s
}
.tb-search input:focus{outline:none;border-color:var(--blue);background:white;box-shadow:0 0 0 3px var(--blue-m)}
.tb-search-icon{
position:absolute;left:10px;top:50%;transform:translateY(-50%);
color:var(--text-3);pointer-events:none;display:flex;
}
.tb-actions{display:flex;align-items:center;gap:6px}
.tb-btn{
display:flex;align-items:center;gap:5px;padding:7px 13px;
border-radius:var(--r3);border:none;font-family:inherit;
font-size:12px;font-weight:700;cursor:pointer;transition:all .15s;
}
.tb-btn.primary{background:var(--blue);color:white}.tb-btn.primary:hover{background:var(--blue-d)}
.tb-btn.ghost{background:transparent;color:var(--text-2);border:1.5px solid var(--border)}.tb-btn.ghost:hover{background:var(--bg)}
.tb-notif{
width:34px;height:34px;border-radius:var(--r3);
border:1.5px solid var(--border);background:transparent;cursor:pointer;
display:flex;align-items:center;justify-content:center;
color:var(--text-3);position:relative;transition:background .15s
}
.tb-notif:hover{background:var(--bg);color:var(--text-2)}
.notif-dot{position:absolute;top:5px;right:5px;width:7px;height:7px;border-radius:50%;background:var(--red);border:1.5px solid white}
/* ── CONTENT ── */
#content{flex:1;overflow-y:auto;padding:18px 20px;min-width:0}
#content::-webkit-scrollbar{width:4px}
#content::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.view{display:none}.view.active{display:block}
/* ── DASHBOARD GRID ── */
.dash-grid{display:grid;grid-template-columns:380px 1fr;gap:14px;height:calc(100vh - var(--topbar-h) - 36px)}
.dash-left{display:flex;flex-direction:column;gap:9px;overflow:hidden}
.dash-right{display:flex;flex-direction:column;gap:9px;overflow:hidden}
/* ── MINI STATS ── */
.mini-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;flex-shrink:0}
.mini-stat{
background:var(--surface-s);border-radius:var(--r2);padding:10px 11px;
border:1px solid var(--border-l);box-shadow:var(--sh-sm);
}
.mini-stat.accent{background:linear-gradient(135deg,var(--blue),var(--blue-d))}
.mini-stat.accent-teal{background:linear-gradient(135deg,var(--teal),#0E7490)}
.ms-icon{width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;margin-bottom:5px;}
.ms-lbl{font-size:9px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.06em}
.mini-stat.accent .ms-lbl,.mini-stat.accent-teal .ms-lbl{color:rgba(255,255,255,.65)}
.ms-num{font-size:18px;font-weight:800;color:var(--text);margin-top:2px}
.mini-stat.accent .ms-num,.mini-stat.accent-teal .ms-num{color:white}
/* ── SEARCH CARD ── */
.search-card{
background:var(--surface-s);border-radius:var(--r2);padding:11px 13px;
border:1px solid var(--border-l);box-shadow:var(--sh-sm);flex-shrink:0
}
.sc-input{position:relative;margin-bottom:9px}
.sc-input input{
width:100%;padding:8px 12px 8px 33px;border-radius:var(--r3);
border:1.5px solid var(--border);background:var(--bg);
font-family:inherit;font-size:12px;color:var(--text);transition:border-color .15s
}
.sc-input input:focus{outline:none;border-color:var(--blue);background:white;box-shadow:0 0 0 3px var(--blue-m)}
.sc-input-icon{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:var(--text-3);pointer-events:none;display:flex;}
.filter-row{display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.filter-chip{
display:flex;align-items:center;gap:4px;padding:4px 10px;border-radius:20px;
border:1.5px solid var(--border);background:var(--bg);
font-size:11px;font-weight:600;color:var(--text-2);cursor:pointer;transition:all .14s
}
.filter-chip.active,.filter-chip:hover{background:var(--blue-l);border-color:var(--blue);color:var(--blue)}
/* ── LIST SCROLL ── */
.list-scroll{overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:7px;padding-right:2px}
.list-scroll::-webkit-scrollbar{width:3px}
.list-scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.tracking-card{
background:var(--surface-s);border-radius:var(--r2);
border:1.5px solid var(--border-l);box-shadow:var(--sh-sm);
padding:13px 14px;cursor:pointer;transition:box-shadow .15s,border-color .15s;flex-shrink:0;
}
.tracking-card:hover{box-shadow:var(--sh);border-color:var(--border)}
.tracking-card.active-card{border-color:var(--blue);background:#FAFCFF;box-shadow:0 0 0 3px rgba(37,99,235,.08),var(--sh)}
.tc-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:9px}
.tc-id{font-size:11.5px;font-weight:700;color:var(--text-2)}
.tc-id span{color:var(--blue);font-size:12.5px}
.tc-header-right{display:flex;align-items:center;gap:5px}
.status-pill{font-size:10px;font-weight:700;padding:3px 9px;border-radius:20px}
.sp-blue{background:var(--blue);color:white}
.sp-amber{background:var(--amber-l);color:#B45309}
.sp-green{background:var(--green-l);color:#047857}
.sp-red{background:var(--red-l);color:#B91C1C}
.sp-teal{background:var(--teal-l);color:#0E7490}
.tc-more{
width:24px;height:24px;border:none;background:none;
cursor:pointer;color:var(--text-3);border-radius:6px;
display:flex;align-items:center;justify-content:center;transition:background .14s;font-size:16px;line-height:1;
}
.tc-more:hover{background:var(--bg)}
.tc-progress{height:4px;border-radius:4px;background:var(--border-l);overflow:hidden;margin-bottom:11px}
.tc-progress-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,var(--blue-d),var(--blue));transition:width .8s ease}
.tc-timeline{display:flex;flex-direction:column}
.tl-row{display:flex;align-items:center;gap:9px;position:relative}
.tl-left{display:flex;flex-direction:column;align-items:center;width:14px;flex-shrink:0}
.tl-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;z-index:1}
.tl-dot.filled{background:var(--blue)}
.tl-dot.empty{background:transparent;border:2px solid var(--border)}
.tl-line{width:1.5px;height:14px;background:var(--border-l)}
.tl-date{font-size:9px;color:var(--text-3);font-weight:600;width:52px;flex-shrink:0}
.tl-label{font-size:11.5px;font-weight:600;color:var(--text);flex:1}
.tl-time{font-size:10px;color:var(--text-3);font-weight:500;white-space:nowrap}
/* ── RIGHT PANEL HEADER ── */
.panel-header{
background:var(--surface-s);border-radius:var(--r2);padding:11px 15px;
border:1px solid var(--border-l);box-shadow:var(--sh-sm);flex-shrink:0
}
.ph-top{display:flex;align-items:center;justify-content:space-between}
.ph-eyebrow{font-size:10px;font-weight:600;color:var(--text-3);text-transform:uppercase;letter-spacing:.06em}
.ph-title{font-size:14px;font-weight:800;color:var(--text);margin-top:2px}
.ph-title span{color:var(--blue)}
/* ── MAP BLOCK ── */
.map-block{
background:var(--surface-s);border-radius:var(--r2);
border:1px solid var(--border-l);overflow:hidden;flex-shrink:0;box-shadow:var(--sh-sm)
}
.map-visual{width:100%;height:190px;background:linear-gradient(135deg,#EEF4FF 0%,#E0EDFF 60%,#D4E6FF 100%);position:relative;overflow:hidden}
.map-open-btn{
position:absolute;top:9px;right:9px;display:flex;align-items:center;gap:5px;
padding:5px 11px;border-radius:var(--r3);background:white;border:1px solid var(--border);
font-size:11px;font-weight:700;color:var(--blue);cursor:pointer;
box-shadow:var(--sh-sm);transition:box-shadow .14s;text-decoration:none;z-index:9999
}
.map-open-btn svg{flex-shrink:0;}
.map-open-btn:hover{box-shadow:var(--sh)}
.map-stats-row{display:flex;border-top:1px solid var(--border-l)}
.msr-item{flex:1;padding:9px 11px;display:flex;flex-direction:column;gap:1px;border-right:1px solid var(--border-l)}
.msr-item:last-child{border-right:none}
.msr-icon{display:flex;color:var(--text-3);}
.msr-icon svg{display:block;}
.msr-label{font-size:9px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.05em;margin-top:3px}
.msr-val{font-size:11.5px;font-weight:800;color:var(--text)}
/* ── TABS ── */
.tabs-card{
background:var(--surface-s);border-radius:var(--r2);
border:1px solid var(--border-l);box-shadow:var(--sh-sm);flex:1;overflow:hidden;display:flex;flex-direction:column
}
.tabs-nav{display:flex;border-bottom:1px solid var(--border-l);padding:0 10px;flex-shrink:0;overflow-x:auto}
.tabs-nav::-webkit-scrollbar{height:0}
.tab-btn{
padding:9px 11px;font-size:11px;font-weight:700;color:var(--text-3);cursor:pointer;
border-bottom:2px solid transparent;margin-bottom:-1px;transition:all .15s;white-space:nowrap;flex-shrink:0
}
.tab-btn.active{color:var(--blue);border-bottom-color:var(--blue)}
.tab-btn:hover:not(.active){color:var(--text-2)}
.tabs-body{flex:1;overflow-y:auto;padding:13px 15px}
.tabs-body::-webkit-scrollbar{width:3px}
.tabs-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.tab-pane{display:none}.tab-pane.active{display:block}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:11px}
.dg-item{background:var(--bg);border-radius:var(--r3);padding:9px 11px}
.dgi-label{font-size:9px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.07em;margin-bottom:3px}
.dgi-val{font-size:17px;font-weight:800;color:var(--text)}
.dgi-sub{font-size:10px;color:var(--text-3);margin-top:1px}
.cov-widget{background:var(--bg);border-radius:var(--r3);padding:11px 13px}
.cov-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
.cov-row-title{font-size:12px;font-weight:700;color:var(--text)}
.cov-pct-badge{font-size:14px;font-weight:800;color:var(--blue)}
.cov-bar{height:6px;border-radius:4px;background:var(--border);overflow:hidden}
.cov-bar-fill{height:100%;border-radius:4px;background:var(--blue);transition:width 1.2s ease;width:0%}
/* ── ACTIVITY FEED ── */
.af-item{display:flex;align-items:flex-start;gap:9px;padding:8px 0;border-bottom:1px solid var(--border-l)}
.af-item:last-child{border-bottom:none}
.af-dot{width:26px;height:26px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:1px;}
.af-dot svg{display:block;}
.af-text{font-size:12px;font-weight:600;color:var(--text);line-height:1.4}
.af-text b{color:var(--blue)}
.af-meta{font-size:10px;color:var(--text-3);margin-top:2px}
.warga-mini{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:var(--r3);background:var(--bg);margin-bottom:5px}
.wm-av{width:28px;height:28px;border-radius:50%;background:var(--amber-l);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#B45309;}
.wm-av svg{display:block;}
.wm-name{font-size:12px;font-weight:700;color:var(--text);flex:1}
.wm-sub{font-size:10px;color:var(--text-3);margin-top:1px}
/* ── DONASI CARD ── */
.donasi-item{
display:flex;align-items:flex-start;gap:9px;padding:9px 11px;
border-radius:var(--r3);background:var(--bg);margin-bottom:5px;
border-left:3px solid transparent;transition:border-color .15s;cursor:pointer;
}
.donasi-item:hover{background:var(--border-l)}
.donasi-item.status-menunggu{border-left-color:var(--amber)}
.donasi-item.status-dikonfirmasi{border-left-color:var(--green)}
.donasi-item.status-ditolak{border-left-color:var(--red)}
.di-avatar{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
.di-avatar svg{display:block;}
.di-name{font-size:12px;font-weight:700;color:var(--text)}
.di-meta{font-size:10px;color:var(--text-3);margin-top:2px;line-height:1.4}
.di-amount{font-size:12.5px;font-weight:800;color:var(--teal);white-space:nowrap;text-align:right}
.di-amount-sub{font-size:9px;color:var(--text-3);margin-top:2px;text-align:right}
/* ── DONASI SUMMARY ROW ── */
.donasi-summary{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-bottom:11px}
.ds-box{background:var(--bg);border-radius:var(--r3);padding:9px 11px;text-align:center}
.ds-box-num{font-size:17px;font-weight:800}
.ds-box-lbl{font-size:9px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}
/* ── FULL CARDS / TABLES ── */
.full-card{background:var(--surface-s);border-radius:var(--r);border:1px solid var(--border-l);box-shadow:var(--sh-sm);margin-bottom:18px}
.card-hdr{padding:13px 17px;border-bottom:1px solid var(--border-l);display:flex;align-items:center;justify-content:space-between}
.card-title{font-size:13.5px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}
.card-icon{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;}
.card-icon svg{display:block;}
.tbl-toolbar{display:flex;align-items:center;gap:7px;padding:9px 15px;border-bottom:1px solid var(--border-l)}
.tbl-search{position:relative;flex:1}
.tbl-search input{
width:100%;padding:7px 10px 7px 30px;font-size:12px;font-family:inherit;
border:1.5px solid var(--border);border-radius:var(--r3);color:var(--text);
background:var(--bg);transition:border-color .18s
}
.tbl-search input:focus{outline:none;border-color:var(--blue);background:white;box-shadow:0 0 0 3px var(--blue-m)}
.tbl-search-icon{position:absolute;left:8px;top:50%;transform:translateY(-50%);color:var(--text-3);pointer-events:none;display:flex;}
.tbl-add{
display:flex;align-items:center;gap:5px;padding:7px 13px;
border-radius:var(--r3);border:none;font-family:inherit;
font-size:11px;font-weight:700;cursor:pointer;transition:all .15s;white-space:nowrap
}
.ta-blue{background:var(--blue);color:white}.ta-blue:hover{background:var(--blue-d)}
.ta-green{background:var(--green);color:white}.ta-green:hover{background:var(--green-d)}
.ta-teal{background:var(--teal);color:white}.ta-teal:hover{background:#0E7490}
.tbl-scroll{overflow-x:auto}
table{width:100%;border-collapse:collapse}
thead tr{background:var(--bg)}
th{padding:8px 13px;font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.07em;text-align:left;border-bottom:1px solid var(--border-l);white-space:nowrap}
td{padding:10px 13px;font-size:12px;color:var(--text);border-bottom:1px solid var(--border-l)}
tr:last-child td{border-bottom:none}
tbody tr:hover{background:#FAFCFF}
.chip{display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:700;padding:3px 8px;border-radius:20px}
.chip-blue{background:var(--blue-m);color:var(--blue)}
.chip-green{background:var(--green-l);color:#047857}
.chip-red{background:var(--red-l);color:#B91C1C}
.chip-amber{background:var(--amber-l);color:#B45309}
.chip-purple{background:var(--purple-l);color:#6D28D9}
.chip-teal{background:var(--teal-l);color:#0E7490}
.chip-gray{background:var(--bg);color:var(--text-3)}
.act-btns{display:flex;align-items:center;gap:4px}
.act-btn{width:27px;height:27px;border:none;border-radius:7px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .14s}
.act-btn svg{display:block;}
.ab-edit{background:var(--blue-l);color:var(--blue)}.ab-edit:hover{background:var(--blue);color:white}
.ab-del{background:var(--red-l);color:var(--red)}.ab-del:hover{background:var(--red);color:white}
.ab-ok{background:var(--green-l);color:var(--green)}.ab-ok:hover{background:var(--green);color:white}
.ab-view{background:var(--teal-l);color:var(--teal)}.ab-view:hover{background:var(--teal);color:white}
.empty-tbl{text-align:center;padding:36px 16px;color:var(--text-3);font-size:12px;font-weight:500}
/* ── ANALYSIS ── */
.an-stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-bottom:14px}
.an-stat{background:var(--surface-s);border-radius:var(--r3);padding:13px 15px;border:1px solid var(--border-l);box-shadow:var(--sh-sm)}
.ans-num{font-size:24px;font-weight:800}
.ans-lbl{font-size:9px;font-weight:700;margin-top:3px;text-transform:uppercase;letter-spacing:.06em}
/* ── MODAL ── */
#modal-overlay{position:fixed;inset:0;background:rgba(15,23,42,.42);backdrop-filter:blur(6px);z-index:9000;display:none;align-items:center;justify-content:center}
#modal-overlay.open{display:flex}
.modal{background:white;border-radius:var(--r);width:460px;max-width:calc(100vw - 32px);max-height:90vh;display:flex;flex-direction:column;box-shadow:var(--sh-md);animation:mIn .2s cubic-bezier(.34,1.5,.64,1)}
@keyframes mIn{from{opacity:0;transform:scale(.9) translateY(14px)}to{opacity:1;transform:scale(1) translateY(0)}}
.modal-hdr{padding:15px 19px 11px;border-bottom:1px solid var(--border-l);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
.modal-title{font-size:14px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:9px}
.mt-icon{width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;}
.mt-icon svg{display:block;}
.modal-close{
width:27px;height:27px;border:none;border-radius:50%;
background:var(--bg);cursor:pointer;
display:flex;align-items:center;justify-content:center;
color:var(--text-2);transition:background .14s;font-size:13px;
}
.modal-close:hover{background:var(--border)}
.modal-body{flex:1;overflow-y:auto;padding:15px 19px 8px}
.modal-body::-webkit-scrollbar{width:3px}
.modal-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.modal-foot{padding:11px 19px 15px;border-top:1px solid var(--border-l);display:flex;gap:7px;flex-shrink:0}
.row-2{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.ff{margin-bottom:11px}
.ff label{display:block;font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.07em;margin-bottom:4px}
.ff input,.ff select,.ff textarea{
width:100%;padding:8px 11px;font-size:12px;font-family:inherit;
border:1.5px solid var(--border);border-radius:var(--r3);color:var(--text);
transition:border-color .18s;background:var(--bg)
}
.ff input:focus,.ff select:focus,.ff textarea:focus{outline:none;border-color:var(--blue);background:white;box-shadow:0 0 0 3px var(--blue-m)}
.ff textarea{min-height:68px;resize:vertical}
.ff-hint{font-size:10px;color:var(--text-3);margin-top:3px}
.ff-divider{display:flex;align-items:center;gap:8px;margin:4px 0 11px}
.ff-divider span{font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.07em;white-space:nowrap}
.ff-divider::before,.ff-divider::after{content:'';flex:1;height:1px;background:var(--border-l)}
.slider-block{background:var(--green-l);border:1.5px solid rgba(16,185,129,.2);border-radius:var(--r3);padding:11px 13px;margin-bottom:11px}
.sb-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:7px}
.sb-lbl{font-size:12px;font-weight:700;color:var(--text)}
.sb-val{background:var(--green);color:white;font-size:11px;font-weight:700;padding:3px 11px;border-radius:16px;min-width:50px;text-align:center}
input[type="range"]{appearance:none;width:100%;height:5px;border-radius:5px;background:rgba(16,185,129,.2);cursor:pointer}
input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:17px;height:17px;border-radius:50%;background:var(--green);border:3px solid white;box-shadow:0 2px 6px rgba(16,185,129,.3);cursor:pointer}
.preset-row{display:flex;gap:5px;margin-top:8px}
.preset{flex:1;padding:4px 2px;border-radius:16px;text-align:center;border:1.5px solid rgba(16,185,129,.2);background:white;font-family:inherit;font-size:10px;font-weight:700;color:var(--text-3);cursor:pointer;transition:all .14s}
.preset:hover{border-color:var(--green);color:var(--green)}
.preset.on{background:var(--green);border-color:var(--green);color:white}
.check-grid{display:flex;flex-wrap:wrap;gap:5px}
.check-item{display:flex;align-items:center;gap:5px;padding:5px 10px;border-radius:20px;border:1.5px solid var(--border);cursor:pointer;transition:all .14s;font-size:11px;font-weight:600;color:var(--text-2);background:var(--bg)}
.check-item:has(input:checked){background:var(--blue-l);border-color:var(--blue);color:var(--blue)}
.check-item input{display:none}
.btn-cncl{padding:8px 15px;border-radius:var(--r3);border:1.5px solid var(--border);background:var(--bg);font-family:inherit;font-size:12px;font-weight:600;color:var(--text-2);cursor:pointer;transition:all .14s}
.btn-cncl:hover{background:var(--border);color:var(--text)}
.btn-save{flex:1;padding:9px;border-radius:var(--r3);border:none;font-family:inherit;font-size:13px;font-weight:700;color:white;cursor:pointer;transition:all .15s;display:flex;align-items:center;justify-content:center;gap:5px}
.bs-green{background:var(--green)}.bs-green:hover{background:var(--green-d)}
.bs-blue{background:var(--blue)}.bs-blue:hover{background:var(--blue-d)}
.bs-red{background:var(--red)}.bs-red:hover{background:#DC2626}
.bs-teal{background:var(--teal)}.bs-teal:hover{background:#0E7490}
.btn-save:active{transform:scale(.98)}
/* ── DONASI DETAIL BOX ── */
.donasi-detail-box{background:var(--bg);border-radius:var(--r2);padding:13px 15px;margin-bottom:11px}
.ddb-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid var(--border-l)}
.ddb-row:last-child{border-bottom:none}
.ddb-label{font-size:11px;color:var(--text-3);font-weight:600}
.ddb-value{font-size:12px;color:var(--text);font-weight:700;text-align:right}
/* ── CONFIRM ── */
#confirm-overlay{position:fixed;inset:0;background:rgba(15,23,42,.42);backdrop-filter:blur(4px);z-index:9500;display:none;align-items:center;justify-content:center}
.confirm-box{background:white;border-radius:var(--r);width:340px;max-width:calc(100vw - 32px);box-shadow:var(--sh-md);animation:mIn .2s cubic-bezier(.34,1.5,.64,1);overflow:hidden}
.cb-icon-wrap{display:flex;align-items:center;justify-content:center;padding:22px 0 10px}
.cb-icon{width:52px;height:52px;border-radius:50%;background:var(--red-l);display:flex;align-items:center;justify-content:center;color:var(--red);}
.cb-icon svg{display:block;}
.cb-title{font-size:15px;font-weight:800;color:var(--text);text-align:center;padding:0 20px}
.cb-sub{font-size:12px;color:var(--text-2);text-align:center;padding:5px 20px 17px;line-height:1.5}
.cb-btns{display:flex;gap:7px;padding:0 15px 15px}
/* ── JENIS DONASI BADGE ── */
.jenis-badge{
display:inline-flex;align-items:center;gap:5px;
padding:3px 9px;border-radius:20px;font-size:10px;font-weight:700;
}
.jb-tunai{background:var(--green-l);color:#047857}
.jb-sembako{background:var(--amber-l);color:#B45309}
.jb-kesehatan{background:var(--red-l);color:#B91C1C}
.jb-pendidikan{background:var(--blue-l);color:var(--blue-d)}
.jb-pakaian{background:var(--purple-l);color:#6D28D9}
.jb-peralatan{background:var(--teal-l);color:#0E7490}
.jb-bangunan{background:#f0fdf4;color:#166534}
.jb-ternak{background:#fefce8;color:#a16207}
.jb-lainnya{background:var(--bg);color:var(--text-2)}
/* ── FILTER DONASI ── */
.don-filter-bar{display:flex;gap:6px;flex-wrap:wrap;padding:9px 15px;border-bottom:1px solid var(--border-l);}
.don-filter-chip{
display:flex;align-items:center;gap:4px;padding:4px 10px;border-radius:20px;
border:1.5px solid var(--border);background:var(--bg);
font-size:10.5px;font-weight:600;color:var(--text-2);cursor:pointer;transition:all .14s;
white-space:nowrap;
}
.don-filter-chip.active{background:var(--teal-l);border-color:var(--teal);color:var(--teal)}
.don-filter-chip:hover:not(.active){background:var(--border-l)}
/* ── TOAST ── */
#toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(10px);z-index:99999;background:var(--text);color:white;font-size:12px;font-weight:600;padding:9px 20px;border-radius:20px;box-shadow:var(--sh-md);opacity:0;transition:all .25s ease;pointer-events:none;white-space:nowrap}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
#toast.ok{background:var(--green)}
#toast.err{background:var(--red)}
</style>
</head>
<body>
<div id="app">
<!-- ═══ SIDEBAR ═══ -->
<aside id="sidebar">
<div class="sb-logo">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>
</div>
<a class="nav-item active" onclick="switchView('dashboard',this)" title="Dashboard">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
<span class="ni-badge" id="badge-ri">0</span>
</a>
<div class="sb-divider"></div>
<a class="nav-item" onclick="switchView('rumah-ibadah',this)" title="Rumah Ibadah">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
</a>
<a class="nav-item" onclick="switchView('warga-miskin',this)" title="Warga Miskin">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
<span class="ni-badge" id="badge-wm">0</span>
</a>
<div class="sb-divider"></div>
<a class="nav-item" onclick="switchView('bantuan',this)" title="Log Bantuan">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
</a>
<a class="nav-item" onclick="switchView('donasi',this)" title="Log Donasi">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>
<span class="ni-badge teal" id="badge-donasi">0</span>
</a>
<div class="sb-divider"></div>
<a class="nav-item" onclick="switchView('analisis',this)" title="Analisis Blind Spot">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
</a>
<div class="sb-divider"></div>
<a class="nav-item" onclick="switchView('verifikasi-laporan',this)" title="Verifikasi Laporan Warga">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
<span class="ni-badge" id="badge-laporan-dashboard" style="display:none">0</span>
</a>
<a class="nav-item" onclick="location.href='index.php'" title="Buka Peta">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
</a>
<div class="sb-spacer"></div>
<div class="sb-user-av" title="Logout — <?= htmlspecialchars($admin_name) ?>" onclick="location.href='logout.php'">
<?= strtoupper(substr($admin_name,0,1)) ?>
</div>
</aside>
<!-- ═══ MAIN ═══ -->
<div id="main">
<header id="topbar">
<div class="tb-breadcrumb">
<div class="tb-page" id="tb-title">Dashboard</div>
<div class="tb-sub" id="tb-sub">Ringkasan Pemetaan Sosial</div>
</div>
<div class="tb-search">
<div class="tb-search-icon">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</div>
<input type="text" placeholder="Cari data...">
</div>
<div class="tb-actions">
<button class="tb-btn ghost" onclick="location.href='index.php'">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
Buka Peta
</button>
<button class="tb-notif" onclick="showToast('Tidak ada notifikasi baru','ok')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
<span class="notif-dot"></span>
</button>
</div>
</header>
<div id="content">
<!-- ╔══════ DASHBOARD ══════╗ -->
<div class="view active" id="view-dashboard">
<div class="dash-grid">
<!-- LEFT -->
<div class="dash-left">
<!-- 4 mini stats -->
<div class="mini-stats">
<div class="mini-stat">
<div class="ms-lbl">Rumah Ibadah</div>
<div class="ms-num" id="dash-total-ri">0</div>
</div>
<div class="mini-stat">
<div class="ms-lbl">KK Warga</div>
<div class="ms-num" id="dash-total-wm">0</div>
</div>
<div class="mini-stat accent">
<div class="ms-lbl">Coverage</div>
<div class="ms-num" id="dash-cov-pct">0%</div>
</div>
<div class="mini-stat accent-teal">
<div class="ms-lbl">Donasi Masuk</div>
<div class="ms-num" id="dash-total-donasi">0</div>
</div>
</div>
<!-- Search + filter -->
<div class="search-card">
<div class="sc-input">
<div class="sc-input-icon">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</div>
<input type="text" placeholder="Cari nama tempat ibadah..." oninput="filterDashList(this.value)">
</div>
<div class="filter-row" id="dash-filter-row">
<div class="filter-chip active" onclick="filterDashKat('all',this)">Semua</div>
<div class="filter-chip" onclick="filterDashKat('Masjid',this)">Masjid</div>
<div class="filter-chip" onclick="filterDashKat('Gereja',this)">Gereja</div>
<div class="filter-chip" onclick="filterDashKat('Vihara',this)">Vihara</div>
<div class="filter-chip" onclick="filterDashKat('Pura',this)">Pura</div>
</div>
</div>
<!-- Ibadah list -->
<div class="list-scroll" id="dash-ibadah-list"></div>
<?php if ($role === 'admin' || $role === 'walikota'): ?>
<button class="tbl-add ta-blue" style="width:100%;justify-content:center;padding:9px;border-radius:var(--r2);"
onclick="openModal('modal-ri');isEditRI=false;resetFormRI();">
+ Tambah Rumah Ibadah
</button>
<?php endif; ?>
</div>
<!-- RIGHT -->
<div class="dash-right">
<div class="panel-header">
<div class="ph-top">
<div>
<div class="ph-eyebrow">Detail Lokasi</div>
<div class="ph-title" id="detail-name">Pilih Rumah Ibadah</div>
</div>
<button class="tb-btn primary" onclick="location.href='index.php'">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
Buka Peta
</button>
</div>
</div>
<!-- Mini map -->
<div class="map-block">
<div class="map-visual" id="mini-map" style="z-index:1">
<a href="index.php" class="map-open-btn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>
Lihat Peta Lengkap
</a>
</div>
<div class="map-stats-row">
<div class="msr-item">
<div class="msr-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></div>
<div class="msr-label">Terjangkau</div>
<div class="msr-val" id="ms-covered">0 KK</div>
</div>
<div class="msr-item">
<div class="msr-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#EF4444" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg></div>
<div class="msr-label">Blind Spot</div>
<div class="msr-val" id="ms-blind">0 KK</div>
</div>
<div class="msr-item">
<div class="msr-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></div>
<div class="msr-label">Ibadah Aktif</div>
<div class="msr-val" id="ms-ibadah">0</div>
</div>
<div class="msr-item">
<div class="msr-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#94A3B8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></div>
<div class="msr-label">Klaster</div>
<div class="msr-val" id="ms-cluster">0</div>
</div>
</div>
</div>
<!-- Tabs -->
<div class="tabs-card">
<div class="tabs-nav">
<div class="tab-btn active" onclick="switchDashTab('statistik',this)">Statistik</div>
<div class="tab-btn" onclick="switchDashTab('warga',this)">Warga</div>
<div class="tab-btn" onclick="switchDashTab('bantuan',this)">Log Bantuan</div>
<div class="tab-btn" onclick="switchDashTab('donasi-tab',this)">Donasi</div>
<div class="tab-btn" onclick="switchDashTab('analisis',this)">Analisis</div>
</div>
<div class="tabs-body">
<!-- Statistik -->
<div class="tab-pane active" id="tab-statistik">
<div class="detail-grid">
<div class="dg-item"><div class="dgi-label">Rumah Ibadah</div><div class="dgi-val" id="ds-ri">0</div><div class="dgi-sub">Aktif dalam sistem</div></div>
<div class="dg-item"><div class="dgi-label">Total Warga</div><div class="dgi-val" id="ds-wm">0</div><div class="dgi-sub">Kepala Keluarga</div></div>
<div class="dg-item"><div class="dgi-label">Terjangkau</div><div class="dgi-val" style="color:var(--green)" id="ds-covered">0</div><div class="dgi-sub">Dalam radius ibadah</div></div>
<div class="dg-item"><div class="dgi-label">Blind Spot</div><div class="dgi-val" style="color:var(--red)" id="ds-blind">0</div><div class="dgi-sub">Di luar jangkauan</div></div>
</div>
<div class="cov-widget">
<div class="cov-row"><span class="cov-row-title">Coverage Rate</span><span class="cov-pct-badge" id="cov-pct2">0%</span></div>
<div class="cov-bar"><div class="cov-bar-fill" id="cov-bar2"></div></div>
</div>
<div style="margin-top:11px">
<div style="font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:7px">Distribusi Terbaru</div>
<div id="dash-activity-feed"></div>
</div>
</div>
<!-- Warga -->
<div class="tab-pane" id="tab-warga"><div id="tab-warga-list"></div></div>
<!-- Log Bantuan -->
<div class="tab-pane" id="tab-bantuan"><div id="tab-bantuan-list"></div></div>
<!-- Donasi Tab -->
<div class="tab-pane" id="tab-donasi-tab">
<div class="donasi-summary">
<div class="ds-box">
<div class="ds-box-num" style="color:var(--teal)" id="dtab-total">0</div>
<div class="ds-box-lbl">Total</div>
</div>
<div class="ds-box">
<div class="ds-box-num" style="color:var(--amber)" id="dtab-menunggu">0</div>
<div class="ds-box-lbl">Menunggu</div>
</div>
<div class="ds-box">
<div class="ds-box-num" style="color:var(--green)" id="dtab-dikonfirmasi">0</div>
<div class="ds-box-lbl">Dikonfirmasi</div>
</div>
</div>
<div style="font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:7px">Donasi Terbaru dari Pengguna</div>
<div id="tab-donasi-list"></div>
<button onclick="switchView('donasi',document.querySelector('[title=\'Log Donasi\']'))"
style="width:100%;margin-top:9px;padding:8px;background:var(--teal-l);color:var(--teal);border:none;border-radius:var(--r3);font-family:inherit;font-size:11px;font-weight:700;cursor:pointer">
Lihat Semua Log Donasi →
</button>
</div>
<!-- Analisis -->
<div class="tab-pane" id="tab-analisis"><div id="tab-analisis-content"></div></div>
</div>
</div>
</div><!-- end dash-right -->
</div>
</div>
<!-- ╔══════ RUMAH IBADAH ══════╗ -->
<div class="view" id="view-rumah-ibadah">
<div class="full-card">
<div class="card-hdr">
<div class="card-title">
<div class="card-icon" style="background:var(--blue-l);color:var(--blue)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
</div>
Manajemen Rumah Ibadah
</div>
<button class="tbl-add ta-blue" onclick="openModal('modal-ri');isEditRI=false;resetFormRI();">+ Tambah Baru</button>
</div>
<div class="tbl-toolbar">
<div class="tbl-search">
<div class="tbl-search-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
<input type="text" placeholder="Cari nama, alamat, PIC..." oninput="filterTable('ri-tbody',this.value)">
</div>
</div>
<div class="tbl-scroll">
<table>
<thead><tr><th>ID</th><th>Nama Ibadah</th><th>Alamat</th><th>PIC</th><th>Radius</th><th>Aksi</th></tr></thead>
<tbody id="ri-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- ╔══════ WARGA MISKIN ══════╗ -->
<div class="view" id="view-warga-miskin">
<div class="full-card">
<div class="card-hdr">
<div class="card-title">
<div class="card-icon" style="background:var(--amber-l);color:var(--amber)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
</div>
Manajemen Data Warga Miskin
</div>
<button class="tbl-add ta-green" onclick="openModal('modal-wm');isEditWM=false;resetFormWM();">+ Tambah KK</button>
</div>
<div class="tbl-toolbar">
<div class="tbl-search">
<div class="tbl-search-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
<input type="text" placeholder="Cari nama KK, alamat..." oninput="filterTable('wm-tbody',this.value)">
</div>
</div>
<div class="tbl-scroll">
<table>
<thead><tr><th>ID</th><th>Kepala Keluarga</th><th>Pekerjaan</th><th>Jml Anggota</th><th>Klasifikasi</th><th>Status</th><th>Aksi</th></tr></thead>
<tbody id="wm-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- ╔══════ BANTUAN ══════╗ -->
<div class="view" id="view-bantuan">
<div class="full-card">
<div class="card-hdr">
<div class="card-title">
<div class="card-icon" style="background:var(--green-l);color:var(--green)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
</div>
Log Distribusi Bantuan
</div>
<button class="tbl-add ta-green" onclick="openModal('modal-bantuan');isEditBantuan=false;resetFormBantuan();">+ Catat Bantuan</button>
</div>
<div class="tbl-toolbar">
<div class="tbl-search">
<div class="tbl-search-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
<input type="text" placeholder="Cari penerima, jenis bantuan..." oninput="filterTable('bantuan-tbody',this.value)">
</div>
</div>
<div class="tbl-scroll">
<table>
<thead><tr><th>ID</th><th>Penerima</th><th>Jenis Bantuan</th><th>Tanggal</th><th>Penyalur</th><th>Keterangan</th><th>Aksi</th></tr></thead>
<tbody id="bantuan-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- ╔══════ LOG DONASI ══════╗ -->
<div class="view" id="view-donasi">
<!-- Ringkasan atas -->
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin-bottom:14px">
<div class="an-stat">
<div class="ans-num" style="color:var(--teal)" id="don-total-num">0</div>
<div class="ans-lbl" style="color:var(--teal)">Total Donasi</div>
</div>
<div class="an-stat">
<div class="ans-num" style="color:var(--amber)" id="don-menunggu-num">0</div>
<div class="ans-lbl" style="color:var(--amber)">Menunggu Konfirmasi</div>
</div>
<div class="an-stat">
<div class="ans-num" style="color:var(--green)" id="don-confirmed-num">0</div>
<div class="ans-lbl" style="color:var(--green)">Dikonfirmasi</div>
</div>
<div class="an-stat">
<div class="ans-num" style="color:var(--blue)" id="don-nilai-total">Rp 0</div>
<div class="ans-lbl" style="color:var(--blue)">Total Nilai Uang</div>
</div>
</div>
<div class="full-card">
<div class="card-hdr">
<div class="card-title">
<div class="card-icon" style="background:var(--teal-l);color:var(--teal)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>
</div>
Log Donasi dari Pengguna
</div>
<div style="display:flex;gap:7px;align-items:center">
<select id="don-filter-status" onchange="renderTableDonasi()" style="padding:6px 10px;border-radius:var(--r3);border:1.5px solid var(--border);background:var(--bg);font-family:inherit;font-size:11px;font-weight:600;color:var(--text-2);cursor:pointer">
<option value="">Semua Status</option>
<option value="Menunggu">Menunggu</option>
<option value="Dikonfirmasi">Dikonfirmasi</option>
<option value="Ditolak">Ditolak</option>
</select>
<button class="tbl-add" style="background:var(--blue-l);color:var(--blue);border:none;padding:6px 11px;border-radius:var(--r3);font-size:11px;font-weight:700;cursor:pointer;display:flex;align-items:center;gap:5px" onclick="muatDataDashboard();showToast('Memperbarui data...','ok')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
Refresh
</button>
</div>
</div>
<!-- Filter jenis donasi -->
<div class="don-filter-bar" id="don-jenis-filter-bar">
<div class="don-filter-chip active" onclick="filterDonasiJenis('',this)">Semua Jenis</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Uang Tunai',this)">Uang Tunai</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Sembako',this)">Sembako</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Bantuan Kesehatan',this)">Kesehatan</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Bantuan Pendidikan',this)">Pendidikan</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Pakaian',this)">Pakaian</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Peralatan Rumah Tangga',this)">Peralatan</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Bahan Bangunan',this)">Bahan Bangunan</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Hewan Ternak',this)">Hewan Ternak</div>
<div class="don-filter-chip" onclick="filterDonasiJenis('Lainnya',this)">Lainnya</div>
</div>
<div class="tbl-toolbar">
<div class="tbl-search">
<div class="tbl-search-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
<input type="text" placeholder="Cari nama donatur, jenis donasi..." oninput="filterTable('donasi-tbody',this.value)">
</div>
</div>
<div class="tbl-scroll">
<table>
<thead>
<tr>
<th>ID</th>
<th>Donatur</th>
<th>Jenis Donasi</th>
<th>Nilai / Keterangan</th>
<th>Disalurkan ke</th>
<th>Tanggal</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
<tbody id="donasi-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- ╔══════ ANALISIS ══════╗ -->
<div class="view" id="view-analisis">
<div class="an-stat-grid">
<div class="an-stat"><div class="ans-num" style="color:var(--green)" id="an-terjangkau">0</div><div class="ans-lbl" style="color:var(--green)">Terjangkau</div></div>
<div class="an-stat"><div class="ans-num" style="color:var(--red)" id="an-blindspot">0</div><div class="ans-lbl" style="color:var(--red)">Blind Spot</div></div>
<div class="an-stat"><div class="ans-num" style="color:var(--blue)" id="an-ibadah">0</div><div class="ans-lbl" style="color:var(--blue)">Ibadah Aktif</div></div>
<div class="an-stat"><div class="ans-num" style="color:var(--amber)" id="an-kritis">0</div><div class="ans-lbl" style="color:var(--amber)">Klaster Terdeteksi</div></div>
</div>
<div class="full-card">
<div class="card-hdr">
<div class="card-title">
<div class="card-icon" style="background:var(--red-l);color:var(--red)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
</div>
Zona Blind Spot Terdeteksi
</div>
<button class="tbl-add" style="background:var(--blue-l);color:var(--blue);border:none;padding:6px 11px;border-radius:var(--r3);font-size:11px;font-weight:700;cursor:pointer;display:flex;align-items:center;gap:5px" onclick="muatDataDashboard();showToast('Memperbarui data...','ok')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
Refresh
</button>
</div>
<div class="tbl-scroll">
<table>
<thead><tr><th>#</th><th>Koordinat Centroid</th><th>Jml KK Terdampak</th><th>Prioritas</th></tr></thead>
<tbody id="blindspot-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- ╔══════ VERIFIKASI LAPORAN ══════╗ -->
<div class="view" id="view-verifikasi-laporan">
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:11px;margin-bottom:14px">
<div class="an-stat"><div class="ans-num" style="color:var(--amber)" id="lap-count-menunggu">0</div><div class="ans-lbl" style="color:var(--amber)">Menunggu Verifikasi</div></div>
<div class="an-stat"><div class="ans-num" style="color:var(--green)" id="lap-count-verified">0</div><div class="ans-lbl" style="color:var(--green)">Diverifikasi</div></div>
<div class="an-stat"><div class="ans-num" style="color:var(--red)" id="lap-count-rejected">0</div><div class="ans-lbl" style="color:var(--red)">Ditolak</div></div>
</div>
<div class="full-card">
<div class="card-hdr">
<div class="card-title">
<div class="card-icon" style="background:var(--amber-l);color:var(--amber)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
</div>
Laporan dari Warga
</div>
<div style="display:flex;gap:7px;align-items:center">
<select id="laporan-filter-status" onchange="renderTableLaporan()" style="padding:6px 10px;border-radius:var(--r3);border:1.5px solid var(--border);background:var(--bg);font-family:inherit;font-size:11px;font-weight:600;color:var(--text-2);cursor:pointer">
<option value="">Semua Status</option>
<option value="menunggu">Menunggu</option>
<option value="diverifikasi">Diverifikasi</option>
<option value="ditolak">Ditolak</option>
</select>
<button class="tbl-add" style="background:var(--blue-l);color:var(--blue);border:none;padding:6px 11px;border-radius:var(--r3);font-size:11px;font-weight:700;cursor:pointer;display:flex;align-items:center;gap:5px" onclick="muatLaporanWarga();showToast('Memperbarui...','ok')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
Refresh
</button>
</div>
</div>
<div class="tbl-scroll">
<table>
<thead><tr><th>Ref</th><th>Nama KK</th><th>Pelapor</th><th>Tipe</th><th>Alamat</th><th>Tanggal</th><th>Status</th><th>Aksi</th></tr></thead>
<tbody id="laporan-tbody"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ MODALS ═══ -->
<div id="modal-overlay" onclick="if(event.target===this)closeAllModals()">
<!-- Modal: Rumah Ibadah -->
<div class="modal" id="modal-ri" style="display:none">
<div class="modal-hdr">
<div class="modal-title">
<div class="mt-icon" style="background:var(--blue-l);color:var(--blue)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
</div>
<span id="ri-modal-title">Tambah Rumah Ibadah</span>
</div>
<button class="modal-close" onclick="closeModal('modal-ri')">&#10005;</button>
</div>
<div class="modal-body">
<input type="hidden" id="ri_id">
<div class="ff"><label>Nama Lengkap Tempat Ibadah *</label><input type="text" id="ri_nama" placeholder="cth. Masjid Al-Ikhlas"></div>
<div class="ff"><label>Kategori *</label><select id="ri_kategori"><option value="Masjid">Masjid</option><option value="Gereja">Gereja</option><option value="Vihara">Vihara</option><option value="Pura">Pura</option><option value="Kelenteng">Kelenteng</option></select></div>
<div class="ff"><label>Alamat / Lokasi</label><input type="text" id="ri_alamat" placeholder="cth. Jl. Merdeka No. 12"></div>
<div class="ff"><label>Nama PIC / Pengurus</label><input type="text" id="ri_pic" placeholder="Nama ketua pengurus"></div>
<div class="ff-divider"><span>Koordinat &amp; Radius</span></div>
<div class="row-2">
<div class="ff"><label>Latitude *</label><input type="text" id="ri_lat" placeholder="-0.0263"></div>
<div class="ff"><label>Longitude *</label><input type="text" id="ri_lng" placeholder="109.3242"></div>
</div>
<div class="ff-hint" style="margin-bottom:8px">Salin dari Google Maps (cth: -0.0548, 109.3494)</div>
<div class="slider-block">
<div class="sb-row"><div class="sb-lbl">Radius Jangkauan</div><div class="sb-val" id="ri-radius-val">2 km</div></div>
<input type="range" id="ri_radius" min="500" max="5000" step="100" value="2000"
oninput="document.getElementById('ri-radius-val').textContent=(this.value>=1000?(this.value/1000)+' km':this.value+' m');updatePresets(this.value)">
<div class="preset-row">
<button class="preset" onclick="setPreset(500)">500m</button>
<button class="preset" onclick="setPreset(1000)">1 km</button>
<button class="preset on" onclick="setPreset(2000)">2 km</button>
<button class="preset" onclick="setPreset(3000)">3 km</button>
<button class="preset" onclick="setPreset(5000)">5 km</button>
</div>
</div>
</div>
<div class="modal-foot">
<button class="btn-cncl" onclick="closeModal('modal-ri')">Batal</button>
<button class="btn-save bs-blue" onclick="saveRI()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan Data
</button>
</div>
</div>
<!-- Modal: Warga Miskin -->
<div class="modal" id="modal-wm" style="display:none;width:500px">
<div class="modal-hdr">
<div class="modal-title">
<div class="mt-icon" style="background:var(--amber-l);color:var(--amber)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
</div>
<span id="wm-modal-title">Tambah Data Warga</span>
</div>
<button class="modal-close" onclick="closeModal('modal-wm')">&#10005;</button>
</div>
<div class="modal-body">
<input type="hidden" id="wm_id">
<div class="ff-divider"><span>Identitas Kepala Keluarga</span></div>
<div class="row-2">
<div class="ff"><label>Nama KK *</label><input type="text" id="wm_nama_kk" placeholder="Nama lengkap KK"></div>
<div class="ff"><label>Jumlah Anggota</label><input type="number" id="wm_jumlah" placeholder="4" min="1"></div>
</div>
<div class="ff"><label>Alamat Lengkap *</label><input type="text" id="wm_alamat" placeholder="Nama jalan, gang, nomor rumah"></div>
<div class="row-2">
<div class="ff"><label>Pekerjaan</label><input type="text" id="wm_pekerjaan" placeholder="cth. Buruh Harian"></div>
<div class="ff"><label>Pendidikan Terakhir</label><select id="wm_pendidikan"><option value="">— Pilih —</option><option>Tidak Sekolah</option><option>SD</option><option>SMP</option><option>SMA</option><option>D3</option><option>S1</option></select></div>
</div>
<div class="row-2">
<div class="ff"><label>Tanggal Lahir</label><input type="date" id="wm_tgl_lahir"></div>
<div class="ff"><label>Jaminan Sosial</label><select id="wm_jaminan"><option value="">— Pilih —</option><option>Tidak Ada</option><option>BPJS Kesehatan</option><option>KIS</option><option>BPJS Ketenagakerjaan</option></select></div>
</div>
<div class="ff"><label>Riwayat Penyakit / Disabilitas</label><input type="text" id="wm_penyakit" placeholder="Opsional"></div>
<div class="ff-divider"><span>Klasifikasi Warga</span></div>
<div class="ff">
<div class="check-grid">
<label class="check-item"><input type="checkbox" id="wm_k_fakir"> Fakir Miskin</label>
<label class="check-item"><input type="checkbox" id="wm_k_dhuafa"> Dhuafa</label>
<label class="check-item"><input type="checkbox" id="wm_k_lansia"> Janda / Lansia</label>
<label class="check-item"><input type="checkbox" id="wm_k_yatim"> Yatim Piatu</label>
<label class="check-item"><input type="checkbox" id="wm_k_bencana"> Korban Bencana</label>
<label class="check-item"><input type="checkbox" id="wm_k_disab"> Disabilitas</label>
</div>
</div>
<div class="ff-divider"><span>Koordinat Lokasi</span></div>
<div class="row-2">
<div class="ff"><label>Latitude *</label><input type="text" id="wm_lat" placeholder="-0.0263"></div>
<div class="ff"><label>Longitude *</label><input type="text" id="wm_lng" placeholder="109.3242"></div>
</div>
</div>
<div class="modal-foot">
<button class="btn-cncl" onclick="closeModal('modal-wm')">Batal</button>
<button class="btn-save bs-green" onclick="saveWM()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan Data
</button>
</div>
</div>
<!-- Modal: Bantuan -->
<div class="modal" id="modal-bantuan" style="display:none">
<div class="modal-hdr">
<div class="modal-title">
<div class="mt-icon" style="background:var(--green-l);color:var(--green)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
</div>
<span id="bantuan-modal-title">Catat Distribusi Bantuan</span>
</div>
<button class="modal-close" onclick="closeModal('modal-bantuan')">&#10005;</button>
</div>
<div class="modal-body">
<input type="hidden" id="hb_id">
<div class="ff"><label>Penerima Bantuan *</label><select id="hb_warga_id"><option value="">— Pilih Warga Penerima —</option></select></div>
<div class="row-2">
<div class="ff">
<label>Jenis Bantuan *</label>
<select id="hb_jenis_id">
<option value="1">Uang Tunai</option>
<option value="2">Sembako</option>
<option value="3">Bantuan Kesehatan</option>
<option value="4">Bantuan Pendidikan</option>
<option value="6">Pakaian</option>
<option value="7">Peralatan Rumah Tangga</option>
<option value="8">Bahan Bangunan</option>
<option value="9">Hewan Ternak</option>
<option value="5">Lainnya</option>
</select>
</div>
<div class="ff"><label>Tanggal Distribusi *</label><input type="date" id="hb_tgl"></div>
</div>
<div class="ff"><label>Penyalur / Sumber *</label><select id="hb_penyalur"><option value="">— Pilih Penyalur —</option></select></div>
<div class="ff"><label>Nilai / Deskripsi Singkat *</label><input type="text" id="hb_deskripsi" placeholder="cth: Rp 500.000 / Paket Sembako"></div>
<div class="ff"><label>Keterangan Tambahan</label><textarea id="hb_catatan" placeholder="Catatan operasional (opsional)..."></textarea></div>
</div>
<div class="modal-foot">
<button class="btn-cncl" onclick="closeModal('modal-bantuan')">Batal</button>
<button class="btn-save bs-green" onclick="saveBantuan()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
Simpan Log
</button>
</div>
</div>
<!-- Modal: Detail Donasi -->
<div class="modal" id="modal-donasi-detail" style="display:none">
<div class="modal-hdr">
<div class="modal-title">
<div class="mt-icon" style="background:var(--teal-l);color:var(--teal)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>
</div>
<span id="don-modal-title">Detail Donasi</span>
</div>
<button class="modal-close" onclick="closeModal('modal-donasi-detail')">&#10005;</button>
</div>
<div class="modal-body">
<div class="donasi-detail-box" id="don-detail-box"><!-- filled by JS --></div>
<div class="ff">
<label>Catatan Konfirmasi / Penolakan</label>
<textarea id="don-catatan-konfirmasi" placeholder="Tulis alasan atau catatan (opsional)..." style="min-height:56px"></textarea>
</div>
</div>
<div class="modal-foot">
<button class="btn-cncl" onclick="closeModal('modal-donasi-detail')">Tutup</button>
<button class="btn-save" style="background:var(--red);flex:0.7" id="btn-tolak-donasi" onclick="aksiDonasi('Ditolak')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Tolak
</button>
<button class="btn-save bs-teal" id="btn-konfirmasi-donasi" onclick="aksiDonasi('Dikonfirmasi')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
Konfirmasi
</button>
</div>
</div>
<!-- Modal: Tolak Laporan -->
<div class="modal" id="modal-tolak-laporan" style="display:none;max-width:360px;">
<div class="modal-hdr">
<div class="modal-title">
<div class="mt-icon" style="background:var(--red-l);color:var(--red)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</div>
Tolak Laporan Warga
</div>
<button class="modal-close" onclick="closeModal('modal-tolak-laporan')">&#10005;</button>
</div>
<div class="modal-body">
<input type="hidden" id="tolak_laporan_id">
<div class="ff">
<label>Alasan Penolakan (Akan dilihat pelapor)</label>
<textarea id="tolak_catatan" placeholder="Tulis alasan penolakan..." style="min-height:80px;"></textarea>
</div>
</div>
<div class="modal-foot">
<button class="btn-cncl" onclick="closeModal('modal-tolak-laporan')">Batal</button>
<button class="btn-save bs-red" onclick="eksekusiTolakLaporan()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Tolak Laporan
</button>
</div>
</div>
</div>
<!-- Confirm -->
<div id="confirm-overlay" onclick="if(event.target===this)closeConfirm()" style="display:none;position:fixed;inset:0;background:rgba(15,23,42,.42);backdrop-filter:blur(4px);z-index:9500;align-items:center;justify-content:center">
<div class="confirm-box">
<div class="cb-icon-wrap">
<div class="cb-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
</div>
</div>
<div class="cb-title" id="confirm-title">Hapus Data Ini?</div>
<div class="cb-sub" id="confirm-sub">Data yang dihapus tidak dapat dikembalikan.</div>
<div class="cb-btns">
<button class="btn-cncl" style="flex:1" onclick="closeConfirm()">Batal</button>
<button class="btn-save bs-red" style="flex:1" onclick="doDelete()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg>
Ya, Hapus
</button>
</div>
</div>
</div>
<div id="toast"></div>
<script>
let ibData=[],miData=[],logData=[],donasiData=[];
let isEditRI=false,isEditWM=false,isEditBantuan=false;
let _dashKat='all',_dashSearch='';
let _activeDonasi=null;
let _donFilterJenis='';
/* ─── JENIS DONASI MAP ─── */
const JENIS_DONASI_MAP = {
'Uang Tunai': { cls:'jb-tunai', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>' },
'Sembako': { cls:'jb-sembako', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>' },
'Bantuan Kesehatan': { cls:'jb-kesehatan', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>' },
'Bantuan Pendidikan': { cls:'jb-pendidikan',icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>' },
'Pakaian': { cls:'jb-pakaian', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.38 3.46L16 2a4 4 0 01-8 0L3.62 3.46a2 2 0 00-1.34 2.23l.58 3.57a1 1 0 00.99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 002-2V10h2.15a1 1 0 00.99-.84l.58-3.57a2 2 0 00-1.34-2.23z"/></svg>' },
'Peralatan Rumah Tangga': { cls:'jb-peralatan', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v16"/></svg>' },
'Bahan Bangunan': { cls:'jb-bangunan', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>' },
'Hewan Ternak': { cls:'jb-ternak', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>' },
'Lainnya': { cls:'jb-lainnya', icon:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>' },
};
function getJenisBadge(jenis){
const j=JENIS_DONASI_MAP[jenis]||JENIS_DONASI_MAP['Lainnya'];
return `<span class="jenis-badge ${j.cls}">${j.icon} ${jenis||'—'}</span>`;
}
/* ─── MINI MAP ─── */
const miniMap=L.map('mini-map',{zoomControl:false,dragging:true,scrollWheelZoom:false}).setView([-0.0548,109.3494],12);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19}).addTo(miniMap);
let miniLyI=L.layerGroup().addTo(miniMap),miniLyM=L.layerGroup().addTo(miniMap);
function renderMiniMap(){
miniLyI.clearLayers();miniLyM.clearLayers();
let bounds=[];
ibData.forEach(d=>{
const lat=parseFloat(d.lat),lng=parseFloat(d.lng);if(isNaN(lat)||isNaN(lng))return;
L.circle([lat,lng],{color:'#2563EB',fillColor:'#2563EB',fillOpacity:.1,radius:parseInt(d.radius),weight:1}).addTo(miniLyI);
L.circleMarker([lat,lng],{radius:5,color:'#1D4ED8',fillColor:'#fff',fillOpacity:1,weight:2}).addTo(miniLyI);
bounds.push([lat,lng]);
});
miData.forEach(d=>{
const lat=parseFloat(d.lat),lng=parseFloat(d.lng);if(isNaN(lat)||isNaN(lng)||lat===0||lng===0)return;
const inside=isInsideAnyCircle(lat,lng);const color=inside?'#10B981':'#EF4444';
L.circleMarker([lat,lng],{radius:3,color,fillColor:color,fillOpacity:.8,weight:1}).addTo(miniLyM);
bounds.push([lat,lng]);
});
if(bounds.length>0)miniMap.fitBounds(bounds,{padding:[20,20]});
}
/* ─── HAVERSINE ─── */
function haversine(la1,ln1,la2,ln2){
const R=6371000,dLat=(la2-la1)*Math.PI/180,dLng=(ln2-ln1)*Math.PI/180;
const a=Math.sin(dLat/2)**2+Math.cos(la1*Math.PI/180)*Math.cos(la2*Math.PI/180)*Math.sin(dLng/2)**2;
return R*2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
}
function isInsideAnyCircle(lat,lng){
return ibData.some(ib=>haversine(lat,lng,parseFloat(ib.lat),parseFloat(ib.lng))<=parseInt(ib.radius));
}
/* ─── FETCH ALL DATA ─── */
async function muatDataDashboard(){
try{
const [rRI, rWM, rDist, rDon] = await Promise.all([
fetch('api_ibadah.php?aksi=tampil'),
fetch('api_kemiskinan.php?aksi=tampil'),
fetch('api_distribusi.php?aksi=global'),
fetch('api_donasi.php?aksi=tampil')
]);
ibData = await rRI.json();
miData = await rWM.json();
const rawDon = await rDon.json();
donasiData = Array.isArray(rawDon) ? rawDon : [];
const distResult = await rDist.json();
const jenisMap = {
'1':'Uang Tunai','2':'Sembako','3':'Bantuan Kesehatan',
'4':'Bantuan Pendidikan','5':'Lainnya','6':'Pakaian',
'7':'Peralatan Rumah Tangga','8':'Bahan Bangunan','9':'Hewan Ternak'
};
logData = (distResult.log_distribusi || []).map(d => ({
id: d.id,
warga_id: d.warga_id,
nama_kk: d.nama_kk,
jenis_bantuan: jenisMap[d.jenis_id] || 'Bantuan',
tanggal: d.tanggal_distribusi,
sumber_bantuan: d.nama_penyalur,
satuan_custom: d.satuan_custom,
catatan: d.catatan,
lat: d.lat,
lng: d.lng
}));
populateDropdowns();
renderDashStats();
renderDashIbadahList();
renderTableRI();renderTableWM();renderTableBantuan();
renderActivityFeed();renderBlindSpots();
renderTabWarga();renderTabBantuan();renderTabAnalisis();
renderTabDonasi();
renderTableDonasi();
renderMiniMap();
}catch(err){
console.error(err);showToast('Gagal menyinkronkan data','err');
}
}
/* ─── DROPDOWNS ─── */
function populateDropdowns(){
const wSel=document.getElementById('hb_warga_id');
wSel.innerHTML='<option value="">— Pilih Warga Penerima —</option>';
miData.forEach(m=>wSel.innerHTML+=`<option value="${m.id}">${m.nama_kk}</option>`);
const pSel=document.getElementById('hb_penyalur');
pSel.innerHTML='<option value="">— Pilih Penyalur —</option>';
ibData.forEach(ib=>pSel.innerHTML+=`<option value="${ib.nama_tempat}">${ib.nama_tempat}</option>`);
pSel.innerHTML+=`<option value="Donatur Pribadi / Anonim">Donatur Anonim</option>`;
pSel.innerHTML+=`<option value="Lainnya">Lainnya</option>`;
}
/* ─── DASHBOARD STATS ─── */
function renderDashStats(){
const totalRI=ibData.length,totalWM=miData.length;
let covered=0;
miData.forEach(d=>{if(parseFloat(d.lat)!==0&&parseFloat(d.lng)!==0&&isInsideAnyCircle(parseFloat(d.lat),parseFloat(d.lng)))covered++;});
const pct=totalWM>0?(covered/totalWM)*100:0;
const totalDon=donasiData.length;
const menunggu=donasiData.filter(d=>d.status==='Menunggu'||!d.status).length;
document.getElementById('badge-ri').textContent=totalRI;
document.getElementById('badge-wm').textContent=totalWM;
document.getElementById('badge-donasi').textContent=menunggu||totalDon;
document.getElementById('dash-total-ri').textContent=totalRI;
document.getElementById('dash-total-wm').textContent=totalWM;
document.getElementById('dash-cov-pct').textContent=Math.round(pct)+'%';
document.getElementById('dash-total-donasi').textContent=totalDon;
document.getElementById('ds-ri').textContent=totalRI;
document.getElementById('ds-wm').textContent=totalWM;
document.getElementById('ds-covered').textContent=covered;
document.getElementById('ds-blind').textContent=totalWM-covered;
document.getElementById('ms-covered').textContent=covered+' KK';
document.getElementById('ms-blind').textContent=(totalWM-covered)+' KK';
document.getElementById('ms-ibadah').textContent=totalRI;
document.getElementById('cov-pct2').textContent=Math.round(pct)+'%';
document.getElementById('cov-bar2').style.width=Math.min(pct,100)+'%';
document.getElementById('an-terjangkau').textContent=covered;
document.getElementById('an-blindspot').textContent=totalWM-covered;
document.getElementById('an-ibadah').textContent=totalRI;
const confirmed=donasiData.filter(d=>d.status==='Dikonfirmasi').length;
let totalNilai=0;
donasiData.forEach(d=>{
if(d.status==='Dikonfirmasi'&&d.jenis_donasi==='Uang Tunai'){
const num=parseInt((d.nilai||'').replace(/\D/g,''));
if(!isNaN(num))totalNilai+=num;
}
});
const setEl=(id,v)=>{const el=document.getElementById(id);if(el)el.textContent=v;};
setEl('don-total-num',totalDon);setEl('don-menunggu-num',menunggu);setEl('don-confirmed-num',confirmed);
setEl('don-nilai-total','Rp '+totalNilai.toLocaleString('id-ID'));
setEl('dtab-total',totalDon);setEl('dtab-menunggu',menunggu);setEl('dtab-dikonfirmasi',confirmed);
}
/* ─── DONASI STATUS CHIP ─── */
function donasiStatusChip(status){
if(status==='Dikonfirmasi')return'<span class="chip chip-green">Dikonfirmasi</span>';
if(status==='Ditolak')return'<span class="chip chip-red">Ditolak</span>';
return'<span class="chip chip-amber">Menunggu</span>';
}
/* ─── TAB DONASI (dashboard) ─── */
function renderTabDonasi(){
const container=document.getElementById('tab-donasi-list');
if(!donasiData.length){
container.innerHTML='<div style="text-align:center;padding:20px;color:var(--text-3);font-size:12px">Belum ada donasi masuk.</div>';
return;
}
const latest=[...donasiData].sort((a,b)=>new Date(b.tanggal||0)-new Date(a.tanggal||0)).slice(0,8);
container.innerHTML=latest.map(d=>`
<div class="donasi-item status-${(d.status||'menunggu').toLowerCase().replace(' ','-')}"
onclick="bukaDetailDonasi(${d.id})">
<div class="di-avatar" style="background:var(--teal-l);color:var(--teal)">
${(JENIS_DONASI_MAP[d.jenis_donasi]||JENIS_DONASI_MAP['Lainnya']).icon}
</div>
<div style="flex:1;min-width:0">
<div class="di-name">${d.nama_donatur||'Anonim'}</div>
<div class="di-meta">${d.jenis_donasi||'—'} · ${d.tanggal||'—'}<br>→ ${d.disalurkan_ke||'Belum ditentukan'}</div>
</div>
<div>
<div class="di-amount">${d.jenis_donasi==='Uang Tunai'?'Rp '+(d.nilai||'—'):d.nilai||'—'}</div>
<div class="di-amount-sub">${donasiStatusChip(d.status)}</div>
</div>
</div>
`).join('');
}
/* ─── FILTER DONASI JENIS ─── */
function filterDonasiJenis(jenis, el){
_donFilterJenis=jenis;
document.querySelectorAll('.don-filter-chip').forEach(c=>c.classList.remove('active'));
el.classList.add('active');
renderTableDonasi();
}
/* ─── TABLE DONASI (full view) ─── */
function renderTableDonasi(){
const tbody=document.getElementById('donasi-tbody');
const filterStatus=document.getElementById('don-filter-status')?.value||'';
let data=donasiData;
if(filterStatus)data=data.filter(d=>(d.status||'Menunggu')===filterStatus);
if(_donFilterJenis)data=data.filter(d=>(d.jenis_donasi||'')===_donFilterJenis);
if(!data.length){tbody.innerHTML='<tr><td colspan="8" class="empty-tbl">Belum ada data donasi</td></tr>';return;}
tbody.innerHTML=data.map(d=>`
<tr>
<td style="color:var(--text-3);font-size:11px">${d.id}</td>
<td>
<strong>${d.nama_donatur||'Anonim'}</strong>
<br><span style="font-size:10px;color:var(--text-3)">${d.kontak||'—'}</span>
</td>
<td>${getJenisBadge(d.jenis_donasi)}</td>
<td>
<strong style="color:var(--teal)">${d.jenis_donasi==='Uang Tunai'?'Rp '+(d.nilai||'—'):d.nilai||'—'}</strong>
<br><span style="font-size:10px;color:var(--text-3)">${d.keterangan||''}</span>
</td>
<td style="font-size:11px">${d.disalurkan_ke||'—'}</td>
<td style="font-size:11px">${d.tanggal||'—'}</td>
<td>${donasiStatusChip(d.status||'Menunggu')}</td>
<td>
<div class="act-btns">
<button class="act-btn ab-view" title="Detail" onclick="bukaDetailDonasi(${d.id})">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
${(d.status||'Menunggu')==='Menunggu'?`
<button class="act-btn ab-ok" title="Konfirmasi" onclick="konfirmasiCepat(${d.id},'Dikonfirmasi')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</button>
<button class="act-btn ab-del" title="Tolak" onclick="konfirmasiCepat(${d.id},'Ditolak')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
`:''}
<button class="act-btn ab-del" title="Hapus" onclick="confirmDelete('donasi',${d.id})">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg>
</button>
</div>
</td>
</tr>
`).join('');
}
/* ─── BUKA DETAIL DONASI ─── */
function bukaDetailDonasi(id){
const d=donasiData.find(x=>x.id==id);if(!d)return;
_activeDonasi=d;
document.getElementById('don-modal-title').textContent='Detail Donasi #'+d.id;
document.getElementById('don-detail-box').innerHTML=`
<div class="ddb-row"><span class="ddb-label">Nama Donatur</span><span class="ddb-value">${d.nama_donatur||'Anonim'}</span></div>
<div class="ddb-row"><span class="ddb-label">Kontak</span><span class="ddb-value">${d.kontak||'—'}</span></div>
<div class="ddb-row"><span class="ddb-label">Jenis Donasi</span><span class="ddb-value">${getJenisBadge(d.jenis_donasi)}</span></div>
<div class="ddb-row"><span class="ddb-label">Nilai / Jumlah</span><span class="ddb-value" style="color:var(--teal)">${d.jenis_donasi==='Uang Tunai'?'Rp '+(d.nilai||'—'):d.nilai||'—'}</span></div>
<div class="ddb-row"><span class="ddb-label">Disalurkan ke</span><span class="ddb-value">${d.disalurkan_ke||'Belum ditentukan'}</span></div>
<div class="ddb-row"><span class="ddb-label">Tanggal</span><span class="ddb-value">${d.tanggal||'—'}</span></div>
<div class="ddb-row"><span class="ddb-label">Status</span><span class="ddb-value">${donasiStatusChip(d.status||'Menunggu')}</span></div>
<div class="ddb-row"><span class="ddb-label">Keterangan</span><span class="ddb-value">${d.keterangan||'—'}</span></div>
`;
document.getElementById('don-catatan-konfirmasi').value='';
const sudahProses=(d.status==='Dikonfirmasi'||d.status==='Ditolak');
document.getElementById('btn-konfirmasi-donasi').style.display=sudahProses?'none':'flex';
document.getElementById('btn-tolak-donasi').style.display=sudahProses?'none':'flex';
openModal('modal-donasi-detail');
}
/* ─── AKSI KONFIRMASI / TOLAK ─── */
function aksiDonasi(status){
if(!_activeDonasi)return;
const fd=new FormData();
fd.append('id',_activeDonasi.id);
fd.append('status',status);
fd.append('catatan',document.getElementById('don-catatan-konfirmasi').value);
fetch('api_donasi.php?aksi=update_status',{method:'POST',body:fd})
.then(r=>r.text())
.then(txt=>{
if(txt.includes('Sukses')){
showToast(status==='Dikonfirmasi'?'Donasi dikonfirmasi':'Donasi ditolak', status==='Dikonfirmasi'?'ok':'err');
closeAllModals();muatDataDashboard();
} else showToast('Gagal memperbarui status','err');
});
}
/* ─── KONFIRMASI CEPAT ─── */
function konfirmasiCepat(id,status){
const fd=new FormData();fd.append('id',id);fd.append('status',status);fd.append('catatan','');
fetch('api_donasi.php?aksi=update_status',{method:'POST',body:fd})
.then(r=>r.text())
.then(txt=>{
if(txt.includes('Sukses')){showToast(status==='Dikonfirmasi'?'Dikonfirmasi':'Ditolak','ok');muatDataDashboard();}
else showToast('Gagal','err');
});
}
/* ─── IBADAH LIST ─── */
function renderDashIbadahList(){
const container=document.getElementById('dash-ibadah-list');
const filtered=ibData.filter(d=>{
const matchKat=_dashKat==='all'||d.kategori===_dashKat;
const matchSearch=!_dashSearch||d.nama_tempat.toLowerCase().includes(_dashSearch)||d.alamat.toLowerCase().includes(_dashSearch)||(d.pic||'').toLowerCase().includes(_dashSearch);
return matchKat&&matchSearch;
});
if(!filtered.length){container.innerHTML='<div style="text-align:center;padding:28px 16px;color:var(--text-3);font-size:12px">Tidak ada data ditemukan</div>';return;}
container.innerHTML=filtered.map((d,i)=>{
const radStr=d.radius>=1000?(d.radius/1000).toFixed(1)+' km':d.radius+' m';
const wmInCircle=miData.filter(m=>haversine(parseFloat(m.lat),parseFloat(m.lng),parseFloat(d.lat),parseFloat(d.lng))<=parseInt(d.radius)).length;
const circlePct=miData.length>0?Math.round(wmInCircle/miData.length*100):0;
return `
<div class="tracking-card${i===0?' active-card':''}" onclick="selectIbadah(${d.id},this)">
<div class="tc-header">
<div class="tc-id">ID: <span>#${String(d.id).padStart(6,'0')}</span></div>
<div class="tc-header-right">
<span class="status-pill sp-blue">${d.kategori||'Masjid'}</span>
<button class="tc-more" onclick="event.stopPropagation();editRI(${d.id})">&#8943;</button>
</div>
</div>
<div class="tc-progress"><div class="tc-progress-fill" style="width:${circlePct}%"></div></div>
<div class="tc-timeline">
<div class="tl-row">
<div class="tl-date" style="font-size:9px;color:var(--text-3);width:52px">${d.alamat.substring(0,12)}…</div>
<div class="tl-left"><div class="tl-dot filled"></div><div class="tl-line"></div></div>
<div class="tl-label">${d.nama_tempat}</div>
<div class="tl-time">${radStr}</div>
</div>
<div class="tl-row">
<div class="tl-date" style="font-size:9px;color:var(--text-3);width:52px">Cakupan</div>
<div class="tl-left"><div class="tl-dot filled"></div><div class="tl-line"></div></div>
<div class="tl-label" style="color:var(--blue);font-size:11px">${wmInCircle} warga terjangkau</div>
<div class="tl-time" style="color:var(--blue);font-weight:700">${circlePct}%</div>
</div>
<div class="tl-row">
<div class="tl-date" style="font-size:9px;color:var(--text-3);width:52px">PIC</div>
<div class="tl-left"><div class="tl-dot empty"></div></div>
<div class="tl-label" style="color:var(--text-3);font-size:11px">${d.pic||'—'}</div>
<div class="tl-time">—</div>
</div>
</div>
</div>
`;
}).join('');
}
function selectIbadah(id,el){
document.querySelectorAll('.tracking-card').forEach(c=>c.classList.remove('active-card'));
el.classList.add('active-card');
const d=ibData.find(x=>x.id==id);
if(d)document.getElementById('detail-name').innerHTML=`<span>${d.nama_tempat}</span>`;
}
function filterDashList(val){_dashSearch=val.toLowerCase();renderDashIbadahList();}
function filterDashKat(kat,el){
_dashKat=kat;
document.querySelectorAll('#dash-filter-row .filter-chip').forEach(c=>c.classList.remove('active'));
el.classList.add('active');
renderDashIbadahList();
}
/* ─── RENDER TABS ─── */
function renderTabWarga(){
const c=document.getElementById('tab-warga-list');
if(!miData.length){c.innerHTML='<div style="text-align:center;padding:22px;color:var(--text-3);font-size:12px">Belum ada data warga</div>';return;}
c.innerHTML=miData.slice(0,20).map(d=>{
const inside=isInsideAnyCircle(parseFloat(d.lat),parseFloat(d.lng));
return`<div class="warga-mini"><div class="wm-av"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div><div style="flex:1"><div class="wm-name">${d.nama_kk}</div><div class="wm-sub">${d.alamat} · ${d.jumlah_anggota||0} jiwa</div></div><span class="chip ${inside?'chip-green':'chip-red'}">${inside?'Terjangkau':'Blind Spot'}</span><button class="act-btn ab-edit" style="margin-left:5px" onclick="editWM(${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button></div>`;
}).join('');
}
function renderTabBantuan(){
const c=document.getElementById('tab-bantuan-list');
if(!logData.length){c.innerHTML='<div style="text-align:center;padding:22px;color:var(--text-3);font-size:12px">Belum ada log bantuan</div>';return;}
c.innerHTML=logData.slice(0,15).map(d=>`
<div class="af-item"><div class="af-dot" style="background:var(--green-l);color:var(--green)"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg></div>
<div style="flex:1"><div class="af-text">Distribusi <b>${d.jenis_bantuan}</b> → ${d.nama_kk}</div><div class="af-meta">${d.tanggal} · ${d.sumber_bantuan}</div></div></div>
`).join('');
}
function renderTabAnalisis(){
const uncovered=miData.filter(d=>parseFloat(d.lat)!==0&&!isInsideAnyCircle(parseFloat(d.lat),parseFloat(d.lng)));
const clusters=[];const visited=new Set();
uncovered.forEach((p,i)=>{
if(visited.has(i))return;const cl=[p];visited.add(i);
uncovered.forEach((q,j)=>{if(!visited.has(j)&&haversine(parseFloat(p.lat),parseFloat(p.lng),parseFloat(q.lat),parseFloat(q.lng))<=600){cl.push(q);visited.add(j);}});
clusters.push(cl);
});
const c=document.getElementById('tab-analisis-content');
if(!clusters.length){c.innerHTML='<div style="text-align:center;padding:22px;color:var(--green);font-size:12px;font-weight:700">Tidak ada zona blind spot!</div>';return;}
c.innerHTML=clusters.map((cl,i)=>{
const cLat=cl.reduce((s,p)=>s+parseFloat(p.lat),0)/cl.length;
const cLng=cl.reduce((s,p)=>s+parseFloat(p.lng),0)/cl.length;
const prio=cl.length>=5?'Kritis':cl.length>=3?'Tinggi':'Sedang';
const chipCls=cl.length>=3?'chip-red':'chip-amber';
return`<div style="background:var(--bg);border-radius:var(--r3);padding:9px 11px;margin-bottom:5px;display:flex;align-items:center;gap:9px">
<div style="width:30px;height:30px;border-radius:50%;background:var(--red-l);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--red);flex-shrink:0">${cl.length}</div>
<div style="flex:1"><div style="font-size:12px;font-weight:700;color:var(--text)">Klaster ${i+1}</div><div style="font-size:10px;color:var(--text-3);margin-top:1px;font-family:monospace">${cLat.toFixed(4)}, ${cLng.toFixed(4)}</div></div>
<span class="chip ${chipCls}">${prio}</span>
</div>`;
}).join('');
}
/* ─── TABLES FULL ─── */
function renderTableRI(){
const tbody=document.getElementById('ri-tbody');let html='';
ibData.forEach(d=>{
const radStr=d.radius>=1000?(d.radius/1000).toFixed(1)+' km':d.radius+' m';
html+=`<tr><td style="color:var(--text-3);font-size:11px">${d.id}</td><td><strong>${d.nama_tempat}</strong><br><span style="font-size:10px;color:var(--text-3)">${d.kategori||''}</span></td><td style="font-size:11px;color:var(--text-2)">${d.alamat}</td><td>${d.pic||'—'}</td><td><span class="chip chip-blue">${radStr}</span></td><td><div class="act-btns"><button class="act-btn ab-edit" onclick="editRI(${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button><button class="act-btn ab-del" onclick="confirmDelete('ri',${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg></button></div></td></tr>`;
});
tbody.innerHTML=html||'<tr><td colspan="6" class="empty-tbl">Belum ada data Rumah Ibadah</td></tr>';
}
function renderTableWM(){
const tbody=document.getElementById('wm-tbody');let html='';
miData.forEach(d=>{
const inside=isInsideAnyCircle(parseFloat(d.lat),parseFloat(d.lng));
let kl=[];
if(d.is_fakir_miskin==1)kl.push('Fakir');if(d.is_dhuafa==1)kl.push('Dhuafa');if(d.is_lansia_tunggal==1)kl.push('Lansia');if(d.is_anak_yatim==1)kl.push('Yatim');if(d.is_korban_bencana==1)kl.push('Bencana');if(d.is_disabilitas==1)kl.push('Disab');
html+=`<tr><td style="color:var(--text-3);font-size:11px">${d.id}</td><td><strong>${d.nama_kk}</strong><br><span style="font-size:10px;color:var(--text-3)">${d.alamat}</span></td><td>${d.pekerjaan||'—'}</td><td style="text-align:center"><span class="chip chip-blue">${d.jumlah_anggota||0} jiwa</span></td><td><span class="chip chip-amber">${kl.join(', ')||'Umum'}</span></td><td><span class="chip ${inside?'chip-green':'chip-red'}">${inside?'Terjangkau':'Blind Spot'}</span></td><td><div class="act-btns"><button class="act-btn ab-edit" onclick="editWM(${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button><button class="act-btn ab-del" onclick="confirmDelete('wm',${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg></button></div></td></tr>`;
});
tbody.innerHTML=html||'<tr><td colspan="7" class="empty-tbl">Belum ada data Warga Miskin</td></tr>';
}
function renderTableBantuan(){
const tbody=document.getElementById('bantuan-tbody');let html='';
logData.forEach(d=>{
html+=`<tr><td style="color:var(--text-3);font-size:11px">${d.id}</td><td><strong>${d.nama_kk}</strong></td><td><span class="chip chip-green">${d.jenis_bantuan}</span></td><td style="font-size:11px">${d.tanggal}</td><td>${d.sumber_bantuan}</td><td style="font-size:11px;color:var(--text-2)">${d.satuan_custom||d.catatan||'—'}</td><td><div class="act-btns"><button class="act-btn ab-edit" onclick="editBantuan(${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button><button class="act-btn ab-del" onclick="confirmDelete('bantuan',${d.id})"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg></button></div></td></tr>`;
});
tbody.innerHTML=html||'<tr><td colspan="7" class="empty-tbl">Belum ada Log Bantuan</td></tr>';
}
function renderActivityFeed(){
const feed=document.getElementById('dash-activity-feed');
if(!logData||!logData.length){feed.innerHTML='<div style="padding:14px;text-align:center;font-size:11px;color:var(--text-3)">Belum ada aktivitas.</div>';return;}
feed.innerHTML=logData.slice(0,5).map(d=>`
<div class="af-item"><div class="af-dot" style="background:var(--amber-l);color:var(--amber)"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg></div>
<div><div class="af-text">Distribusi ${d.jenis_bantuan} ke <b>${d.nama_kk}</b></div><div class="af-meta">${d.tanggal} · ${d.sumber_bantuan}</div></div></div>
`).join('');
}
function renderBlindSpots(){
const tbody=document.getElementById('blindspot-tbody');
const uncovered=miData.filter(d=>parseFloat(d.lat)!==0&&!isInsideAnyCircle(parseFloat(d.lat),parseFloat(d.lng)));
const clusters=[];const visited=new Set();
uncovered.forEach((p,i)=>{if(visited.has(i))return;const cl=[p];visited.add(i);uncovered.forEach((q,j)=>{if(!visited.has(j)&&haversine(parseFloat(p.lat),parseFloat(p.lng),parseFloat(q.lat),parseFloat(q.lng))<=600){cl.push(q);visited.add(j);}});clusters.push(cl);});
document.getElementById('an-kritis').textContent=clusters.length;
document.getElementById('ms-cluster').textContent=clusters.length;
let html='';
clusters.forEach((c,i)=>{
const cLat=c.reduce((s,p)=>s+parseFloat(p.lat),0)/c.length;const cLng=c.reduce((s,p)=>s+parseFloat(p.lng),0)/c.length;
const prio=c.length>=5?'Kritis':c.length>=3?'Tinggi':'Sedang';
html+=`<tr><td>Klaster ${i+1}</td><td style="font-size:10px;font-family:monospace">${cLat.toFixed(4)}, ${cLng.toFixed(4)}</td><td style="text-align:center"><span style="font-weight:800;color:var(--red)">${c.length} KK</span></td><td><span class="chip ${c.length>=3?'chip-red':'chip-amber'}">${prio}</span></td></tr>`;
});
tbody.innerHTML=html||'<tr><td colspan="4" class="empty-tbl">Tidak ada zona blind spot terdeteksi.</td></tr>';
}
/* ─── CRUD ─── */
function resetFormRI(){['ri_id','ri_nama','ri_pic','ri_alamat','ri_lat','ri_lng'].forEach(id=>document.getElementById(id).value='');document.getElementById('ri_kategori').value='Masjid';setPreset(2000);document.getElementById('ri-modal-title').textContent='Tambah Rumah Ibadah';}
function editRI(id){const d=ibData.find(x=>x.id==id);if(!d)return;isEditRI=true;document.getElementById('ri-modal-title').textContent='Edit Rumah Ibadah';document.getElementById('ri_id').value=d.id;document.getElementById('ri_nama').value=d.nama_tempat;document.getElementById('ri_kategori').value=d.kategori||'Masjid';document.getElementById('ri_pic').value=d.pic;document.getElementById('ri_alamat').value=d.alamat;document.getElementById('ri_lat').value=d.lat;document.getElementById('ri_lng').value=d.lng;setPreset(d.radius);openModal('modal-ri');}
function saveRI(){const fd=new FormData();const id=document.getElementById('ri_id').value;fd.append('nama_tempat',document.getElementById('ri_nama').value);fd.append('kategori',document.getElementById('ri_kategori').value);fd.append('pic',document.getElementById('ri_pic').value);fd.append('alamat',document.getElementById('ri_alamat').value);fd.append('radius',document.getElementById('ri_radius').value);let url='';if(isEditRI){fd.append('id',id);url='api_ibadah.php?aksi=update';}else{fd.append('lat',document.getElementById('ri_lat').value);fd.append('lng',document.getElementById('ri_lng').value);url='api_ibadah.php?aksi=simpan';}fetch(url,{method:'POST',body:fd}).then(r=>r.text()).then(txt=>{if(txt.includes('Sukses')){showToast('Rumah ibadah disimpan','ok');closeAllModals();muatDataDashboard();}else showToast('Gagal: '+txt,'err');});}
function resetFormWM(){['wm_id','wm_nama_kk','wm_jumlah','wm_alamat','wm_pekerjaan','wm_tgl_lahir','wm_penyakit','wm_lat','wm_lng'].forEach(id=>document.getElementById(id).value='');document.getElementById('wm_pendidikan').value='';document.getElementById('wm_jaminan').value='';['fakir','dhuafa','lansia','yatim','bencana','disab'].forEach(x=>document.getElementById('wm_k_'+x).checked=false);document.getElementById('wm-modal-title').textContent='Tambah Data Warga';}
function editWM(id){const d=miData.find(x=>x.id==id);if(!d)return;isEditWM=true;document.getElementById('wm-modal-title').textContent='Edit Data Warga';document.getElementById('wm_id').value=d.id;document.getElementById('wm_nama_kk').value=d.nama_kk;document.getElementById('wm_jumlah').value=d.jumlah_anggota;document.getElementById('wm_alamat').value=d.alamat;document.getElementById('wm_pekerjaan').value=d.pekerjaan;document.getElementById('wm_pendidikan').value=d.pendidikan_terakhir;document.getElementById('wm_tgl_lahir').value=d.tanggal_lahir;document.getElementById('wm_jaminan').value=d.kepemilikan_jaminan;document.getElementById('wm_penyakit').value=d.riwayat_penyakit;document.getElementById('wm_lat').value=d.lat;document.getElementById('wm_lng').value=d.lng;document.getElementById('wm_k_fakir').checked=(d.is_fakir_miskin==1);document.getElementById('wm_k_dhuafa').checked=(d.is_dhuafa==1);document.getElementById('wm_k_lansia').checked=(d.is_lansia_tunggal==1);document.getElementById('wm_k_yatim').checked=(d.is_anak_yatim==1);document.getElementById('wm_k_bencana').checked=(d.is_korban_bencana==1);document.getElementById('wm_k_disab').checked=(d.is_disabilitas==1);openModal('modal-wm');}
function saveWM(){const fd=new FormData();const id=document.getElementById('wm_id').value;['nama_kk:wm_nama_kk','jumlah_anggota:wm_jumlah','alamat:wm_alamat','pekerjaan:wm_pekerjaan','pendidikan_terakhir:wm_pendidikan','tanggal_lahir:wm_tgl_lahir','kepemilikan_jaminan:wm_jaminan','riwayat_penyakit:wm_penyakit'].forEach(pair=>{const[k,v]=pair.split(':');fd.append(k,document.getElementById(v).value);});['fakir:is_fakir_miskin','dhuafa:is_dhuafa','lansia:is_lansia_tunggal','yatim:is_anak_yatim','bencana:is_korban_bencana','disab:is_disabilitas'].forEach(pair=>{const[k,v]=pair.split(':');fd.append(v,document.getElementById('wm_k_'+k).checked?1:0);});let url='';if(isEditWM){fd.append('id',id);url='api_kemiskinan.php?aksi=update';}else{fd.append('lat',document.getElementById('wm_lat').value);fd.append('lng',document.getElementById('wm_lng').value);url='api_kemiskinan.php?aksi=simpan';}fetch(url,{method:'POST',body:fd}).then(r=>r.text()).then(txt=>{if(txt.includes('Sukses')){showToast('Warga berhasil disimpan','ok');closeAllModals();muatDataDashboard();}else showToast('Gagal: '+txt,'err');});}
function resetFormBantuan(){document.getElementById('hb_id').value='';document.getElementById('hb_warga_id').value='';document.getElementById('hb_jenis_id').value='1';document.getElementById('hb_tgl').value=new Date().toISOString().split('T')[0];document.getElementById('hb_penyalur').value='';document.getElementById('hb_deskripsi').value='';document.getElementById('hb_catatan').value='';document.getElementById('bantuan-modal-title').textContent='Catat Distribusi Bantuan';}
function editBantuan(id){const d=logData.find(x=>x.id==id);if(!d)return;isEditBantuan=true;document.getElementById('bantuan-modal-title').textContent='Edit Log Bantuan';document.getElementById('hb_id').value=d.id;document.getElementById('hb_warga_id').value=d.warga_id;const jMap={'Uang Tunai':'1','Sembako':'2','Bantuan Kesehatan':'3','Bantuan Pendidikan':'4','Pakaian':'6','Peralatan Rumah Tangga':'7','Bahan Bangunan':'8','Hewan Ternak':'9','Lainnya':'5'};document.getElementById('hb_jenis_id').value=jMap[d.jenis_bantuan]||'5';document.getElementById('hb_tgl').value=d.tanggal;document.getElementById('hb_penyalur').value=d.sumber_bantuan;document.getElementById('hb_deskripsi').value=d.satuan_custom||'';document.getElementById('hb_catatan').value=d.catatan||'';openModal('modal-bantuan');}
function saveBantuan(){const fd=new FormData();const id=document.getElementById('hb_id').value;fd.append('warga_id',document.getElementById('hb_warga_id').value);fd.append('jenis_id',document.getElementById('hb_jenis_id').value);fd.append('tanggal_distribusi',document.getElementById('hb_tgl').value);fd.append('nama_penyalur',document.getElementById('hb_penyalur').value);fd.append('deskripsi_bantuan',document.getElementById('hb_deskripsi').value);fd.append('catatan',document.getElementById('hb_catatan').value);let url=isEditBantuan?'api_kemiskinan.php?aksi=update_bantuan':'api_kemiskinan.php?aksi=simpan_bantuan';if(isEditBantuan)fd.append('id',id);fetch(url,{method:'POST',body:fd}).then(r=>r.text()).then(txt=>{if(txt.includes('Sukses')){showToast('Log bantuan disimpan','ok');closeAllModals();muatDataDashboard();}else showToast('Gagal: '+txt,'err');});}
let _delTarget=null;
function confirmDelete(type,id){_delTarget={type,id};document.getElementById('confirm-overlay').style.display='flex';}
function closeConfirm(){document.getElementById('confirm-overlay').style.display='none';_delTarget=null;}
function doDelete(){
if(!_delTarget)return;const fd=new FormData();fd.append('id',_delTarget.id);
let url='';
if(_delTarget.type==='ri')url='api_ibadah.php?aksi=hapus';
if(_delTarget.type==='wm')url='api_kemiskinan.php?aksi=hapus';
if(_delTarget.type==='bantuan')url='api_kemiskinan.php?aksi=hapus_bantuan';
if(_delTarget.type==='donasi')url='api_donasi.php?aksi=hapus';
fetch(url,{method:'POST',body:fd}).then(r=>r.text()).then(txt=>{if(txt.includes('Sukses')){showToast('Data dihapus','ok');closeConfirm();muatDataDashboard();}else showToast('Gagal menghapus','err');});
}
/* ─── VIEW / TAB SWITCHING ─── */
function switchView(name,el){
document.querySelectorAll('.view').forEach(v=>v.classList.remove('active'));
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
document.getElementById('view-'+name).classList.add('active');
if(el)el.classList.add('active');
const titles={'dashboard':['Dashboard','Ringkasan Pemetaan Sosial'],'rumah-ibadah':['Rumah Ibadah','Manajemen Data Fasilitas'],'warga-miskin':['Warga Miskin','Manajemen Data Penduduk'],'bantuan':['Log Bantuan','Transparansi Distribusi'],'donasi':['Log Donasi','Donasi dari Pengguna'],'analisis':['Analisis Spasial','Blind Spot & Coverage'], 'verifikasi-laporan':['Verifikasi Laporan','Laporan Warga Menunggu Tindakan Admin']};
const t=titles[name]||['Dashboard','Ringkasan'];
document.getElementById('tb-title').textContent=t[0];
document.getElementById('tb-sub').textContent=t[1];
}
function switchDashTab(name,el){
document.querySelectorAll('.tab-pane').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
document.getElementById('tab-'+name).classList.add('active');
el.classList.add('active');
}
/* ─── MODAL HELPERS ─── */
function openModal(id){document.getElementById('modal-overlay').classList.add('open');document.getElementById(id).style.display='flex';}
function closeModal(id){document.getElementById(id).style.display='none';if(!Array.from(document.querySelectorAll('.modal')).some(m=>m.style.display!=='none'))document.getElementById('modal-overlay').classList.remove('open');}
function closeAllModals(){document.querySelectorAll('.modal').forEach(m=>m.style.display='none');document.getElementById('modal-overlay').classList.remove('open');}
/* ─── SLIDER / PRESETS ─── */
function setPreset(val){const sl=document.querySelector('.slider-block input[type="range"]');if(sl){sl.value=val;document.getElementById('ri-radius-val').textContent=val>=1000?(val/1000)+' km':val+' m';updatePresets(val);}}
function updatePresets(val){const map={500:'500m',1000:'1 km',2000:'2 km',3000:'3 km',5000:'5 km'};document.querySelectorAll('.preset').forEach(p=>{p.classList.toggle('on',p.textContent.trim()===(map[parseInt(val)]||''));});}
/* ─── TABLE FILTER ─── */
function filterTable(tbodyId,text){const tbody=document.getElementById(tbodyId);if(!tbody)return;tbody.querySelectorAll('tr').forEach(tr=>{tr.style.display=(!text||tr.textContent.toLowerCase().includes(text.toLowerCase()))?'':'none';});}
/* ─── TOAST ─── */
function showToast(msg,type){const t=document.getElementById('toast');t.textContent=msg;t.className='show '+(type||'');clearTimeout(t._tid);t._tid=setTimeout(()=>t.className='',2800);}
/* ─── INIT ─── */
window.addEventListener('load',()=>{
muatDataDashboard();
muatLaporanWarga();
document.getElementById('hb_tgl').value=new Date().toISOString().split('T')[0];
});
// ─── LAPORAN WARGA ───────────────────────────────────────────────────────────
let laporanData = [];
async function muatLaporanWarga() {
try {
const r = await fetch('api_laporan_warga.php?aksi=tampil');
laporanData = await r.json();
renderTableLaporan();
// Update stats
const menunggu = laporanData.filter(l => l.status === 'menunggu').length;
const verified = laporanData.filter(l => l.status === 'diverifikasi').length;
const rejected = laporanData.filter(l => l.status === 'ditolak').length;
const setEl = (id, v) => { const el = document.getElementById(id); if(el) el.textContent = v; };
setEl('lap-count-menunggu', menunggu);
setEl('lap-count-verified', verified);
setEl('lap-count-rejected', rejected);
// Badge di sidebar
const badge = document.getElementById('badge-laporan-dashboard');
if(badge) {
badge.textContent = menunggu;
badge.style.display = menunggu > 0 ? 'flex' : 'none';
}
} catch(err) {
console.error('Gagal muat laporan:', err);
}
}
function renderTableLaporan() {
const tbody = document.getElementById('laporan-tbody');
if(!tbody) return;
const filterStatus = document.getElementById('laporan-filter-status')?.value || '';
let data = laporanData;
if(filterStatus) data = data.filter(l => l.status === filterStatus);
if(!data.length) {
tbody.innerHTML = '<tr><td colspan="8" class="empty-tbl">Tidak ada laporan' + (filterStatus ? ' dengan status ini' : '') + '</td></tr>';
return;
}
const stCfg = {
'menunggu': { chip:'chip-amber', label:'Menunggu' },
'diverifikasi': { chip:'chip-green', label:'Diverifikasi' },
'ditolak': { chip:'chip-red', label:'Ditolak' }
};
tbody.innerHTML = data.map(l => {
const st = stCfg[l.status] || stCfg['menunggu'];
const tglStr = l.created_at ? new Date(l.created_at).toLocaleDateString('id-ID',{day:'2-digit',month:'short',year:'numeric'}) : '—';
const tipeLbl = l.tipe_laporan === 'diri_sendiri'
? '<span class="chip chip-blue">Diri Sendiri</span>'
: '<span class="chip chip-gray">Orang Lain</span>';
const actionBtns = l.status === 'menunggu'
? `<button class="act-btn ab-ok" title="Verifikasi" onclick="verifikasiLaporan(${l.id})">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>
</button>
<button class="act-btn ab-del" title="Tolak" onclick="bukaModalTolakLaporan(${l.id})">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>`
: '<span style="font-size:10px;color:var(--text-3)">—</span>';
return `<tr>
<td style="color:var(--text-3);font-size:11px">#${String(l.id).padStart(6,'0')}</td>
<td><strong>${l.nama_kk}</strong></td>
<td style="font-size:11px">${l.nama_pelapor || '—'}</td>
<td>${tipeLbl}</td>
<td style="font-size:11px;color:var(--text-2);max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${l.alamat}</td>
<td style="font-size:11px">${tglStr}</td>
<td><span class="chip ${st.chip}">${st.label}</span></td>
<td><div class="act-btns">${actionBtns}</div></td>
</tr>`;
}).join('');
}
function verifikasiLaporan(id) {
if(!confirm('Verifikasi laporan ini? Data warga akan otomatis masuk ke peta.')) return;
const fd = new FormData();
fd.append('id', id);
fetch('api_laporan_warga.php?aksi=verifikasi', { method:'POST', body:fd })
.then(r => r.json())
.then(res => {
if(res.status === 'sukses') {
showToast('✅ Diverifikasi! Titik warga masuk ke peta.', 'ok');
muatLaporanWarga();
muatDataDashboard();
} else {
showToast('❌ ' + res.pesan, 'err');
}
})
.catch(() => showToast('❌ Gagal menghubungi server.', 'err'));
}
function bukaModalTolakLaporan(id) {
document.getElementById('tolak_laporan_id').value = id;
document.getElementById('tolak_catatan').value = '';
openModal('modal-tolak-laporan');
}
function eksekusiTolakLaporan() {
const id = document.getElementById('tolak_laporan_id').value;
const catatan = document.getElementById('tolak_catatan').value.trim();
const fd = new FormData();
fd.append('id', id);
fd.append('catatan_admin', catatan);
fetch('api_laporan_warga.php?aksi=tolak', { method:'POST', body:fd })
.then(r => r.json())
.then(res => {
if(res.status === 'sukses') {
showToast('Laporan ditolak.', 'ok');
closeModal('modal-tolak-laporan');
muatLaporanWarga();
} else {
showToast('❌ ' + res.pesan, 'err');
}
})
.catch(() => showToast('❌ Gagal.', 'err'));
}
</script>
</body>
</html>