1828 lines
118 KiB
PHP
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')">✕</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 & 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')">✕</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')">✕</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')">✕</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')">✕</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})">⋯</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>
|