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

3374 lines
181 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start(); // Memulai sesi
// Cek apakah variabel sesi user sudah ada
if (!isset($_SESSION['user_id'])) {
// Jika tidak ada, tendang pengguna ke halaman login
header("Location: login.php");
exit; // Pastikan skrip berhenti agar tidak memuat sisa halaman
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebGIS Pemetaan Sosial — Data Kemiskinan</title>
<meta name="description" content="Platform WebGIS interaktif untuk pemetaan data sosial, kemiskinan, dan distribusi bantuan warga.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400&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>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<style>
/* ─── DESIGN TOKENS ──────────────────────────────────────── */
:root {
/* Primary (blue) */
--blue: #2563EB;
--blue-d: #1D4ED8;
--blue-l: #EFF6FF;
--blue-m: rgba(37,99,235,.09);
/* Dark text */
--navy: #0F172A;
/* Green */
--green: #10B981;
--green-d: #059669;
--green-l: #ECFDF5;
/* Red */
--red: #EF4444;
--red-l: #FEF2F2;
/* Amber */
--amber: #F59E0B;
--amber-l: #FFFBEB;
/* Surfaces & Layout */
--bg: #EBF0F7;
--surface: rgba(255,255,255,0.96);
--surface-s: #FFFFFF;
--border: #E2E8F0;
--border-l: #F1F5F9;
--text-2: #475569;
--text-3: #94A3B8;
/* Legacy aliases */
--glass: rgba(255,255,255,0.96);
/* Shadows */
--shadow: 0 8px 32px rgba(15,23,42,0.14);
--shadow-sm: 0 2px 10px rgba(15,23,42,0.08);
--sh-sm: 0 1px 4px rgba(15,23,42,0.06);
--sh: 0 4px 20px rgba(15,23,42,0.10);
--sh-md: 0 8px 32px rgba(15,23,42,0.14);
/* Radius */
--r: 16px;
--r2: 12px;
--r3: 8px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; font-family: 'Plus Jakarta Sans', sans-serif; overflow: hidden; }
/* ── MAP with navbar offset ── */
#map {
position: fixed;
top: 56px;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
cursor: crosshair;
}
#navbar {
position: fixed;
top: 0; left: 0; right: 0;
height: 56px;
z-index: 1200;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(24px) saturate(1.8);
-webkit-backdrop-filter: blur(24px) saturate(1.8);
border-bottom: 1px solid rgba(226,232,240,0.7);
box-shadow: 0 1px 16px rgba(15,23,42,0.08);
display: flex;
align-items: center;
padding: 0 16px;
gap: 4px;
animation: slideDown 0.45s cubic-bezier(0.34,1.56,0.64,1) both;
}
@keyframes slideDown {
from { opacity:0; transform:translateY(-14px); }
to { opacity:1; transform:translateY(0); }
}
/* Brand */
.nav-brand {
display: flex;
align-items: center;
gap: 9px;
margin-right: 12px;
text-decoration: none;
flex-shrink: 0;
}
.nav-brand-logo {
width: 32px; height: 32px;
border-radius: 9px;
background: linear-gradient(135deg, #2563EB, #1D4ED8);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 2px 8px rgba(37,99,235,0.35);
}
.nav-brand-logo svg { display: block; }
.nav-brand-text {
font-size: 13.5px;
font-weight: 800;
color: var(--navy);
letter-spacing: -0.3px;
line-height: 1;
}
.nav-brand-text span {
color: var(--blue);
}
.nav-divider {
width: 1px;
height: 24px;
background: var(--border);
margin: 0 8px;
flex-shrink: 0;
}
/* Nav Items */
.nav-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 11px;
border-radius: 8px;
border: none;
background: transparent;
font-family: inherit;
font-size: 12.5px;
font-weight: 600;
color: var(--text-2);
cursor: pointer;
transition: all 0.18s cubic-bezier(0.4,0,0.2,1);
white-space: nowrap;
flex-shrink: 0;
text-decoration: none;
}
.nav-item svg { flex-shrink: 0; transition: transform 0.18s; }
.nav-item:hover {
background: var(--blue-m);
color: var(--blue);
}
.nav-item:hover svg { transform: scale(1.1); }
.nav-item:active { transform: scale(0.97); }
.nav-item.active {
background: var(--blue-m);
color: var(--blue);
}
/* Colored variants */
.nav-item.danger:hover { background: var(--red-l); color: var(--red); }
.nav-item.warning:hover { background: #fff7ed; color: #D97706; }
.nav-item.success:hover { background: var(--green-l); color: var(--green-d); }
/* Active gap state */
.nav-item.gap-active {
background: var(--red-l);
color: var(--red);
animation: navPulse 2s ease-in-out infinite;
}
@keyframes navPulse {
0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0);}
50% { box-shadow: 0 0 0 4px rgba(239,68,68,0.15); } }
/* Spacer */
.nav-spacer { flex: 1; }
/* Stats in Navbar */
.nav-stats {
display: flex;
align-items: center;
gap: 12px;
background: var(--border-l);
border: 1px solid var(--border);
border-radius: 20px;
padding: 5px 14px;
font-size: 11.5px;
font-weight: 500;
color: var(--navy);
flex-shrink: 0;
}
.nsp-item { display: flex; align-items: center; gap: 5px; }
.nsp-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.nsp-num { font-weight: 700; }
.nsp-sep { width: 1px; height: 12px; background: var(--border); }
.nsp-label { color: var(--text-3); font-size: 10.5px; }
/* User area in Navbar */
.nav-user {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 12px 5px 8px;
border-radius: 20px;
background: var(--border-l);
border: 1px solid var(--border);
cursor: default;
flex-shrink: 0;
margin-left: 8px;
}
.nav-user-avatar {
width: 26px; height: 26px;
border-radius: 50%;
background: linear-gradient(135deg, #2563EB, #1D4ED8);
display: flex; align-items: center; justify-content: center;
}
.nav-user-avatar svg { display: block; }
.nav-user-name {
font-size: 12px;
font-weight: 700;
color: var(--navy);
}
.nav-logout {
display: flex;
align-items: center;
gap: 4px;
color: var(--red);
font-size: 11px;
font-weight: 700;
text-decoration: none;
background: var(--red-l);
padding: 3px 9px;
border-radius: 12px;
transition: background 0.15s;
flex-shrink: 0;
}
.nav-logout:hover { background: rgba(239,68,68,0.18); }
.nav-logout svg { display: block; }
/* ── HINT BAR ── */
#hint-bar {
position: fixed;
bottom: 28px; left: 50%;
transform: translateX(-50%);
z-index: 1000;
background: rgba(15,23,42,0.78);
color: rgba(255,255,255,0.88);
font-size: 11.5px; font-weight: 500;
padding: 7px 20px 7px 14px;
border-radius: 24px;
pointer-events: none;
backdrop-filter: blur(12px);
display: flex;
align-items: center;
gap: 8px;
border: 1px solid rgba(255,255,255,0.12);
box-shadow: 0 4px 16px rgba(15,23,42,0.2);
}
/* ── PANEL BACKDROP ── */
#panel-backdrop {
position: fixed; inset: 0;
z-index: 1900;
display: none;
}
#panel-backdrop.show { display: block; }
/* ── SLIDE PANELS ── */
.slide-panel {
position: fixed;
top: 0; right: 0;
height: 100%;
width: 340px;
z-index: 2000;
transform: translateX(110%);
transition: transform 0.34s cubic-bezier(0.4,0,0.2,1);
}
.slide-panel.open { transform: translateX(0); }
.panel-inner {
position: absolute; inset: 0;
background: var(--surface-s);
border-left: 1px solid var(--border);
display: flex; flex-direction: column;
box-shadow: -12px 0 48px rgba(15,23,42,0.14);
}
.panel-header {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 17px 14px;
border-bottom: 1px solid var(--border-l);
flex-shrink: 0;
background: linear-gradient(to bottom, #fafcff, var(--surface-s));
}
.panel-title {
font-size: 14.5px; font-weight: 700; color: var(--navy);
display: flex; align-items: center; gap: 9px;
}
.pt-icon {
width: 30px; height: 30px; border-radius: 9px;
display: flex; align-items: center; justify-content: center;
}
.btn-close {
width: 28px; height: 28px; border: none;
background: var(--border-l); border-radius: 50%;
cursor: pointer;
display: flex; align-items: center; justify-content: center;
color: var(--text-3); transition: all 0.15s;
line-height: 1;
}
.btn-close:hover { background: var(--border); color: var(--navy); }
.btn-close svg { display: block; }
.panel-body {
flex: 1; overflow-y: auto; padding: 14px 16px 20px;
background: var(--surface-s);
}
.panel-body::-webkit-scrollbar { width: 4px; }
.panel-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
/* ── SEARCH ── */
.search-wrap { position: relative; margin-bottom: 10px; }
.si { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); opacity: 0.45; pointer-events: none; display: flex; }
#searchInput {
width: 100%; padding: 9px 12px 9px 32px;
font-size: 13px; font-family: inherit;
border: 1.5px solid var(--border); border-radius: var(--r3);
background: var(--border-l); color: var(--navy);
transition: border-color 0.2s, background 0.2s;
}
#searchInput:focus { outline: none; border-color: var(--blue); background: white; box-shadow: 0 0 0 3px var(--blue-m); }
#searchInput::placeholder { color: var(--text-3); }
.sr-item {
display: flex; align-items: center; gap: 10px;
padding: 9px 10px; border-radius: var(--r3);
cursor: pointer; transition: background 0.15s;
}
.sr-item:hover { background: var(--blue-m); }
.sr-icon {
width: 32px; height: 32px; border-radius: var(--r3);
display: flex; align-items: center; justify-content: center;
font-size: 15px; flex-shrink: 0;
}
.sr-name { font-size: 13px; font-weight: 600; color: var(--navy); }
.sr-sub { font-size: 11px; color: var(--text-3); margin-top: 1px; }
.empty-state {
text-align: center; padding: 30px 16px;
color: var(--text-3); font-size: 13px;
}
.es-icon { font-size: 26px; margin-bottom: 7px; display: flex; justify-content: center; }
.es-icon svg { opacity: 0.35; }
/* ── HISTORY ITEMS ── */
.hi {
display: flex; align-items: center; gap: 10px;
padding: 10px; border-radius: var(--r2);
border: 1px solid var(--border-l);
margin-bottom: 7px;
background: var(--surface-s);
transition: box-shadow 0.15s, border-color 0.15s;
}
.hi:hover { box-shadow: var(--shadow-sm); border-color: var(--border); }
.hi-icon {
width: 34px; height: 34px; border-radius: var(--r3);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.hi-icon svg { display: block; }
.hi-info { flex: 1; min-width: 0; cursor: pointer; }
.hi-name { font-size: 12px; font-weight: 600; color: var(--navy); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hi-meta { font-size: 10px; color: var(--text-3); margin-top: 1px; }
.hi-del {
width: 26px; height: 26px; border: none;
background: var(--red-l); border-radius: var(--r3);
cursor: pointer; font-size: 12px;
display: flex; align-items: center; justify-content: center;
color: var(--red); transition: background 0.15s; flex-shrink: 0;
}
.hi-del:hover { background: rgba(239,68,68,0.20); }
/* ── MODAL UTAMA ── */
#modal-backdrop {
position: fixed; inset: 0;
background: rgba(15,23,42,0.40);
backdrop-filter: blur(4px);
z-index: 3000;
display: none; align-items: center; justify-content: center;
}
#modal-backdrop.show { display: flex; }
#modal-box {
background: var(--surface-s); border-radius: var(--r);
width: 360px; max-width: calc(100vw - 24px);
max-height: 90vh;
display: flex;
flex-direction: column;
box-shadow: 0 24px 60px rgba(15,23,42,0.22);
overflow: hidden;
animation: mIn 0.24s cubic-bezier(0.34,1.5,0.64,1);
}
@keyframes mIn {
from { opacity:0; transform: scale(0.88) translateY(14px); }
to { opacity:1; transform: scale(1) translateY(0); }
}
.mh {
padding: 16px 16px 0;
display: flex; align-items: flex-start; justify-content: space-between;
}
.mh-left { display: flex; align-items: center; gap: 10px; }
.mh-icon {
width: 36px; height: 36px; border-radius: var(--r3);
display: flex; align-items: center; justify-content: center;
}
.mh-icon svg { display: block; }
.mh-title { font-size: 14px; font-weight: 700; color: var(--navy); }
.mh-coord { font-size: 10px; color: var(--text-3); margin-top: 2px; }
/* ── TAB KATEGORI ── */
.mt-row {
display: flex; gap: 6px;
padding: 13px 16px 0;
}
.mt {
flex: 1; padding: 7px 8px; border-radius: var(--r3);
border: 1.5px solid var(--border);
background: var(--border-l);
font-family: inherit; font-size: 12px; font-weight: 600;
color: var(--text-3); cursor: pointer;
transition: all 0.18s;
display: flex; align-items: center; justify-content: center; gap: 6px;
}
.mt svg { flex-shrink: 0; }
.mt.a-green { background: var(--green-l); border-color: var(--green); color: var(--green-d); }
.mt.a-blue { background: var(--blue-l); border-color: var(--blue); color: var(--blue-d); }
.mb {
padding: 12px 16px 0;
overflow-y: auto;
flex: 1;
}
.mb::-webkit-scrollbar { width: 4px; }
.mb::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
/* ── FORM FIELDS ── */
.ff { margin-bottom: 10px; }
.ff label {
display: block; font-size: 10px; font-weight: 700;
color: var(--text-3); text-transform: uppercase;
letter-spacing: 0.07em; margin-bottom: 4px;
}
.ff input, .ff select {
width: 100%; padding: 8px 11px; font-size: 13px; font-family: inherit;
border: 1.5px solid var(--border); border-radius: var(--r3);
color: var(--navy); transition: border-color 0.18s, box-shadow 0.18s;
background: var(--border-l);
}
.ff input:focus {
outline: none;
border-color: var(--blue);
box-shadow: 0 0 0 3px var(--blue-m);
background: #fff;
}
/* ── SLIDER ── */
.sc {
background: var(--green-l);
border: 1.5px solid rgba(16,185,129,0.25);
border-radius: var(--r2);
padding: 11px 13px;
margin-bottom: 10px;
}
.sc-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 9px;
}
.sc-lbl {
font-size: 12px;
font-weight: 700;
color: var(--navy);
}
.sc-val {
background: var(--green);
color: white;
font-size: 11px;
font-weight: 700;
padding: 3px 11px;
border-radius: 16px;
min-width: 52px;
text-align: center;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 5px;
border-radius: 5px;
background: rgba(16,185,129,0.20);
cursor: pointer;
margin-bottom: 8px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 19px;
height: 19px;
border-radius: 50%;
background: var(--green);
border: 3px solid white;
box-shadow: 0 2px 6px rgba(16,185,129,0.25);
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 19px;
height: 19px;
border-radius: 50%;
background: var(--green);
border: 3px solid white;
box-shadow: 0 2px 6px rgba(16,185,129,0.25);
cursor: pointer;
}
/* ── MODAL FOOTER BUTTONS ── */
.mf {
padding: 12px 16px 16px;
display: flex;
gap: 8px;
background: var(--surface-s);
border-top: 1px solid var(--border-l);
flex-shrink: 0;
}
.btn-cncl {
padding: 9px 15px;
border-radius: var(--r3);
border: 1.5px solid var(--border);
background: var(--border-l);
font-family: inherit;
font-size: 12px;
font-weight: 600;
color: var(--text-2);
cursor: pointer;
transition: all 0.14s;
}
.btn-cncl:hover {
background: var(--border);
color: var(--navy);
}
.btn-sv {
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 0.18s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.btn-sv.g { background: var(--green); }
.btn-sv.g:hover { background: var(--green-d); }
.btn-sv.b { background: var(--blue); }
.btn-sv.b:hover { background: var(--blue-d); }
.btn-sv:active { transform: scale(0.98); }
/* ── PRESET BUTTONS ── */
.pp-row {
display: flex;
gap: 5px;
}
.pp {
flex: 1;
padding: 4px 2px;
border-radius: 16px;
border: 1.5px solid rgba(16,185,129,0.22);
background: rgba(255,255,255,0.75);
font-family: inherit;
font-size: 10px;
font-weight: 700;
color: var(--text-3);
cursor: pointer;
transition: all 0.14s;
text-align: center;
}
.pp:hover {
border-color: var(--green);
color: var(--green);
}
.pp.active {
background: var(--green) !important;
border-color: var(--green) !important;
color: white !important;
}
/* ── TOAST ── */
#toast {
position: fixed; bottom: 68px; left: 50%; transform: translateX(-50%) translateY(8px);
z-index: 9999; background: var(--navy); color: white; font-size: 12px; font-weight: 600;
padding: 9px 22px; border-radius: 20px;
box-shadow: 0 6px 24px rgba(15,23,42,0.28), 0 0 0 1px rgba(255,255,255,0.08) inset;
opacity: 0; transition: all 0.3s cubic-bezier(0.34,1.56,0.64,1); 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); }
/* ── LEAFLET POPUP ── */
.leaflet-popup-content-wrapper {
border-radius: var(--r) !important; border: none !important;
box-shadow: 0 16px 48px rgba(15,23,42,0.2) !important;
font-family: 'Plus Jakarta Sans',sans-serif !important;
padding: 0 !important; overflow: hidden !important;
}
.leaflet-popup-content {
margin: 0 !important;
width: 250px !important;
display: flex !important;
flex-direction: column !important;
box-sizing: border-box !important;
}
.lp { display: flex; flex-direction: column; width: 100%; }
.lp-hd {
display: flex; align-items: center; gap: 9px;
padding: 12px 14px 8px;
border-bottom: 1px solid var(--border-l); flex-shrink: 0;
background: linear-gradient(to bottom, #f8fafc, white);
}
.lp-ti { font-size: 13px; font-weight: 700; color: var(--navy); }
.lp-su { font-size: 10px; color: var(--text-3); }
.lp-bd { padding: 12px 14px; font-size: 12px; color: var(--text-2); line-height: 1.6; }
.lp-ef {
max-height: 180px !important;
overflow-y: auto !important;
padding: 10px 14px;
border-bottom: 1px solid var(--border-l);
}
.lp-in {
width: 100%; padding: 6px 8px; font-size: 11px; font-family: inherit;
border: 1.5px solid var(--border); border-radius: var(--r3); color: var(--navy);
margin-bottom: 8px; display: block; background: var(--border-l);
}
.lp-in:focus { outline: none; border-color: var(--blue); background: white; }
.lp-btns {
padding: 10px 14px 12px;
background: var(--border-l);
border-top: 1px solid var(--border-l);
flex-shrink: 0;
display: grid !important;
grid-template-columns: 1fr 1fr;
gap: 5px;
}
.lp-bt {
padding: 6px 5px; border-radius: var(--r3); border: 1.5px solid var(--border);
background: white; font-family: inherit; font-size: 11px; font-weight: 700;
color: var(--text-2); cursor: pointer; transition: all 0.14s;
display: flex; align-items: center; justify-content: center; gap: 4px;
}
.lp-bt svg { flex-shrink: 0; }
.lp-bt:hover { background: var(--blue); color: white; border-color: var(--blue); }
.lp-bt:hover svg { color: white; }
.lp-bt.sv { background: var(--green); color: white; border-color: var(--green); }
.lp-bt.dl:hover { background: var(--red); color: white; border-color: var(--red); }
/* ── USER PROFILE BAR ── */
#user-profile {
animation: slideDown 0.5s 0.1s cubic-bezier(0.34,1.56,0.64,1) both;
}
/* ── GAP BADGE ── */
@keyframes pulseRing {
0% { transform: scale(0.9); opacity: 0.9; }
70% { transform: scale(1.4); opacity: 0.3; }
100% { transform: scale(0.9); opacity: 0.9; }
}
.gap-badge {
position: fixed;
bottom: 68px; left: 16px;
z-index: 1000;
background: var(--red);
color: white;
font-size: 11px; font-weight: 700;
padding: 6px 14px 6px 12px;
border-radius: 20px;
box-shadow: 0 4px 16px rgba(239,68,68,0.38);
display: none;
font-family: 'Plus Jakarta Sans', sans-serif;
animation: pulseRing 2s ease-in-out infinite;
align-items: center;
gap: 6px;
}
.gap-badge svg { flex-shrink: 0; }
.fab-gap-on {
background: var(--red-l) !important;
border: 1.5px solid rgba(239,68,68,0.30) !important;
}
</style>
</head>
<body>
<div id="map"></div>
<!-- ═══════════════════════════════════════════
NAVBAR
═══════════════════════════════════════════ -->
<nav id="navbar">
<!-- Brand -->
<div class="nav-brand">
<div class="nav-brand-logo">
<svg width="16" height="16" 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>
<div class="nav-brand-text">WebGIS<span>&nbsp;Sosial</span></div>
</div>
<div class="nav-divider"></div>
<!-- Menu Items -->
<?php if (isset($_SESSION['role']) && in_array($_SESSION['role'], ['admin', 'walikota'])): ?>
<a href="dashboard.php" class="nav-item" style="background:var(--blue-m); 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"><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>
Dashboard
</a>
<div class="nav-divider"></div>
<?php endif; ?>
<button class="nav-item" onclick="openPanel('cari')">
<svg width="14" height="14" 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>
Cari Lokasi
</button>
<button class="nav-item" onclick="openPanel('histori')">
<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>
Histori
</button>
<button class="nav-item danger" id="fab-gap" onclick="toggleGapLayer()">
<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="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<span id="fab-gap-text">Blind Spot</span>
</button>
<button class="nav-item warning" onclick="bukaModalLaporan()" style="position:relative">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
Lapor Warga
<?php if (in_array($_SESSION['role'] ?? '', ['admin', 'walikota'])): ?>
<span id="badge-laporan-nav" style="display:none;position:absolute;top:4px;right:4px;min-width:14px;height:14px;border-radius:8px;background:var(--red);color:white;font-size:8px;font-weight:700;align-items:center;justify-content:center;padding:0 3px;border:1.5px solid rgba(255,255,255,0.85)">0</span>
<?php endif; ?>
</button>
<button class="nav-item success" onclick="openModalDonasi()">
<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>
Bantu Mereka
</button>
<button class="nav-item" onclick="bukaTransparansi()" style="color:#D97706">
<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>
Distribusi
</button>
<!-- Spacer -->
<div class="nav-spacer"></div>
<!-- Live Stats -->
<div class="nav-stats">
<div class="nsp-item">
<div class="nsp-dot" style="background:#10B981"></div>
<span class="nsp-label">Ibadah</span>
<span class="nsp-num" id="cI">&mdash;</span>
</div>
<div class="nsp-sep"></div>
<div class="nsp-item">
<div class="nsp-dot" style="background:#2563EB"></div>
<span class="nsp-label">KK</span>
<span class="nsp-num" id="cM">&mdash;</span>
</div>
<div class="nsp-sep"></div>
<div class="nsp-item">
<div class="nsp-dot" style="background:#94A3B8"></div>
<span class="nsp-label">Jiwa</span>
<span class="nsp-num" id="cJ">&mdash;</span>
</div>
</div>
<!-- User -->
<div class="nav-user">
<div class="nav-user-avatar">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" 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>
<span class="nav-user-name"><?php echo ucfirst($_SESSION['role']); ?></span>
<a href="logout.php" class="nav-logout">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
Logout
</a>
</div>
</nav>
<!-- Hint bar (bottom center) -->
<div id="hint-bar">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.7)" stroke-width="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>
Klik di peta untuk menambahkan data
</div>
<div id="panel-backdrop" onclick="closePanel()"></div>
<div class="slide-panel" id="panel-cari">
<div class="panel-inner">
<div class="panel-header">
<div class="panel-title">
<div class="pt-icon" style="background:var(--blue-l)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#2563EB" 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>
Cari Lokasi
</div>
<button class="btn-close" onclick="closePanel()">
<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>
</div>
<div class="panel-body">
<div class="search-wrap">
<span class="si">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="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>
</span>
<input type="text" id="searchInput" placeholder="Nama tempat atau kepala keluarga..." oninput="doSearch(this.value)" />
</div>
<div id="searchResults">
<div class="empty-state">
<div class="es-icon"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
Ketik untuk mencari lokasi
</div>
</div>
</div>
</div>
</div>
<div class="slide-panel" id="panel-histori">
<div class="panel-inner">
<div class="panel-header">
<div class="panel-title">
<div class="pt-icon" style="background:var(--border-l)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#475569" 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>
Histori Data
</div>
<button class="btn-close" onclick="closePanel()">
<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>
</div>
<div class="panel-body">
<div id="histList">
<div class="empty-state">
<div class="es-icon"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10h18M3 14h18M10 3v18M14 3v18" opacity=".3"/><rect x="3" y="3" width="18" height="18" rx="2"/></svg></div>
Belum ada data tersimpan
</div>
</div>
</div>
</div>
</div>
<div class="slide-panel" id="panel-transparansi">
<div class="panel-inner">
<div class="panel-header">
<div class="panel-title">
<div class="pt-icon" style="background:#fef3c7">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D97706" 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>
Transparansi Distribusi
</div>
<button class="btn-close" onclick="closePanel()">
<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>
</div>
<div class="panel-body">
<div style="background: linear-gradient(135deg,#10B981,#059669); border-radius: var(--r2); padding: 18px; color: white; margin-bottom: 16px; box-shadow: 0 8px 24px rgba(16,185,129,0.28);">
<div style="font-size: 11px; font-weight: 600; opacity: 0.85; margin-bottom: 4px; display:flex; align-items:center; gap:5px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="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>
Total Donasi Diterima
</div>
<div style="font-size: 26px; font-weight: 800; letter-spacing: 0.5px;" id="trans-masuk">Rp 0</div>
<div style="font-size: 10px; margin-top: 8px; background: rgba(255,255,255,0.2); display: inline-block; padding: 3px 10px; border-radius: 12px;" id="trans-donatur">Dari 0 Donatur</div>
</div>
<div style="font-size: 12px; font-weight: 700; color: var(--navy); margin-bottom: 12px; border-bottom: 2px solid var(--border-l); padding-bottom: 6px; display:flex; align-items:center; gap:6px;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#475569" stroke-width="2" stroke-linecap="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>
Log Penyaluran ke Warga
</div>
<div id="list-distribusi">
<div class="empty-state">
<div class="es-icon"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
Memuat data transparansi...
</div>
</div>
</div>
</div>
</div>
<div id="modal-donasi" style="display:none; position:fixed; inset:0; background:rgba(15,23,42,0.48); backdrop-filter:blur(6px); z-index:3000; align-items:center; justify-content:center;">
<div style="background:white; border-radius:var(--r); padding:20px 24px; width:340px; max-width:90vw; max-height:90vh; overflow-y:auto; box-shadow:0 28px 64px rgba(15,23,42,0.28); animation: mIn 0.24s cubic-bezier(0.34,1.5,0.64,1);">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
<div style="display:flex;align-items:center;gap:10px">
<div id="mdl-icon" style="width:34px;height:34px;background:linear-gradient(135deg,#10B981,#059669);border-radius:10px;display:flex;align-items:center;justify-content:center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" 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 id="mdl-title-container" style="text-align:left">
<div style="font-size:14px;font-weight:700;color:var(--navy)">Salurkan Donasi</div>
</div>
</div>
<button onclick="closeModalDonasi()" style="width:28px;height:28px;border:none;background:var(--border-l);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#475569" 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>
</div>
<div id="donasi-modal-body">
<div style="background: var(--border-l); border: 1.5px dashed rgba(16,185,129,0.3); border-radius: var(--r2); padding: 16px; text-align: center; margin-bottom: 16px;">
<div style="font-size: 12px; font-weight: 700; color: var(--navy); margin-bottom: 4px;">Pilih Metode Pembayaran</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;">
<label style="border: 1.5px solid var(--blue); border-radius: var(--r3); padding: 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px; background: var(--blue-l);">
<input type="radio" name="metode_donasi" value="QRIS" checked style="margin: 0;">
<span style="font-size: 11px; font-weight: 700; color: var(--navy);">QRIS</span>
</label>
<label style="border: 1.5px solid var(--border); border-radius: var(--r3); padding: 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px; background: white;">
<input type="radio" name="metode_donasi" value="Transfer Bank" style="margin: 0;">
<span style="font-size: 11px; font-weight: 700; color: var(--navy);">Transfer Bank</span>
</label>
</div>
</div>
<div class="ff"><label>Nominal Donasi (Rp)</label><input type="number" id="donasi_nominal" placeholder="Contoh: 50000" min="10000"></div>
<div class="ff"><label>Nama Donatur</label><input type="text" id="donasi_nama" placeholder="Nama Anda (Boleh Anonim)"></div>
<div class="ff"><label>Pesan Dukungan (Opsional)</label><input type="text" id="donasi_pesan" placeholder="Tuliskan doa atau pesan..."></div>
<button class="btn-sv g" style="width: 100%; margin-top: 14px; padding: 10px;" onclick="prosesDonasi()">Lanjutkan Pembayaran</button>
</div>
</div>
</div>
<div class="slide-panel" id="panel-laporan-saya">
<div class="panel-inner">
<div class="panel-header">
<div class="panel-title">
<div class="pt-icon" style="background:var(--amber-l)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D97706" 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 Saya
</div>
<button class="btn-close" onclick="closePanel()">
<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>
</div>
<div class="panel-body">
<div id="laporan-saya-list">
<div class="empty-state">
<div class="es-icon"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
Memuat data laporan...
</div>
</div>
</div>
</div>
</div>
<div class="slide-panel" id="panel-detail">
<div class="panel-inner">
<div class="panel-header">
<div class="panel-title">
<div class="pt-icon" style="background:var(--blue-l)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
</div>
Detail Warga
</div>
<button class="btn-close" onclick="closePanel()">
<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>
</div>
<div class="panel-body" id="detail-content"></div>
</div>
</div>
<div id="modal-backdrop">
<div id="modal-box">
<div class="mh">
<div class="mh-left">
<div class="mh-icon" id="micon" style="background:var(--green-l)">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="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>
<div>
<div class="mh-title">Tambah Data Baru</div>
<div class="mh-coord" id="mcoord">—</div>
</div>
</div>
<button class="btn-close" onclick="closeModal()">
<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>
</div>
<div class="mt-row">
<button class="mt a-green" id="mt-ibadah" onclick="switchTab('ibadah')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="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>
Rumah Ibadah
</button>
<button class="mt" id="mt-miskin" onclick="switchTab('miskin')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="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>
Warga Miskin
</button>
</div>
<div class="mb" id="fb-ibadah">
<div class="ff"><label>Nama Tempat</label><input type="text" id="f_nama" placeholder="Contoh: Masjid Al-Ikhlas" /></div>
<div class="ff"><label>Kategori Rumah Ibadah</label>
<select id="f_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>PIC / Pengurus</label><input type="text" id="f_pic" placeholder="Nama pengurus" /></div>
<div class="ff"><label>Alamat</label><input type="text" id="f_alm_i" placeholder="Alamat lengkap" /></div>
<div class="sc">
<div class="sc-top">
<span class="sc-lbl">Radius Jangkauan</span>
<span class="sc-val" id="rvDisplay">500 m</span>
</div>
<input type="range" id="radSlider" min="500" max="5000" step="50" value="500" oninput="onSlide(this.value)" />
<div class="pp-row">
<button class="pp active" data-r="500" onclick="setPreset(500,this)">500m</button>
<button class="pp" data-r="1000" onclick="setPreset(1000,this)">1 km</button>
<button class="pp" data-r="2000" onclick="setPreset(2000,this)">2 km</button>
<button class="pp" data-r="5000" onclick="setPreset(5000,this)">5 km</button>
</div>
</div>
</div>
<div class="mb" id="fb-miskin" style="display:none">
<div style="display:flex;gap:5px;margin-bottom:10px">
<button id="stab-kk" onclick="switchSubTab('kk')" style="flex:1;padding:6px;border-radius:6px;border:1.5px solid var(--blue);background:var(--blue-l);font-family:inherit;font-size:11px;font-weight:700;color:var(--blue-d);cursor:pointer;transition:all 0.15s;display:flex;align-items:center;justify-content:center;gap:5px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
Data KK
</button>
<button id="stab-anggota" onclick="switchSubTab('anggota')" style="flex:1;padding:6px;border-radius:6px;border:1.5px solid var(--border);background:var(--border-l);font-family:inherit;font-size:11px;font-weight:700;color:var(--text-3);cursor:pointer;transition:all 0.15s;display:flex;align-items:center;justify-content:center;gap:5px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="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>
Anggota <span id="badge-anggota" style="background:var(--blue);color:white;border-radius:10px;padding:1px 6px;font-size:10px;margin-left:3px;display:none">0</span>
</button>
</div>
<div id="stab-body-kk">
<div class="ff"><label>Nama Kepala Keluarga</label><input type="text" id="f_kk" placeholder="Contoh: Budi Santoso" /></div>
<div class="ff">
<label>Klasifikasi Khusus (Bisa Pilih Lebih Dari Satu)</label>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:6px; font-size:11px; color:var(--navy); padding:4px 0;">
<label style="display:flex; align-items:center; gap:6px; text-transform:none; letter-spacing:normal;"><input type="checkbox" id="f_k_fakir" value="1" style="width:auto; margin:0;"> Fakir Miskin</label>
<label style="display:flex; align-items:center; gap:6px; text-transform:none; letter-spacing:normal;"><input type="checkbox" id="f_k_dhuafa" value="1" style="width:auto; margin:0;"> Dhuafa</label>
<label style="display:flex; align-items:center; gap:6px; text-transform:none; letter-spacing:normal;"><input type="checkbox" id="f_k_lansia" value="1" style="width:auto; margin:0;"> Janda/Lansia</label>
<label style="display:flex; align-items:center; gap:6px; text-transform:none; letter-spacing:normal;"><input type="checkbox" id="f_k_yatim" value="1" style="width:auto; margin:0;"> Anak Yatim Piatu</label>
<label style="display:flex; align-items:center; gap:6px; text-transform:none; letter-spacing:normal;"><input type="checkbox" id="f_k_bencana" value="1" style="width:auto; margin:0;"> Korban Bencana</label>
<label style="display:flex; align-items:center; gap:6px; text-transform:none; letter-spacing:normal;"><input type="checkbox" id="f_k_disab" value="1" style="width:auto; margin:0;"> Disabilitas</label>
</div>
</div>
<div class="ff"><label>Jumlah Anggota (Jiwa)</label><input type="number" id="f_jml" placeholder="Contoh: 4" min="1" /></div>
<div class="ff"><label>Alamat</label><input type="text" id="f_alm_m" placeholder="Alamat lengkap" /></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<div class="ff"><label>Tanggal Lahir KK</label><input type="date" id="f_tgl_lahir" style="font-size:12px;padding:7px 9px" /></div>
<div class="ff"><label>Pendidikan Terakhir</label>
<select id="f_pendidikan">
<option value="">— Pilih —</option>
<option>Tidak Sekolah</option><option>SD</option><option>SMP</option><option>SMA</option><option>D3</option><option>S1</option><option>S2</option><option>S3</option>
</select></div>
</div>
<div class="ff"><label>Pekerjaan</label><input type="text" id="f_pekerjaan" placeholder="Contoh: Buruh harian" /></div>
<div class="ff"><label>Riwayat Penyakit / Disabilitas</label><input type="text" id="f_penyakit" placeholder="Contoh: Diabetes, atau Tidak ada" /></div>
<div class="ff"><label>Kepemilikan Jaminan Sosial</label>
<select id="f_jaminan">
<option value="">— Pilih —</option>
<option>BPJS Kesehatan</option><option>KIS</option><option>BPJS Ketenagakerjaan</option><option>Tidak Ada</option>
</select></div>
<button onclick="switchSubTab('anggota')" style="width:100%;padding:8px;margin-top:4px;border-radius:var(--r3);border:1.5px dashed rgba(37,99,235,0.35);background:var(--blue-m);font-family:inherit;font-size:12px;font-weight:600;color:var(--blue-d);cursor:pointer;"> Tambah Data Anggota Keluarga</button>
</div>
<div id="stab-body-anggota" style="display:none">
<div id="anggota-list" style="margin-bottom:8px"></div>
<div style="background:var(--border-l);border:1.5px solid var(--border);border-radius:var(--r2);padding:10px;margin-bottom:8px">
<div style="font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:0.07em;margin-bottom:8px"> Anggota Baru</div>
<div class="ff"><label>Nama Anggota</label><input type="text" id="an_nama" placeholder="Nama lengkap" /></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<div class="ff"><label>Tanggal Lahir</label><input type="date" id="an_tgl" /></div>
<div class="ff"><label>Jenis Kelamin</label>
<select id="an_jk">
<option value="">— Pilih —</option><option>Laki-laki</option><option>Perempuan</option>
</select></div>
</div>
<div class="ff"><label>Hubungan dengan KK</label>
<select id="an_hub">
<option value="">— Pilih —</option><option>Istri/Suami</option><option>Anak</option><option>Orang Tua</option><option>Saudara</option><option>Lainnya</option>
</select></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<div class="ff"><label>Pendidikan</label>
<select id="an_pend">
<option value="">— Pilih —</option><option>Tidak Sekolah</option><option>SD</option><option>SMP</option><option>SMA</option><option>D3</option><option>S1</option><option>S2</option><option>S3</option>
</select></div>
<div class="ff"><label>Pekerjaan</label><input type="text" id="an_kerja" placeholder="Pekerjaan" /></div>
</div>
<div class="ff"><label>Riwayat Penyakit</label><input type="text" id="an_sakit" placeholder="Atau tulis Tidak ada" /></div>
<div class="ff"><label>Jaminan Sosial</label>
<select id="an_jamin">
<option value="">— Pilih —</option><option>BPJS Kesehatan</option><option>KIS</option><option>BPJS Ketenagakerjaan</option><option>Tidak Ada</option>
</select></div>
<button onclick="tambahAnggota()" style="width:100%;padding:8px;border:none;border-radius:var(--r3);background:var(--blue);color:white;font-weight:700;cursor:pointer;"> Masukkan ke Daftar</button>
</div>
</div>
</div>
<div class="mf">
<button class="btn-cncl" onclick="closeModal()">Batal</button>
<button class="btn-sv g" id="btnSv" onclick="simpanModal()" style="display:flex;align-items:center;justify-content:center;gap:6px;">
<svg width="13" height="13" 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>
Simpan ke Peta
</button>
</div>
</div>
</div>
<!-- ═══ MODAL LAPORAN WARGA ═══ -->
<div id="modal-laporan" style="display:none; position:fixed; inset:0; background:rgba(15,23,42,0.48); backdrop-filter:blur(6px); z-index:3000; align-items:center; justify-content:center;">
<div style="background:white; border-radius:var(--r); width:380px; max-width:calc(100vw - 24px); max-height:90vh; display:flex; flex-direction:column; box-shadow:0 24px 60px rgba(15,23,42,0.22); overflow:hidden; animation: mIn 0.24s cubic-bezier(0.34,1.5,0.64,1);">
<div style="padding:16px 16px 0; display:flex; align-items:flex-start; justify-content:space-between; flex-shrink:0;">
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:36px; height:36px; border-radius:var(--r3); background:linear-gradient(135deg,#F59E0B,#D97706); display:flex; align-items:center; justify-content:center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" 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>
<div>
<div style="font-size:14px; font-weight:700; color:var(--navy)">Lapor Warga</div>
<div style="font-size:10px; color:var(--text-3)" id="laporan-step-label">Langkah 1 dari 3</div>
</div>
</div>
<button onclick="tutupModalLaporan()" style="width:28px;height:28px;border:none;background:var(--border-l);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#475569" 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>
</div>
<div style="padding:10px 16px 0; flex-shrink:0;">
<div style="height:4px; background:var(--border-l); border-radius:4px; overflow:hidden;">
<div id="laporan-progress-bar" style="height:100%; background:linear-gradient(90deg,#F59E0B,#D97706); border-radius:4px; transition:width 0.4s ease; width:33%"></div>
</div>
</div>
<div style="flex:1; overflow-y:auto; padding:14px 16px 4px;">
<!-- Step 1 -->
<div id="laporan-step-1">
<div style="font-size:13px; font-weight:700; color:var(--navy); margin-bottom:4px;">Siapa yang dilaporkan?</div>
<div style="font-size:11px; color:var(--text-3); margin-bottom:14px;">Laporan akan diverifikasi admin sebelum masuk ke peta.</div>
<div style="display:flex; flex-direction:column; gap:8px;">
<div id="lbl-diri" onclick="pilihTipeLaporan('diri_sendiri')" style="display:flex;align-items:center;gap:12px;padding:14px;border-radius:var(--r2);border:2px solid var(--border);cursor:pointer;transition:all 0.18s;">
<div style="width:40px;height:40px;border-radius:50%;background:var(--blue-l);display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="2" stroke-linecap="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>
<div style="font-size:13px;font-weight:700;color:var(--navy)">Laporan Diri Sendiri</div>
<div style="font-size:11px;color:var(--text-3);margin-top:2px;">Saya yang membutuhkan bantuan</div>
</div>
</div>
<div id="lbl-lain" onclick="pilihTipeLaporan('orang_lain')" style="display:flex;align-items:center;gap:12px;padding:14px;border-radius:var(--r2);border:2px solid var(--green);background:var(--green-l);cursor:pointer;transition:all 0.18s;">
<div style="width:40px;height:40px;border-radius:50%;background:white;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2" stroke-linecap="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>
<div>
<div style="font-size:13px;font-weight:700;color:var(--navy)">Laporan Orang Lain</div>
<div style="font-size:11px;color:var(--text-3);margin-top:2px;">Saya melaporkan warga yang butuh bantuan</div>
</div>
</div>
</div>
</div>
<!-- Step 2 -->
<div id="laporan-step-2" style="display:none;">
<div class="ff"><label>Nama Kepala Keluarga *</label><input type="text" id="lap_nama_kk" placeholder="Nama lengkap KK"></div>
<div class="ff"><label>Alamat *</label><input type="text" id="lap_alamat" placeholder="Alamat lengkap"></div>
<div style="background:var(--border-l);border:1.5px solid var(--border);border-radius:var(--r3);padding:10px;margin-bottom:10px;">
<div style="font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:7px;">Titik Lokasi</div>
<div style="display:flex;gap:7px;margin-bottom:7px;">
<input type="text" id="lap_lat" placeholder="Latitude" style="flex:1;padding:7px 9px;font-size:12px;font-family:inherit;border:1.5px solid var(--border);border-radius:var(--r3);background:white;color:var(--navy);">
<input type="text" id="lap_lng" placeholder="Longitude" style="flex:1;padding:7px 9px;font-size:12px;font-family:inherit;border:1.5px solid var(--border);border-radius:var(--r3);background:white;color:var(--navy);">
</div>
<button onclick="gunakanLokasiGPS()" id="btn-gps" style="width:100%;padding:8px;border-radius:var(--r3);border:none;background:var(--blue);color:white;font-family:inherit;font-size:12px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/></svg>
Gunakan Lokasi Saya (GPS)
</button>
<div id="lap-lokasi-status" style="font-size:10px;color:var(--text-3);text-align:center;margin-top:5px;display:none;"></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div class="ff"><label>Jml Anggota</label><input type="number" id="lap_jml" placeholder="4" min="1" value="1"></div>
<div class="ff"><label>Pekerjaan</label><input type="text" id="lap_pekerjaan" placeholder="Pekerjaan KK"></div>
</div>
<div class="ff">
<label>Klasifikasi (Boleh Lebih dari Satu)</label>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;font-size:11px;color:var(--navy);padding:4px 0;">
<label style="display:flex;align-items:center;gap:6px;text-transform:none;letter-spacing:normal;"><input type="checkbox" id="lap_fakir" style="width:auto;margin:0;"> Fakir Miskin</label>
<label style="display:flex;align-items:center;gap:6px;text-transform:none;letter-spacing:normal;"><input type="checkbox" id="lap_dhuafa" style="width:auto;margin:0;"> Dhuafa</label>
<label style="display:flex;align-items:center;gap:6px;text-transform:none;letter-spacing:normal;"><input type="checkbox" id="lap_lansia" style="width:auto;margin:0;"> Janda/Lansia</label>
<label style="display:flex;align-items:center;gap:6px;text-transform:none;letter-spacing:normal;"><input type="checkbox" id="lap_yatim" style="width:auto;margin:0;"> Anak Yatim Piatu</label>
<label style="display:flex;align-items:center;gap:6px;text-transform:none;letter-spacing:normal;"><input type="checkbox" id="lap_bencana" style="width:auto;margin:0;"> Korban Bencana</label>
<label style="display:flex;align-items:center;gap:6px;text-transform:none;letter-spacing:normal;"><input type="checkbox" id="lap_disab" style="width:auto;margin:0;"> Disabilitas</label>
</div>
</div>
<div class="ff"><label>Keterangan Tambahan (Opsional)</label><input type="text" id="lap_keterangan" placeholder="Info tambahan untuk admin..."></div>
</div>
<!-- Step 3 -->
<div id="laporan-step-3" style="display:none;text-align:center;padding:10px 0 20px;">
<div style="font-size:42px;margin-bottom:12px;">✅</div>
<div style="font-size:15px;font-weight:800;color:var(--navy);margin-bottom:6px;">Laporan Terkirim!</div>
<div style="font-size:12px;color:var(--text-2);margin-bottom:16px;">Laporan Anda sedang menunggu verifikasi dari admin.</div>
<div style="background:var(--amber-l);border:1.5px solid rgba(245,158,11,0.3);border-radius:var(--r2);padding:14px;margin-bottom:14px;">
<div style="font-size:10px;font-weight:700;color:#B45309;text-transform:uppercase;letter-spacing:0.06em;margin-bottom:4px;">Nomor Referensi</div>
<div style="font-size:22px;font-weight:800;color:var(--navy);" id="lap-ref-number">#000000</div>
</div>
<button onclick="bukaStatusLaporan()" style="width:100%;padding:9px;border-radius:var(--r3);border:none;background:var(--border-l);font-family:inherit;font-size:12px;font-weight:700;color:var(--text-2);cursor:pointer;margin-bottom:8px;">
Cek Status Laporan Saya →
</button>
<button onclick="tutupModalLaporan()" style="width:100%;padding:9px;border-radius:var(--r3);border:1.5px solid var(--border);background:transparent;font-family:inherit;font-size:12px;font-weight:600;color:var(--text-3);cursor:pointer;">
Tutup
</button>
</div>
</div>
<div style="padding:12px 16px 16px;display:flex;gap:8px;background:white;border-top:1px solid var(--border-l);flex-shrink:0;">
<button id="btn-lap-back" onclick="laporanBack()" style="display:none;padding:9px 15px;border-radius:var(--r3);border:1.5px solid var(--border);background:var(--border-l);font-family:inherit;font-size:12px;font-weight:600;color:var(--text-2);cursor:pointer;">Kembali</button>
<button id="btn-lap-next" onclick="laporanNext()" style="flex:1;padding:9px;border-radius:var(--r3);border:none;background:var(--amber);color:white;font-family:inherit;font-size:13px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px;">
Lanjut →
</button>
</div>
</div>
</div>
<div id="modal-qr" style="display:none; position:fixed; inset:0; background:rgba(15,23,42,0.48); backdrop-filter:blur(6px); z-index:3000; align-items:center; justify-content:center;">
<div style="background:white; border-radius:var(--r); padding:24px 28px; width:300px; text-align:center; box-shadow:0 28px 64px rgba(15,23,42,0.28); animation: mIn 0.24s cubic-bezier(0.34,1.5,0.64,1);">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
<div style="display:flex;align-items:center;gap:10px">
<div style="width:34px;height:34px;background:linear-gradient(135deg,#F59E0B,#D97706);border-radius:10px;display:flex;align-items:center;justify-content:center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
</div>
<div style="text-align:left">
<div style="font-size:13px;font-weight:700;color:var(--navy)">Scan untuk Lapor</div>
<div style="font-size:10px;color:var(--text-3)">WebGIS Pemetaan Sosial</div>
</div>
</div>
<button onclick="closeQR()" style="width:28px;height:28px;border:none;background:var(--border-l);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#475569" 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>
</div>
<div style="background:var(--border-l); border:2px dashed var(--border); border-radius:var(--r2); padding:16px; margin-bottom:14px">
<div id="qr-canvas" style="display:flex;justify-content:center;align-items:center;width:180px;height:180px;margin:0 auto"></div>
</div>
<div style="background:var(--border-l); border-radius:var(--r3); padding:7px 10px; margin-bottom:14px; font-size:10px; font-weight:600; color:var(--text-3); word-break:break-all" id="qr-url-text">—</div>
<a id="qr-download" href="#" download="qr-lapor-warga.png" style="display:flex; align-items:center; justify-content:center; gap:7px; background:var(--navy); color:white; border-radius:var(--r3); padding:10px; font-size:12px; font-weight:700; text-decoration:none;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Download untuk Dicetak
</a>
</div>
</div>
<div class="gap-badge" id="gap-badge">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<span id="gap-badge-text">—</span>
</div>
<div id="toast"></div>
<script>
const USER_ROLE = '<?php echo $_SESSION['role'] ?? "warga"; ?>';
const USER_MASJID = '<?php echo $_SESSION['nama_masjid'] ?? ""; ?>';
const USER_ID = <?php echo $_SESSION['user_id'] ?? 0; ?>;
let tempLL = null;
let curTab = 'ibadah';
let ibData = [];
let miData = [];
let gapData = [];
let daftarAnggota = [];
function getIconIbadah(kat) {
if (!kat) return '🕌';
const k = kat.toLowerCase();
if (k.includes('gereja')) return '⛪';
if (k.includes('vihara')) return '🏯';
if (k.includes('pura')) return '🛕';
if (k.includes('kelenteng')) return '🏮';
return '🕌'; // Default Masjid/Mushola
}
// PETA
const map = L.map('map', { zoomControl: true }).setView([-0.0548, 109.3494], 14);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom:19, attribution:'© OpenStreetMap' }).addTo(map);
let lyI = L.layerGroup().addTo(map);
let lyM = L.layerGroup().addTo(map);
let lyGap = L.layerGroup();
let gapAktif = false;
L.control.layers(null, { "Rumah Ibadah": lyI, "Warga Miskin": lyM, "Zona Gap": lyGap }, { collapsed:true, position:'bottomright' }).addTo(map);
// Legenda
const legend = L.control({ position: 'bottomleft' });
legend.onAdd = function() {
const div = L.DomUtil.create('div');
div.style.cssText = 'background:rgba(255,255,255,0.96);padding:8px 12px;border-radius:10px;font-size:11px;font-family:Plus Jakarta Sans,sans-serif;box-shadow:0 2px 8px rgba(15,23,42,0.12);line-height:1.8;border:1px solid #E2E8F0';
div.innerHTML = `
<strong style="display:block;margin-bottom:4px;color:#0F172A">Warga Miskin</strong>
<span style="display:flex;align-items:center;gap:6px"><svg width="12" height="12"><circle cx="6" cy="6" r="5" fill="#EF4444"/></svg> Di luar jangkauan</span>
<span style="display:flex;align-items:center;gap:6px"><svg width="12" height="12"><circle cx="6" cy="6" r="5" fill="#10B981"/></svg> Di dalam jangkauan</span>`;
return div;
};
legend.addTo(map);
const mkG = new L.Icon({ iconUrl:'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl:'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize:[25,41], iconAnchor:[12,41], popupAnchor:[1,-34], shadowSize:[41,41] });
map.on('click', function(e) {
// 1. Walikota dan Warga tidak boleh menambah titik data sama sekali
if (USER_ROLE === 'warga' || USER_ROLE === 'walikota') {
toast('⚠️ Anda hanya memiliki akses untuk melihat data.', 'err');
return;
}
tempLL = e.latlng;
document.getElementById('mcoord').textContent = e.latlng.lat.toFixed(5) + ', ' + e.latlng.lng.toFixed(5);
// 2. Jika yang login adalah pengurus masjid, sembunyikan tab Rumah Ibadah
if (USER_ROLE === 'pengurus_masjid') {
document.getElementById('mt-ibadah').style.display = 'none';
switchTab('miskin'); // Langsung arahkan ke tab Warga Miskin
} else {
document.getElementById('mt-ibadah').style.display = 'flex';
switchTab('ibadah'); // Default untuk admin
}
document.getElementById('modal-backdrop').classList.add('show');
});
function closeModal() {
document.getElementById('modal-backdrop').classList.remove('show');
['f_nama','f_pic','f_alm_i','f_kk','f_jml','f_alm_m','f_tgl_lahir','f_pekerjaan','f_penyakit'].forEach(id => {
const e = document.getElementById(id); if(e) e.value='';
});
['f_pendidikan','f_jaminan'].forEach(id => {
const e = document.getElementById(id); if(e) e.selectedIndex = 0;
});
// PERBAIKAN: Bersihkan checkbox klasifikasi
['f_k_fakir','f_k_dhuafa','f_k_lansia','f_k_yatim','f_k_bencana','f_k_disab'].forEach(id => {
const e = document.getElementById(id); if(e) e.checked = false;
});
daftarAnggota = [];
renderDaftarAnggota();
resetFormAnggota();
switchSubTab('kk');
document.getElementById('radSlider').value = 500; onSlide(500);
}
function switchTab(tab) {
curTab = tab;
document.getElementById('mt-ibadah').className = 'mt' + (tab==='ibadah' ? ' a-green' : '');
document.getElementById('mt-miskin').className = 'mt' + (tab==='miskin' ? ' a-blue' : '');
document.getElementById('fb-ibadah').style.display = tab==='ibadah' ? 'block' : 'none';
document.getElementById('fb-miskin').style.display = tab==='miskin' ? 'block' : 'none';
const btn = document.getElementById('btnSv');
btn.className = tab==='ibadah' ? 'btn-sv g' : 'btn-sv b';
const micon = document.getElementById('micon');
if (tab === 'ibadah') {
micon.style.background = 'var(--green-l)';
micon.innerHTML = `<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="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>`;
} else {
micon.style.background = 'var(--blue-l)';
micon.innerHTML = `<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="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>`;
}
}
function onSlide(v) {
const n = parseInt(v);
document.getElementById('rvDisplay').textContent = n >= 1000 ? (n/1000).toFixed(1)+' km' : n+' m';
document.querySelectorAll('.pp').forEach(b => b.classList.toggle('active', parseInt(b.dataset.r)===n));
const slider = document.getElementById('radSlider');
const pct = ((n - 500) / (5000 - 500)) * 100;
slider.style.background = `linear-gradient(to right, var(--green) ${pct}%, rgba(16,185,129,0.2) ${pct}%)`;
}
function updateEditSlider(id, v) {
const n = parseInt(v);
const display = document.getElementById(`pe_rv_${id}`);
const slider = document.getElementById(`pe_r_${id}`);
// Update angka km / meter
if (display) {
display.textContent = n >= 1000 ? (n / 1000).toFixed(1) + ' km' : n + ' m';
}
// Update warna progres hijau pada bar
if (slider) {
const pct = ((n - 500) / (5000 - 500)) * 100;
slider.style.background = `linear-gradient(to right, var(--green) ${pct}%, rgba(16,185,129,0.2) ${pct}%)`;
}
}
function setPreset(v, el) {
document.getElementById('radSlider').value = v; onSlide(v);
document.querySelectorAll('.pp').forEach(b => b.classList.remove('active'));
el.classList.add('active');
}
function simpanModal() {
if (!tempLL) return;
// Cegah pengurus masjid menambahkan titik masjid baru (Hanya Admin)
if (curTab === 'ibadah' && USER_ROLE !== 'admin') {
toast('⚠️ Hanya Admin pusat yang dapat menambah data Rumah Ibadah.', 'err');
return;
}
const fd = new FormData();
fd.append('lat', tempLL.lat); fd.append('lng', tempLL.lng);
if (curTab === 'ibadah') {
// Tangkap elemen
const elNama = document.getElementById('f_nama');
const elKat = document.getElementById('f_kategori'); // 👈 Tangkap elemen dropdown
const elPic = document.getElementById('f_pic');
const elAlm = document.getElementById('f_alm_i');
const elRad = document.getElementById('radSlider');
// Ambil nilainya
const n = elNama ? elNama.value.trim() : '';
const k = elKat ? elKat.value : 'Masjid'; // 👈 Ambil value, default Masjid
const p = elPic ? elPic.value.trim() : '';
const a = elAlm ? elAlm.value.trim() : '';
const r = elRad ? elRad.value : 500;
if (!n || !p || !a) { toast('⚠️ Lengkapi semua field rumah ibadah', 'err'); return; }
// Masukkan ke paket kiriman
fd.append('nama_tempat', n);
fd.append('kategori', k); // 👈 Masukkan kategori ke API
fd.append('pic', p);
fd.append('alamat', a);
fd.append('radius', r);
fetch('api_ibadah.php?aksi=simpan', { method:'POST', body:fd })
.then(res => res.text())
.then(text => {
console.log("Respon Ibadah PHP:", text);
if(text.includes('Sukses')) {
closeModal(); muatData(); toast('✅ Rumah Ibadah ditambahkan!', 'ok');
} else {
toast('❌ ' + text, 'err');
}
}).catch(e => toast('❌ Error Jaringan','err'));
} else {
// VALIDASI GEO-FENCE KHUSUS PENGURUS MASJID
if (USER_ROLE === 'pengurus_masjid') {
const myMosque = ibData.find(ib => ib.nama_tempat === USER_MASJID);
if (myMosque) {
const jarak = haversine(tempLL.lat, tempLL.lng, parseFloat(myMosque.lat), parseFloat(myMosque.lng));
if (jarak > parseInt(myMosque.radius)) {
toast(`⚠️ Ditolak! Titik ini berjarak ${Math.round(jarak)}m (di luar radius ${myMosque.radius}m masjid Anda).`, 'err');
return;
}
} else {
toast('⚠️ Data masjid Anda tidak ditemukan di peta. Hubungi Admin.', 'err');
return;
}
}
const k = document.getElementById('f_kk') ? document.getElementById('f_kk').value.trim() : '';
const j = document.getElementById('f_jml') ? document.getElementById('f_jml').value : 0;
const a = document.getElementById('f_alm_m') ? document.getElementById('f_alm_m').value.trim() : '';
if (!k || !j || !a) { toast('⚠️ Lengkapi field Kepala Keluarga, Anggota, dan Alamat','err'); return; }
fd.append('nama_kk', k);
fd.append('jumlah_anggota', j);
fd.append('alamat', a);
fd.append('tanggal_lahir', document.getElementById('f_tgl_lahir') ? document.getElementById('f_tgl_lahir').value : '');
fd.append('pendidikan_terakhir', document.getElementById('f_pendidikan') ? document.getElementById('f_pendidikan').value : '');
fd.append('pekerjaan', document.getElementById('f_pekerjaan') ? document.getElementById('f_pekerjaan').value.trim() : '');
fd.append('riwayat_penyakit', document.getElementById('f_penyakit') ? document.getElementById('f_penyakit').value.trim() : '');
fd.append('kepemilikan_jaminan', document.getElementById('f_jaminan') ? document.getElementById('f_jaminan').value : '');
fd.append('anggota', JSON.stringify(daftarAnggota));
// Amankan pembacaan checkbox
const cekFakir = document.getElementById('f_k_fakir'); fd.append('is_fakir_miskin', (cekFakir && cekFakir.checked) ? 1 : 0);
const cekDhuafa= document.getElementById('f_k_dhuafa');fd.append('is_dhuafa', (cekDhuafa && cekDhuafa.checked) ? 1 : 0);
const cekLansia= document.getElementById('f_k_lansia');fd.append('is_lansia_tunggal', (cekLansia && cekLansia.checked) ? 1 : 0);
const cekYatim = document.getElementById('f_k_yatim'); fd.append('is_anak_yatim', (cekYatim && cekYatim.checked) ? 1 : 0);
const cekBencana=document.getElementById('f_k_bencana');fd.append('is_korban_bencana', (cekBencana && cekBencana.checked) ? 1 : 0);
const cekDisab = document.getElementById('f_k_disab'); fd.append('is_disabilitas', (cekDisab && cekDisab.checked) ? 1 : 0);
fetch('api_kemiskinan.php?aksi=simpan', { method:'POST', body:fd })
.then(res => res.text())
.then(text => {
console.log("Respon Warga PHP:", text);
if(text.includes('Sukses') || text.trim() === '') {
closeModal(); muatData(); toast('✅ Warga Miskin berhasil ditambahkan!', 'ok');
} else {
toast('❌ ' + text, 'err'); // Akan menampilkan error MySQL langsung di layar!
}
})
.catch(err => {
toast('❌ Gagal menghubungi server', 'err');
console.error(err);
});
}
}
function haversine(lat1, lng1, lat2, lng2) {
const R = 6371000;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLng = (lng2 - lng1) * Math.PI / 180;
const a = Math.sin(dLat/2)**2 + Math.cos(lat1 * Math.PI/180) * Math.cos(lat2 * 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 => {
const dist = haversine(lat, lng, parseFloat(ib.lat), parseFloat(ib.lng));
return dist <= parseInt(ib.radius);
});
}
function renderMiskin() {
lyM.clearLayers();
if (!miData || !Array.isArray(miData)) return;
miData.forEach(d => {
const lat = parseFloat(d.lat);
const lng = parseFloat(d.lng);
if (isNaN(lat) || isNaN(lng) || lat === 0 || lng === 0) {
console.warn("Koordinat dilewati karena tidak valid untuk KK:", d.nama_kk);
return;
}
const inside = isInsideAnyCircle(lat, lng);
const warna = inside ? '#10B981' : '#EF4444';
// 1. Buat markernya saja, JANGAN pasang bindPopup di sini
const marker = L.circleMarker([lat, lng], {
radius: inside ? 7 : 9,
color: warna,
fillColor: warna,
fillOpacity: inside ? 0.75 : 0.95,
weight: inside ? 2 : 3
}).addTo(lyM);
// 2. Pasang popup berdasarkan role
if (USER_ROLE !== 'warga') {
marker.bindPopup(popM(d));
} else {
marker.bindPopup(`<div class="lp-bd" style="font-family: 'Plus Jakarta Sans', sans-serif;">📌 <strong>Status Zona:</strong> Warga terdata di area ${inside ? '<span style="color:var(--green)">Terjangkau</span>' : '<span style="color:var(--red)">Blind Spot</span>'}.</div>`);
}
if (!inside) {
marker.once('add', () => {
const el = marker.getElement();
if (el) el.style.animation = 'pulseRing 2s ease-in-out infinite';
});
}
});
}
function muatData() {
fetch('api_ibadah.php?aksi=tampil').then(r => r.json()).then(data => {
lyI.clearLayers(); ibData = data;
data.forEach(d => {
L.circle([d.lat, d.lng], { color: '#10B981', fillColor: '#10B981', fillOpacity: 0.1, radius: parseInt(d.radius), weight: 1.5 }).addTo(lyI);
const m = L.marker([d.lat, d.lng], { icon: mkG, draggable: true }).addTo(lyI).bindPopup(popI(d));
m.on('dragend', e => updLoc('ibadah', d.id, e.target.getLatLng()));
});
renderMiskin(); updStats(); renderHist();
if (gapAktif) renderGapZones();
});
fetch('api_kemiskinan.php?aksi=tampil')
.then(r => r.json())
.then(data => {
lyM.clearLayers(); // Bersihkan layer lama agar tidak menumpuk
miData = data; // Masukkan data baru dari PHP ke global array
renderMiskin(); // Panggil penggambaran ulang titik lingkaran
updStats(); // Update papan statistik di atas peta
renderHist(); // Segarkan timeline histori kiri/kanan
})
.catch(e => console.error("Gagal sinkronisasi data spasial:", e));
}
function popI(d) {
const rl = d.radius >= 1000 ? (d.radius / 1000).toFixed(1) + 'km' : d.radius + 'm';
let rVal = parseInt(d.radius) || 500;
if (rVal < 500) rVal = 500;
if (rVal > 5000) rVal = 5000;
const pct = ((rVal - 500) / (5000 - 500)) * 100;
const bgGradient = `linear-gradient(to right, var(--green) ${pct}%, rgba(16,185,129,0.2) ${pct}%)`;
// Panggil fungsi Emoji dinamis
const katIbadah = d.kategori || 'Masjid';
const iconDynamic = getIconIbadah(katIbadah);
let tombolAdminHtml = '';
if (USER_ROLE === 'admin' || (USER_ROLE === 'pengurus_masjid' && USER_MASJID === d.nama_tempat)) {
tombolAdminHtml = `
<div class="lp-btns" id="pi_btn_view_${d.id}" style="display: grid !important; grid-template-columns: 1fr 1fr !important; gap: 5px;">
<button class="lp-bt" onclick="lpToggle('ibadah',${d.id})">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="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>
Edit
</button>
<button class="lp-bt dl" onclick="hapus('ibadah',${d.id})">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="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 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg>
Hapus
</button>
</div>
<div class="lp-btns" id="pi_btn_edit_${d.id}" style="display: none !important; grid-template-columns: 1fr 1fr !important; gap: 5px;">
<button class="lp-bt" onclick="lpToggle('ibadah',${d.id})">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Batal
</button>
<button class="lp-bt sv" onclick="lpUpdate('ibadah',${d.id})">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>
Selesai
</button>
</div>
`;
}
// Category badge color mapping
const katColors = { 'Masjid':'#10B981','Gereja':'#3B82F6','Vihara':'#F59E0B','Pura':'#F97316','Kelenteng':'#EC4899' };
const katColor = katColors[katIbadah] || '#10B981';
return `<div class="lp">
<div class="lp-hd" style="background: linear-gradient(135deg, ${katColor}18, ${katColor}08); border-bottom: 3px solid ${katColor}30; padding: 14px;">
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:36px; height:36px; border-radius:10px; background:${katColor}20; border:1.5px solid ${katColor}40; display:flex; align-items:center; justify-content:center; flex-shrink:0;">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="${katColor}" stroke-width="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>
<div style="flex:1; min-width:0;">
<div class="lp-ti" style="font-size:13px; font-weight:800; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${d.nama_tempat}</div>
<div style="display:flex; align-items:center; gap:5px; margin-top:3px;">
<span style="background:${katColor}; color:white; font-size:9px; font-weight:700; padding:2px 8px; border-radius:10px; letter-spacing:0.03em;">${katIbadah}</span>
<span style="font-size:10px; color:#64748B; display:flex; align-items:center; gap:3px;">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="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>
Radius: <strong style="color:${katColor}; font-weight:800;">${rl}</strong>
</span>
</div>
</div>
</div>
</div>
<div class="lp-bd" id="pi_${d.id}" style="display:grid; gap:6px; padding:12px 14px;">
<div style="display:flex; align-items:flex-start; gap:7px; background:#F8FAFC; border-radius:7px; padding:7px 9px;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#64748B" stroke-width="2" stroke-linecap="round" style="flex-shrink:0; margin-top:1px;"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<div style="line-height:1.4;"><span style="font-size:9px; font-weight:700; color:#94A3B8; text-transform:uppercase; letter-spacing:0.08em;">PIC / Pengurus</span><br><span style="font-size:11.5px; font-weight:600; color:#0F172A;">${d.pic}</span></div>
</div>
<div style="display:flex; align-items:flex-start; gap:7px; background:#F8FAFC; border-radius:7px; padding:7px 9px;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#64748B" stroke-width="2" stroke-linecap="round" style="flex-shrink:0; margin-top:1px;"><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 style="line-height:1.4;"><span style="font-size:9px; font-weight:700; color:#94A3B8; text-transform:uppercase; letter-spacing:0.08em;">Alamat</span><br><span style="font-size:11.5px; font-weight:600; color:#0F172A;">${d.alamat}</span></div>
</div>
</div>
<div class="lp-ef" id="pif_${d.id}" style="display:none">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Nama Tempat</label>
<input class="lp-in" id="pe_n_${d.id}" value="${d.nama_tempat}" placeholder="Nama Tempat">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Kategori</label>
<select class="lp-in" id="pe_k_${d.id}">
<option value="Masjid" ${katIbadah=='Masjid'?'selected':''}>Masjid</option>
<option value="Gereja" ${katIbadah=='Gereja'?'selected':''}>Gereja</option>
<option value="Vihara" ${katIbadah=='Vihara'?'selected':''}>Vihara</option>
<option value="Pura" ${katIbadah=='Pura'?'selected':''}>Pura</option>
<option value="Kelenteng" ${katIbadah=='Kelenteng'?'selected':''}>Kelenteng</option>
</select>
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">PIC / Pengurus</label>
<input class="lp-in" id="pe_p_${d.id}" value="${d.pic}" placeholder="Nama Pengurus">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Alamat</label>
<input class="lp-in" id="pe_a_${d.id}" value="${d.alamat}" placeholder="Alamat Lengkap">
<div style="background: var(--border-l); border: 1.5px solid var(--border); border-radius: var(--r3); padding: 10px; margin-top: 8px; margin-bottom: 8px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<label style="font-size:10px; font-weight:700; color:var(--navy); margin:0;">Radius Jangkauan</label>
<span id="pe_rv_${d.id}" style="background:var(--green); color:white; font-size:9px; font-weight:700; padding:2px 8px; border-radius:10px;">${rl}</span>
</div>
<input type="range" id="pe_r_${d.id}" min="500" max="5000" step="50" value="${rVal}" style="background: ${bgGradient};" oninput="updateEditSlider(${d.id}, this.value)">
</div>
</div>
${tombolAdminHtml}
</div>`;
}
function popM(d) {
const sel = (val, opt) => val === opt ? 'selected' : '';
const chk = (val) => val == 1 ? 'checked' : '';
// AMANKAN KELUARGA: Hitung jumlah jiwa secara dinamis dari string anggota keluarga
let jumlahJiwa = d.jumlah_anggota || 0;
try {
if (d.anggota) {
const angArr = typeof d.anggota === 'string' ? JSON.parse(d.anggota) : d.anggota;
if (angArr && angArr.length > 0) {
jumlahJiwa = angArr.length + 1;
}
}
} catch(e) {
console.log("Format anggota belum berupa array/JSON valid saat render titik");
}
const infoBantuan = d.status_bantuan || "Warga ini belum pernah menerima bantuan.";
// PROSES TAG KLASIFIKASI UNTUK MUNCUL DI ATAS INFO ANGGOTA
let tags = [];
if (d.is_fakir_miskin == 1) tags.push({ label: 'Fakir Miskin', color: '#dc2626' });
if (d.is_dhuafa == 1) tags.push({ label: 'Dhuafa', color: '#9333ea' });
if (d.is_lansia_tunggal == 1) tags.push({ label: 'Janda/Lansia', color: '#d97706' });
if (d.is_anak_yatim == 1) tags.push({ label: 'Yatim Piatu', color: '#0891b2' });
if (d.is_korban_bencana == 1) tags.push({ label: 'Bencana', color: '#ea580c' });
if (d.is_disabilitas == 1) tags.push({ label: 'Disabilitas', color: '#0284c7' });
let klasifikasiHtmlPopup = '';
if (tags.length > 0) {
klasifikasiHtmlPopup = `<div style="display:flex; flex-wrap:wrap; gap:4px; margin-bottom:10px;">` +
tags.map(t => `<span style="background:${t.color}20; color:${t.color}; border:1px solid ${t.color}40; font-size:9px; font-weight:700; padding:2px 8px; border-radius:10px; letter-spacing:0.02em;">${t.label}</span>`).join('') +
`</div>`;
}
// ==========================================
// LOGIKA GEO-FENCING (BATASAN RADIUS MASJID)
// ==========================================
let masukRadiusSaya = false;
if (USER_ROLE === 'pengurus_masjid') {
const myMosque = ibData.find(ib => ib.nama_tempat === USER_MASJID);
if (myMosque) {
// Hitung jarak warga ke masjid pengurus
const jarak = haversine(parseFloat(d.lat), parseFloat(d.lng), parseFloat(myMosque.lat), parseFloat(myMosque.lng));
// Jika jaraknya lebih kecil atau sama dengan radius masjid, tandai TRUE
if (jarak <= parseInt(myMosque.radius)) {
masukRadiusSaya = true;
}
}
}
// Penentuan Hak Akses Edit/Hapus
let btnEditHapusHtml = '';
let isBisaEdit = false;
// Admin bisa edit semua. Pengurus HANYA bisa edit jika warga di dalam radiusnya.
if (USER_ROLE === 'admin' || (USER_ROLE === 'pengurus_masjid' && masukRadiusSaya)) {
isBisaEdit = true;
btnEditHapusHtml = `
<button class="lp-bt" onclick="lpToggle('miskin',${d.id})" style="display:flex;align-items:center;justify-content:center;gap:4px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="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>
Edit
</button>
<button class="lp-bt dl" onclick="hapus('miskin',${d.id})" style="display:flex;align-items:center;justify-content:center;gap:4px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="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 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg>
Hapus
</button>
`;
}
// Penentuan Hak Akses Tombol Tambah Histori Bantuan
let btnBantuanHtml = '';
if (USER_ROLE === 'admin' || (USER_ROLE === 'pengurus_masjid' && masukRadiusSaya)) {
btnBantuanHtml = `
<button class="lp-bt" style="grid-column: 1 / -1 !important; width: 100%; margin-top: 3px; background: linear-gradient(135deg,#2563EB,#1D4ED8); color: white; border: none; display:flex; align-items:center; justify-content:center; gap:5px;" onclick="bukaFormBantuan(${d.id})">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="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>
Tambah Histori Bantuan
</button>
`;
}
// Penentuan jumlah kolom tabel agar tombol responsif
const gridKolom = isBisaEdit ? '1fr 1fr 1fr' : '1fr';
// Determine status
const insidePop = isInsideAnyCircle(parseFloat(d.lat), parseFloat(d.lng));
const statusColor = insidePop ? '#10B981' : '#EF4444';
const statusLabel = insidePop ? 'Terjangkau' : 'Blind Spot';
const statusBg = insidePop ? '#ECFDF5' : '#FEF2F2';
const statusBorder= insidePop ? '#6EE7B7' : '#FECACA';
return `<div class="lp">
<div class="lp-hd" style="background: linear-gradient(135deg,#1E40AF12,#2563EB08); border-bottom: 3px solid #2563EB22; padding:14px;">
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:36px; height:36px; border-radius:10px; background:#2563EB18; border:1.5px solid #2563EB35; display:flex; align-items:center; justify-content:center; flex-shrink:0;">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="2" stroke-linecap="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>
<div style="flex:1; min-width:0;">
<div class="lp-ti" style="font-size:13px; font-weight:800; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${d.nama_kk}</div>
<div style="display:flex; align-items:center; gap:5px; margin-top:3px;">
<span style="background:${statusColor}; color:white; font-size:9px; font-weight:700; padding:2px 8px; border-radius:10px;">${statusLabel}</span>
<span style="font-size:10px; color:#64748B; display:flex; align-items:center; gap:3px;">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
<strong>${jumlahJiwa} jiwa</strong>
</span>
</div>
</div>
</div>
</div>
<div style="background:${statusBg}; border:1px solid ${statusBorder}; padding:8px 12px; margin:10px 14px 0; border-radius:8px; font-size:10px; color:${statusColor}; font-weight:600; display:flex; align-items:flex-start; gap:6px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" style="flex-shrink:0; margin-top:1px;"><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>${infoBantuan}</span>
</div>
<div class="lp-bd" id="pm_${d.id}" style="padding:12px 14px;">
${klasifikasiHtmlPopup}
<div style="display:flex; flex-direction:column; gap:6px;">
<div style="display:flex; align-items:flex-start; gap:7px; background:#F8FAFC; border-radius:7px; padding:7px 9px;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#64748B" stroke-width="2" stroke-linecap="round" style="flex-shrink:0; margin-top:1px;"><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 style="line-height:1.4;"><span style="font-size:9px; font-weight:700; color:#94A3B8; text-transform:uppercase; letter-spacing:0.08em;">Alamat</span><br><span style="font-size:11.5px; font-weight:600; color:#0F172A;">${d.alamat}</span></div>
</div>
</div>
</div>
<div class="lp-ef" id="pmf_${d.id}" style="display: none;">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Nama Kepala Keluarga</label>
<input class="lp-in" id="me_k_${d.id}" value="${d.nama_kk || ''}">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Jumlah Anggota</label>
<input class="lp-in" type="number" id="me_j_${d.id}" value="${d.jumlah_anggota || ''}" min="1">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Alamat</label>
<input class="lp-in" id="me_a_${d.id}" value="${d.alamat || ''}">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Tanggal Lahir</label>
<input class="lp-in" type="date" id="me_tl_${d.id}" value="${d.tanggal_lahir || ''}">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Pendidikan Terakhir</label>
<select class="lp-in" id="me_pd_${d.id}">
<option value="">— Pilih —</option>
${['Tidak Sekolah','SD','SMP','SMA','D3','S1','S2','S3'].map(opt => `<option value="${opt}" ${sel(d.pendidikan_terakhir, opt)}>${opt}</option>`).join('')}
</select>
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Pekerjaan</label>
<input class="lp-in" id="me_pk_${d.id}" value="${d.pekerjaan || ''}">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Riwayat Penyakit</label>
<input class="lp-in" id="me_rp_${d.id}" value="${d.riwayat_penyakit || ''}">
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-bottom:2px;">Jaminan Sosial</label>
<select class="lp-in" id="me_js_${d.id}">
<option value="">— Pilih —</option>
${['BPJS Kesehatan','KIS','BPJS Ketenagakerjaan','Tidak Ada'].map(opt => `<option value="${opt}" ${sel(d.kepemilikan_jaminan, opt)}>${opt}</option>`).join('')}
</select>
<label style="font-size:10px; font-weight:700; color:var(--navy); display:block; margin-top:8px; margin-bottom:4px;">Klasifikasi Khusus</label>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:6px; font-size:10px; margin-bottom:10px;">
<label style="display:flex; align-items:center; gap:4px;"><input type="checkbox" id="me_k_fakir_${d.id}" style="width:auto; margin:0;" ${chk(d.is_fakir_miskin)}> Fakir Miskin</label>
<label style="display:flex; align-items:center; gap:4px;"><input type="checkbox" id="me_k_dhuafa_${d.id}" style="width:auto; margin:0;" ${chk(d.is_dhuafa)}> Dhuafa</label>
<label style="display:flex; align-items:center; gap:4px;"><input type="checkbox" id="me_k_lansia_${d.id}" style="width:auto; margin:0;" ${chk(d.is_lansia_tunggal)}> Janda/Lansia</label>
<label style="display:flex; align-items:center; gap:4px;"><input type="checkbox" id="me_k_yatim_${d.id}" style="width:auto; margin:0;" ${chk(d.is_anak_yatim)}> Yatim Piatu</label>
<label style="display:flex; align-items:center; gap:4px;"><input type="checkbox" id="me_k_bencana_${d.id}" style="width:auto; margin:0;" ${chk(d.is_korban_bencana)}> Bencana</label>
<label style="display:flex; align-items:center; gap:4px;"><input type="checkbox" id="me_k_disab_${d.id}" style="width:auto; margin:0;" ${chk(d.is_disabilitas)}> Disabilitas</label>
</div>
</div>
<div class="lp-btns" id="pm_btn_view_${d.id}" style="display: grid !important; grid-template-columns: ${gridKolom} !important; gap: 5px;">
<button class="lp-bt" onclick="lihatDetailMiskin(${d.id})" style="display:flex;align-items:center;justify-content:center;gap:4px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Detail
</button>
${btnEditHapusHtml}
${btnBantuanHtml}
</div>
<div class="lp-btns" id="pm_btn_edit_${d.id}" style="display: none !important; grid-template-columns: 1fr 1fr !important; gap: 5px;">
<button class="lp-bt" onclick="lpToggle('miskin',${d.id})" style="display:flex;align-items:center;justify-content:center;gap:4px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Batal
</button>
<button class="lp-bt sv" onclick="lpUpdate('miskin',${d.id})" style="display:flex;align-items:center;justify-content:center;gap:4px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>
Selesai
</button>
</div>
</div>`;
}
function lpUpdate(jenis, id) {
const fd = new FormData();
fd.append('id', id);
let url = '';
if (jenis === 'ibadah') {
fd.append('nama_tempat', document.getElementById(`pe_n_${id}`).value);
// TAMBAHKAN BARIS INI UNTUK MENANGKAP KATEGORI EDIT
fd.append('kategori', document.getElementById(`pe_k_${id}`).value);
fd.append('pic', document.getElementById(`pe_p_${id}`).value);
fd.append('alamat', document.getElementById(`pe_a_${id}`).value);
fd.append('radius', document.getElementById(`pe_r_${id}`).value);
url = 'api_ibadah.php?aksi=update';
} else {
// 1. Ambil data teks utama dengan pengaman (mencegah JS crash jika elemen telat dimuat)
const elNama = document.getElementById(`me_k_${id}`);
const elJml = document.getElementById(`me_j_${id}`);
const elAlm = document.getElementById(`me_a_${id}`);
const elTgl = document.getElementById(`me_tl_${id}`);
const elPend = document.getElementById(`me_pd_${id}`);
const elPek = document.getElementById(`me_pk_${id}`);
const elPeny = document.getElementById(`me_rp_${id}`);
const elJam = document.getElementById(`me_js_${id}`);
fd.append('nama_kk', elNama ? elNama.value : '');
fd.append('jumlah_anggota', elJml ? elJml.value : 0);
fd.append('alamat', elAlm ? elAlm.value : '');
fd.append('tanggal_lahir', elTgl ? elTgl.value : '');
fd.append('pendidikan_terakhir', elPend ? elPend.value : '');
fd.append('pekerjaan', elPek ? elPek.value : '');
fd.append('riwayat_penyakit', elPeny ? elPeny.value : '');
fd.append('kepemilikan_jaminan', elJam ? elJam.value : '');
// 2. KUNCI PERBAIKAN: Tangkap data checkbox klasifikasi khusus saat Edit
const cekFakir = document.getElementById(`me_k_fakir_${id}`);
const cekDhuafa = document.getElementById(`me_k_dhuafa_${id}`);
const cekLansia = document.getElementById(`me_k_lansia_${id}`);
const cekYatim = document.getElementById(`me_k_yatim_${id}`);
const cekBencana = document.getElementById(`me_k_bencana_${id}`);
const cekDisab = document.getElementById(`me_k_disab_${id}`);
fd.append('is_fakir_miskin', (cekFakir && cekFakir.checked) ? 1 : 0);
fd.append('is_dhuafa', (cekDhuafa && cekDhuafa.checked) ? 1 : 0);
fd.append('is_lansia_tunggal', (cekLansia && cekLansia.checked) ? 1 : 0);
fd.append('is_anak_yatim', (cekYatim && cekYatim.checked) ? 1 : 0);
fd.append('is_korban_bencana', (cekBencana && cekBencana.checked) ? 1 : 0);
fd.append('is_disabilitas', (cekDisab && cekDisab.checked) ? 1 : 0);
url = 'api_kemiskinan.php?aksi=update';
}
// 3. Kirim ke PHP dan muat ulang peta
fetch(url, { method: 'POST', body: fd })
.then(res => res.text()) // Ambil teks asli dari PHP untuk debugging jika ada error
.then(text => {
console.log("Respon Update PHP:", text);
if(text.includes('Sukses') || text.trim() === '') {
map.closePopup();
muatData();
toast('✅ Data diperbarui!','ok');
} else {
toast('❌ Gagal Update. Lihat Console.', 'err');
}
})
.catch(err => {
console.error("Kesalahan Fetch:", err);
toast('❌ Gagal memperbarui data', 'err');
});
}
// =========================================
// FUNGSI TOGGLE & UPDATE UNTUK POPUP PETA
// =========================================
function lpToggle(jenis, id) {
const prefix = jenis === 'ibadah' ? 'pi' : 'pm';
const infoText = document.getElementById(`${prefix}_${id}`);
const formInput = document.getElementById(`${prefix}f_${id}`);
const btnView = document.getElementById(`${prefix}_btn_view_${id}`);
const btnEdit = document.getElementById(`${prefix}_btn_edit_${id}`);
if (!formInput) {
console.error(`[DEBUG] Elemen dengan ID ${prefix}f_${id} tidak ditemukan!`);
toast('⚠️ Terjadi kesalahan UI.', 'err');
return;
}
// Cek apakah form sedang tersembunyi
const isFormHidden = (formInput.style.display === 'none' || formInput.style.display === '');
if (isFormHidden) {
// Mode Edit: Sembunyikan teks, Munculkan form
if (infoText) infoText.style.display = 'none';
formInput.style.setProperty('display', 'block', 'important');
if (btnView) btnView.style.setProperty('display', 'none', 'important');
if (btnEdit) btnEdit.style.setProperty('display', 'grid', 'important');
} else {
// Mode Batal/View: Munculkan teks, Sembunyikan form
if (infoText) infoText.style.display = 'block';
formInput.style.setProperty('display', 'none', 'important');
if (btnView) btnView.style.setProperty('display', 'grid', 'important');
if (btnEdit) btnEdit.style.setProperty('display', 'none', 'important');
}
}
function lpUpdate(jenis, id) {
const fd = new FormData();
fd.append('id', id);
let url = '';
if (jenis === 'ibadah') {
fd.append('nama_tempat', document.getElementById(`pe_n_${id}`).value);
// Mengunci pengiriman kategori agar tidak hilang saat update
fd.append('kategori', document.getElementById(`pe_k_${id}`).value);
fd.append('pic', document.getElementById(`pe_p_${id}`).value);
fd.append('alamat', document.getElementById(`pe_a_${id}`).value);
fd.append('radius', document.getElementById(`pe_r_${id}`).value);
url = 'api_ibadah.php?aksi=update';
} else {
// Ambil data teks utama warga miskin dengan pengaman
const elNama = document.getElementById(`me_k_${id}`);
const elJml = document.getElementById(`me_j_${id}`);
const elAlm = document.getElementById(`me_a_${id}`);
const elTgl = document.getElementById(`me_tl_${id}`);
const elPend = document.getElementById(`me_pd_${id}`);
const elPek = document.getElementById(`me_pk_${id}`);
const elPeny = document.getElementById(`me_rp_${id}`);
const elJam = document.getElementById(`me_js_${id}`);
fd.append('nama_kk', elNama ? elNama.value : '');
fd.append('jumlah_anggota', elJml ? elJml.value : 0);
fd.append('alamat', elAlm ? elAlm.value : '');
fd.append('tanggal_lahir', elTgl ? elTgl.value : '');
fd.append('pendidikan_terakhir', elPend ? elPend.value : '');
fd.append('pekerjaan', elPek ? elPek.value : '');
fd.append('riwayat_penyakit', elPeny ? elPeny.value : '');
fd.append('kepemilikan_jaminan', elJam ? elJam.value : '');
// Tangkap data checkbox klasifikasi khusus saat Edit
const cekFakir = document.getElementById(`me_k_fakir_${id}`);
const cekDhuafa = document.getElementById(`me_k_dhuafa_${id}`);
const cekLansia = document.getElementById(`me_k_lansia_${id}`);
const cekYatim = document.getElementById(`me_k_yatim_${id}`);
const cekBencana = document.getElementById(`me_k_bencana_${id}`);
const cekDisab = document.getElementById(`me_k_disab_${id}`);
fd.append('is_fakir_miskin', (cekFakir && cekFakir.checked) ? 1 : 0);
fd.append('is_dhuafa', (cekDhuafa && cekDhuafa.checked) ? 1 : 0);
fd.append('is_lansia_tunggal', (cekLansia && cekLansia.checked) ? 1 : 0);
fd.append('is_anak_yatim', (cekYatim && cekYatim.checked) ? 1 : 0);
fd.append('is_korban_bencana', (cekBencana && cekBencana.checked) ? 1 : 0);
fd.append('is_disabilitas', (cekDisab && cekDisab.checked) ? 1 : 0);
url = 'api_kemiskinan.php?aksi=update';
}
// Kirim paket data ke server PHP
fetch(url, { method: 'POST', body: fd })
.then(res => res.text())
.then(text => {
console.log("Respon Update PHP:", text);
if(text.includes('Sukses') || text.trim() === '') {
map.closePopup();
muatData();
toast('✅ Data diperbarui!', 'ok');
} else {
toast('❌ Gagal Update. Lihat Console.', 'err');
}
})
.catch(err => {
console.error("Kesalahan Fetch:", err);
toast('❌ Gagal memperbarui data', 'err');
});
}
function hapus(jenis, id) {
if (!confirm('Yakin hapus data ini?')) return;
const fd = new FormData(); fd.append('id',id);
const url = jenis==='ibadah' ? 'api_ibadah.php?aksi=hapus' : 'api_kemiskinan.php?aksi=hapus';
fetch(url,{method:'POST',body:fd}).then(()=>{ map.closePopup(); muatData(); toast('🗑️ Data dihapus',''); });
}
function updLoc(jenis, id, ll) {
if (!confirm('Simpan posisi baru?')) { muatData(); return; }
const fd = new FormData(); fd.append('id',id); fd.append('lat',ll.lat); fd.append('lng',ll.lng);
const url = jenis==='ibadah' ? 'api_ibadah.php?aksi=update_lokasi' : 'api_kemiskinan.php?aksi=update_lokasi';
fetch(url,{method:'POST',body:fd}).then(()=>{ muatData(); toast('📍 Lokasi diperbarui','ok'); });
}
function updStats() {
const jiwa = miData.reduce((a,d)=>a+parseInt(d.jumlah_anggota||0),0);
document.getElementById('cI').textContent = ibData.length;
document.getElementById('cM').textContent = miData.length;
document.getElementById('cJ').textContent = jiwa;
}
function renderHist() {
const el = document.getElementById('histList');
fetch('api_kemiskinan.php?aksi=tampil_bantuan')
.then(r => r.json())
.then(logBantuan => {
// PRIVASI: Jika warga, sembunyikan riwayat spasial pendaftaran warga
const historiSpasialMiskin = USER_ROLE === 'warga' ? [] : miData.map(d => ({
tipe: 'spasial_miskin',
nama: d.nama_kk,
meta: `Titik Warga Miskin baru terdata · ${d.jumlah_anggota} jiwa`,
lat: d.lat,
lng: d.lng,
icon: '🏘️',
bg: '#ddf0fb',
htmlKanan: USER_ROLE === 'admin' ? `<button class="hi-del" onclick="hapus('miskin',${d.id})">🗑</button>` : ''
}));
const historiSpasial = [
...ibData.map(d => ({
tipe: 'spasial_ibadah',
nama: d.nama_tempat,
meta: `Rumah Ibadah baru ditambahkan · Radius ${d.radius >= 1000 ? (d.radius/1000).toFixed(1)+'km' : d.radius+'m'}`,
lat: d.lat,
lng: d.lng,
icon: '🕌',
bg: '#d7f5e7',
htmlKanan: USER_ROLE === 'admin' ? `<button class="hi-del" onclick="hapus('ibadah',${d.id})">🗑</button>` : ''
})),
...historiSpasialMiskin
];
// PRIVASI: Jika warga, sembunyikan semua riwayat log bansos spesifik nama orang lain
const historiBansos = USER_ROLE === 'warga' ? [] : logBantuan.map(b => ({
tipe: 'bantuan',
nama: `Bantuan: ${b.jenis_bantuan}`,
meta: `Disalurkan ke KK ${b.nama_kk} oleh ${b.sumber_bantuan} (${b.tanggal})`,
lat: b.lat,
lng: b.lng,
icon: '🎁',
bg: '#fff7ed',
htmlKanan: `<span style="font-size: 10px; font-weight: 700; color: #c2410c; background: #ffedd5; padding: 2px 6px; border-radius: 10px; white-space: nowrap;">Log</span>`
}));
const allHistori = [...historiSpasial, ...historiBansos];
if (!allHistori.length) {
el.innerHTML = '<div class="empty-state"><div class="es-icon">📭</div>Belum ada aktivitas data atau bantuan</div>';
return;
}
el.innerHTML = allHistori.map(d => `
<div class="hi" style="border-left: 3px solid ${d.tipe === 'bantuan' ? '#f97316' : 'transparent'};">
<div class="hi-icon" style="background:${d.bg}">${d.icon}</div>
<div class="hi-info" onclick="flyClose(${d.lat},${d.lng})">
<div class="hi-name" style="font-weight:700; color:var(--navy);">${d.nama}</div>
<div class="hi-meta" style="font-size:10px; margin-top:2px;">${d.meta}</div>
</div>
<div style="display:flex; align-items:center; flex-shrink:0;">
${d.htmlKanan}
</div>
</div>
`).join('');
})
.catch(err => {
console.error("Gagal memuat histori bantuan:", err);
});
}
function doSearch(q) {
const el = document.getElementById('searchResults');
const qn = q.trim().toLowerCase();
if (!qn) { el.innerHTML='<div class="empty-state"><div class="es-icon">🗺️</div>Ketik untuk mencari lokasi</div>'; return; }
// PRIVASI: Jika role-nya warga, kosongkan array data pencarian warga miskin
const dataWargaPencarian = USER_ROLE === 'warga' ? [] : miData.map(d=>({ jenis:'miskin', nama:d.nama_kk, meta:`${d.jumlah_anggota} jiwa`, lat:d.lat, lng:d.lng }));
const res = [
...ibData.map(d=>({ jenis:'ibadah', nama:d.nama_tempat, meta:`r=${d.radius>=1000?(d.radius/1000).toFixed(1)+'km':d.radius+'m'}`, lat:d.lat, lng:d.lng })),
...dataWargaPencarian
].filter(d=>d.nama.toLowerCase().includes(qn));
if (!res.length) { el.innerHTML='<div class="empty-state"><div class="es-icon">🔍</div>Tidak ditemukan</div>'; return; }
el.innerHTML = res.slice(0,8).map(d=>`
<div class="sr-item" onclick="flyClose(${d.lat},${d.lng})">
<div class="sr-icon" style="background:${d.jenis==='ibadah'?'var(--green-l)':'var(--blue-l)'}">${d.jenis==='ibadah'?'🕌':'🏘️'}</div>
<div><div class="sr-name">${d.nama}</div><div class="sr-sub">${d.meta}</div></div>
</div>`).join('');
}
function flyClose(lat, lng) { closePanel(); setTimeout(() => map.flyTo([lat,lng], 17, {animate:true, duration:1.2}), 220); }
function openPanel(n) {
document.querySelectorAll('.slide-panel').forEach(p=>p.classList.remove('open'));
document.getElementById('panel-'+n).classList.add('open');
document.getElementById('panel-backdrop').classList.add('show');
if (n==='cari') setTimeout(()=>document.getElementById('searchInput').focus(), 340);
}
function closePanel() {
document.querySelectorAll('.slide-panel').forEach(p=>p.classList.remove('open'));
document.getElementById('panel-backdrop').classList.remove('show');
}
function toast(msg, type) {
const t = document.getElementById('toast'); t.textContent = msg; t.className = 'show' + (type ? ' '+type : ''); setTimeout(()=>t.className='', 3000);
}
let qrInstance = null;
function openModal(type) {
if (type !== 'qr') return;
const url = window.location.href.replace(/\/[^/]*$/, '') + '/lapor.php';
document.getElementById('qr-url-text').textContent = url;
const container = document.getElementById('qr-canvas'); container.innerHTML = '';
qrInstance = new QRCode(container, { text: url, width: 180, height: 180, colorDark: '#0F172A', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.H });
setTimeout(() => {
const img = container.querySelector('img') || container.querySelector('canvas');
if (img) document.getElementById('qr-download').href = img.tagName === 'CANVAS' ? img.toDataURL('image/png') : img.src;
}, 300);
document.getElementById('modal-qr').style.display = 'flex';
}
function closeQR() { document.getElementById('modal-qr').style.display = 'none'; }
document.getElementById('modal-qr').addEventListener('click', function(e) { if (e.target === this) closeQR(); });
function switchSubTab(tab) {
const isKK = tab === 'kk';
document.getElementById('stab-body-kk').style.display = isKK ? 'block' : 'none';
document.getElementById('stab-body-anggota').style.display = isKK ? 'none' : 'block';
const styleAktif = 'flex:1;padding:6px;border-radius:6px;font-family:inherit;font-size:11px;font-weight:700;cursor:pointer;transition:all 0.15s;border:1.5px solid ';
document.getElementById('stab-kk').style.cssText = styleAktif + (isKK ? 'var(--blue);background:var(--blue-l);color:var(--blue-d)' : 'var(--border);background:var(--border-l);color:var(--text-3)');
document.getElementById('stab-anggota').style.cssText = styleAktif + (!isKK ? 'var(--blue);background:var(--blue-l);color:var(--blue-d)' : 'var(--border);background:var(--border-l);color:var(--text-3)');
}
function tambahAnggota() {
const nama = document.getElementById('an_nama').value.trim();
const hub = document.getElementById('an_hub').value;
if (!nama) { toast('⚠️ Nama anggota wajib diisi', 'err'); return; }
if (!hub) { toast('⚠️ Pilih hubungan dengan KK', 'err'); return; }
const anggota = { nama, tanggal_lahir: document.getElementById('an_tgl').value || '', jenis_kelamin: document.getElementById('an_jk').value || '', hubungan_kk: hub, pendidikan_terakhir: document.getElementById('an_pend').value || '', pekerjaan: document.getElementById('an_kerja').value.trim() || '', riwayat_penyakit: document.getElementById('an_sakit').value.trim() || '', kepemilikan_jaminan: document.getElementById('an_jamin').value || '' };
daftarAnggota.push(anggota); renderDaftarAnggota(); resetFormAnggota(); toast('✅ ' + nama + ' ditambahkan', 'ok');
}
function renderDaftarAnggota() {
const el = document.getElementById('anggota-list'); const badge = document.getElementById('badge-anggota');
badge.style.display = daftarAnggota.length ? 'inline' : 'none'; badge.textContent = daftarAnggota.length;
if (!daftarAnggota.length) { el.innerHTML = '<div style="text-align:center;font-size:11px;color:var(--text-3);padding:8px 0">Belum ada anggota ditambahkan</div>'; return; }
el.innerHTML = daftarAnggota.map((a, i) => {
let usiaTxt = ''; if (a.tanggal_lahir) { const usia = Math.floor((new Date() - new Date(a.tanggal_lahir)) / (365.25*24*3600*1000)); usiaTxt = ` · ${usia} thn`; }
return `<div style="display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--r3);border:1px solid var(--border-l);background:var(--surface-s);margin-bottom:5px"><div style="width:30px;height:30px;border-radius:var(--r3);background:var(--blue-l);display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0">${a.jenis_kelamin==='Perempuan'?'👩':'👨'}</div><div style="flex:1;min-width:0"><div style="font-size:12px;font-weight:700;color:var(--navy);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${a.nama}</div><div style="font-size:10px;color:var(--text-3);margin-top:1px">${a.hubungan_kk}${usiaTxt}${a.pendidikan_terakhir?' · '+a.pendidikan_terakhir:''}</div></div><button onclick="hapusAnggota(${i})" style="width:24px;height:24px;border:none;background:var(--red-l);border-radius:var(--r3);cursor:pointer;font-size:11px;color:var(--red);flex-shrink:0">🗑</button></div>`;
}).join('');
}
function hapusAnggota(index) { daftarAnggota.splice(index, 1); renderDaftarAnggota(); }
function resetFormAnggota() { ['an_nama','an_tgl','an_kerja','an_sakit'].forEach(id => document.getElementById(id).value = ''); ['an_jk','an_hub','an_pend','an_jamin'].forEach(id => document.getElementById(id).selectedIndex = 0); }
function lihatDetailMiskin(id) {
const d = miData.find(x => x.id == id);
if (!d) return;
let anggotaHtml = '';
try {
const ang = typeof d.anggota === 'string' ? JSON.parse(d.anggota) : (d.anggota || []);
if (ang.length === 0) {
anggotaHtml = '<div style="font-size:11px;color:var(--text-3);text-align:center;padding:10px 0;background:var(--border-l);border-radius:var(--r3);">Tidak ada data anggota keluarga lain.</div>';
} else {
anggotaHtml = ang.map((a, i) => {
let usiaTxt = '';
if (a.tanggal_lahir) {
const tglLahir = new Date(a.tanggal_lahir);
const tglSekarang = new Date();
let usia = tglSekarang.getFullYear() - tglLahir.getFullYear();
const bulan = tglSekarang.getMonth() - tglLahir.getMonth();
if (bulan < 0 || (bulan === 0 && tglSekarang.getDate() < tglLahir.getDate())) { usia--; }
usiaTxt = ` (${usia} tahun)`;
}
return `
<div style="background:var(--border-l); border:1px solid var(--border); padding:10px; border-radius:var(--r3); margin-bottom:6px; font-size:11px; line-height:1.5;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<strong style="color:var(--navy);font-size:12px;">${i+1}. ${a.nama}</strong>
<span style="color:var(--blue); font-weight:700; background:var(--blue-l); padding:1px 7px; border-radius:10px; font-size:9px;">${a.hubungan_kk}</span>
</div>
<div style="margin-top:5px; color:var(--text-2);">
<strong>Lahir:</strong> ${a.tanggal_lahir || '-'} ${usiaTxt} | ${a.jenis_kelamin || '-'}<br>
<strong>Pekerjaan:</strong> ${a.pekerjaan || '-'}<br>
<strong>Kesehatan:</strong> ${a.riwayat_penyakit || '-'} | <strong>Jaminan:</strong> ${a.kepemilikan_jaminan || '-'}
</div>
</div>`;
}).join('');
}
} catch(e) {
anggotaHtml = '<div style="font-size:11px;color:var(--red);">Format data anggota tidak valid.</div>';
}
const panelInfoBantuan = d.status_bantuan || "Belum ada rekam jejak bantuan tercatat.";
// Proses Label Klasifikasi Khusus Untuk Panel
let tags = [];
if (d.is_fakir_miskin == 1) tags.push('Fakir Miskin');
if (d.is_dhuafa == 1) tags.push('Dhuafa');
if (d.is_lansia_tunggal == 1) tags.push('Janda/Duda Lansia');
if (d.is_anak_yatim == 1) tags.push('Anak Yatim Piatu');
if (d.is_korban_bencana == 1) tags.push('Korban Bencana');
if (d.is_disabilitas == 1) tags.push('Penyandang Disabilitas');
let klasifikasiHtml = tags.length > 0
? tags.map(t => `<span style="background:var(--red); color:white; padding:3px 8px; border-radius:12px; font-size:9px; font-weight:700; display:inline-block; margin-right:4px; margin-bottom:4px;">⚠️ ${t}</span>`).join('')
: '';
// Susun struktur HTML utama
const html = `
<div style="margin-bottom:16px;">
<div style="font-size:16px;font-weight:700;color:var(--navy);">${d.nama_kk}</div>
<div style="margin-top:6px; margin-bottom:6px;">${klasifikasiHtml}</div>
<div style="font-size:12px;color:var(--text-3);">📍 ${d.alamat}</div>
</div>
<div style="background: #fff7ed; border: 1px solid #ffedd5; padding: 10px 12px; border-radius: var(--r3); font-size: 11px; color: #c2410c; font-weight: 600; margin-bottom: 15px; display: flex; align-items: center; gap: 8px;">
🎁 ${panelInfoBantuan}
</div>
<table style="width:100%; font-size:12px; margin-bottom:18px; border-collapse:collapse;">
<tr style="border-bottom:1px solid var(--border-l);"><td style="padding:6px 0;color:var(--text-2);">Jml Anggota</td><td style="font-weight:700;color:var(--navy);text-align:right;">${d.jumlah_anggota} Jiwa</td></tr>
<tr style="border-bottom:1px solid var(--border-l);"><td style="padding:6px 0;color:var(--text-2);">Tgl Lahir KK</td><td style="font-weight:600;color:var(--navy);text-align:right;">${d.tanggal_lahir || '-'}</td></tr>
<tr style="border-bottom:1px solid var(--border-l);"><td style="padding:6px 0;color:var(--text-2);">Pendidikan</td><td style="font-weight:600;color:var(--navy);text-align:right;">${d.pendidikan_terakhir || '-'}</td></tr>
<tr style="border-bottom:1px solid var(--border-l);"><td style="padding:6px 0;color:var(--text-2);">Pekerjaan</td><td style="font-weight:600;color:var(--navy);text-align:right;">${d.pekerjaan || '-'}</td></tr>
<tr style="border-bottom:1px solid var(--border-l);"><td style="padding:6px 0;color:var(--text-2);">Riwayat Sakit</td><td style="font-weight:600;color:var(--navy);text-align:right;">${d.riwayat_penyakit || '-'}</td></tr>
<tr style="border-bottom:1px solid var(--border-l);"><td style="padding:6px 0;color:var(--text-2);">Jaminan Sosial</td><td style="font-weight:600;color:var(--navy);text-align:right;">${d.kepemilikan_jaminan || '-'}</td></tr>
</table>
<div style="font-size:12px; font-weight:700; color:var(--navy); margin-bottom:8px; border-bottom:1.5px solid var(--border-l); padding-bottom:4px;">👨‍👩‍👧 Daftar Anggota Keluarga</div>
<div style="margin-bottom:18px;">${anggotaHtml}</div>
<div style="font-size:12px; font-weight:700; color:var(--navy); margin-bottom:8px; border-bottom:1.5px solid var(--border-l); padding-bottom:4px;">📋 Histori Log Bantuan Keluarga</div>
<div id="panel-bantuan-kk-list" style="font-size:11px; color:var(--text-3);">Memuat riwayat bantuan...</div>
`;
document.getElementById('detail-content').innerHTML = html;
openPanel('detail');
fetch('api_kemiskinan.php?aksi=tampil_bantuan')
.then(r => r.json())
.then(logBantuan => {
const listEl = document.getElementById('panel-bantuan-kk-list');
const historiSpesifik = logBantuan.filter(b => b.warga_id == id);
if (historiSpesifik.length === 0) {
listEl.innerHTML = '<div style="text-align:center; padding:10px 0; color:var(--text-3); background:var(--border-l); border-radius:var(--r3);">Keluarga ini belum tercatat menerima bantuan spesifik apapun.</div>';
return;
}
listEl.innerHTML = historiSpesifik.map(b => `
<div style="display:flex; flex-direction:column; background:#fff7ed; border:1px solid #ffedd5; padding:8px 10px; border-radius:var(--r3); margin-bottom:5px;">
<div style="display:flex; justify-content:space-between; font-weight:700; color:#c2410c;">
<span>🎁 ${b.jenis_bantuan}</span>
<span style="font-size:10px; opacity:0.75; font-weight:500;">${b.tanggal}</span>
</div>
<div style="font-size:10px; color:var(--text-2); margin-top:4px;">
Penyalur: <strong>${b.sumber_bantuan}</strong>
</div>
</div>
`).join('');
})
.catch(err => {
console.error(err);
document.getElementById('panel-bantuan-kk-list').innerHTML = '<div style="color:var(--red); font-size:11px;">Gagal menarik data histori bantuan keluarga.</div>';
});
}
// =========================================
// COVERAGE GAP DETECTION — ZONA BLIND SPOT
// =========================================
function clusterPoints(points, radiusMeter) {
const clusters = [];
const visited = new Set();
points.forEach((p, i) => {
if (visited.has(i)) return;
const cluster = [p];
visited.add(i);
points.forEach((q, j) => {
if (visited.has(j)) return;
const dist = haversine(
parseFloat(p.lat), parseFloat(p.lng),
parseFloat(q.lat), parseFloat(q.lng)
);
if (dist <= radiusMeter) {
cluster.push(q);
visited.add(j);
}
});
clusters.push(cluster);
});
return clusters;
}
function renderGapZones() {
lyGap.clearLayers();
// 1. Saring warga yang TIDAK terjangkau oleh ibadah manapun
const uncovered = miData.filter(d => {
const lat = parseFloat(d.lat);
const lng = parseFloat(d.lng);
if (isNaN(lat) || isNaN(lng) || lat === 0 || lng === 0) return false;
return !isInsideAnyCircle(lat, lng);
});
if (uncovered.length === 0) {
toast('✅ Tidak ada zona blind spot — semua warga terjangkau!', 'ok');
updateGapBadge(0, 0, 0);
return;
}
// 2. Kelompokkan titik berdekatan ke dalam cluster (radius 600m)
const clusters = clusterPoints(uncovered, 600);
// 3. Visualisasi tiap cluster sebagai zona
clusters.forEach(cluster => {
if (cluster.length < 2) return; // ← skip zona yang isinya cuma 1 KK
// Hitung titik tengah (centroid)
const cLat = cluster.reduce((s, p) => s + parseFloat(p.lat), 0) / cluster.length;
const cLng = cluster.reduce((s, p) => s + parseFloat(p.lng), 0) / cluster.length;
// Hitung radius zona berdasarkan sebaran titik + buffer
const maxDist = Math.max(
150,
...cluster.map(p => haversine(cLat, cLng, parseFloat(p.lat), parseFloat(p.lng)))
);
const zoneRadius = maxDist + 180;
// Tentukan warna berdasarkan jumlah KK tidak terjangkau
const n = cluster.length;
const color = n >= 5 ? '#dc2626' : n >= 3 ? '#f97316' : '#f59e0b';
const label = n >= 5 ? 'Kritis' : n >= 3 ? 'Sedang' : 'Rendah';
const totalJiwa = cluster.reduce((s, d) => s + parseInt(d.jumlah_anggota || 0), 0);
const daftarKK = cluster.map(d =>
`<div style="padding:3px 0;border-bottom:1px solid rgba(0,0,0,0.05)">
• <strong>${d.nama_kk}</strong> — ${d.jumlah_anggota} jiwa
</div>`
).join('');
// Zona transparan dengan garis putus-putus
L.circle([cLat, cLng], {
radius: zoneRadius,
color: color,
fillColor: color,
fillOpacity: 0.12,
weight: 2.5,
dashArray: '8, 5'
}).addTo(lyGap).bindPopup(`
<div style="font-family:'Plus Jakarta Sans',sans-serif; min-width:220px">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
<div style="width:36px;height:36px;background:${color}20;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:18px">🚨</div>
<div>
<div style="font-size:14px;font-weight:700;color:#0F172A">Zona Blind Spot</div>
<div style="font-size:10px;background:${color};color:white;border-radius:10px;padding:1px 8px;font-weight:700;display:inline-block">
Risiko ${label}
</div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:10px">
<div style="background:${color}15;border-radius:8px;padding:8px;text-align:center">
<div style="font-size:20px;font-weight:800;color:${color}">${n}</div>
<div style="font-size:9px;color:#0F172A;font-weight:600">KK Tidak Terjangkau</div>
</div>
<div style="background:rgba(15,23,42,0.05);border-radius:8px;padding:8px;text-align:center">
<div style="font-size:20px;font-weight:800;color:#0F172A">${totalJiwa}</div>
<div style="font-size:9px;color:#0F172A;font-weight:600">Total Jiwa</div>
</div>
</div>
<div style="font-size:11px;color:rgba(15,23,42,0.7);margin-bottom:10px;max-height:110px;overflow-y:auto">
${daftarKK}
</div>
<div style="background:#fef3c7;border-radius:7px;padding:7px 10px;font-size:10px;color:#92400e;font-weight:600">
💡 Rekomendasi: Pertimbangkan pendirian pos bantuan atau program jangkauan baru di sekitar area ini.
</div>
</div>
`, { maxWidth: 260 });
// Titik centroid zona (lebih terlihat di peta kecil)
L.circleMarker([cLat, cLng], {
radius: 10,
color: color,
fillColor: color,
fillOpacity: 1,
weight: 3
}).addTo(lyGap);
});
// 4. Update badge ringkasan
const totalJiwaTidakTerjangkau = uncovered.reduce((s, d) => s + parseInt(d.jumlah_anggota || 0), 0);
updateGapBadge(clusters.length, uncovered.length, totalJiwaTidakTerjangkau);
}
function updateGapBadge(zona, kk, jiwa) {
const badge = document.getElementById('gap-badge');
const text = document.getElementById('gap-badge-text');
if (!gapAktif || zona === 0) { badge.style.display = 'none'; return; }
badge.style.display = 'flex';
text.textContent = `${zona} zona · ${kk} KK · ${jiwa} jiwa tidak terjangkau`;
}
function toggleGapLayer() {
const fabEl = document.getElementById('fab-gap');
const textEl = document.getElementById('fab-gap-text');
if (gapAktif) {
lyGap.remove();
gapAktif = false;
fabEl.classList.remove('gap-active');
textEl.textContent = 'Blind Spot';
document.getElementById('gap-badge').style.display = 'none';
toast('Layer Zona Blind Spot disembunyikan', '');
} else {
if (miData.length === 0) {
toast('Belum ada data warga untuk dianalisis', 'err');
return;
}
renderGapZones();
lyGap.addTo(map);
gapAktif = true;
fabEl.classList.add('gap-active');
textEl.textContent = 'Sembunyikan Zona';
}
}
// =========================================
// FITUR DONASI DIGITAL (QRIS & TRANSFER)
// =========================================
function prosesDonasi() {
const jenisEl = document.getElementById('donasi_jenis');
const nominalEl = document.getElementById('donasi_nominal');
const namaEl = document.getElementById('donasi_nama');
const pesanEl = document.getElementById('donasi_pesan');
const tujuanEl = document.getElementById('donasi_tujuan');
if (!nominalEl) return;
const jenis = jenisEl ? jenisEl.value : 'Uang Tunai';
const isUang = (jenis === 'Uang Tunai');
const nilaiInput = nominalEl.value.trim();
const nama = namaEl ? namaEl.value.trim() || 'Hamba Allah' : 'Hamba Allah';
const pesan = pesanEl ? pesanEl.value.trim() : '';
const tujuanId = tujuanEl ? tujuanEl.value : '0';
const metodeInput = document.querySelector('input[name="metode_donasi"]:checked');
const metode = metodeInput ? metodeInput.value : 'QRIS';
// Validasi berbeda tergantung jenis donasi
if (isUang) {
const nominal = parseInt(nilaiInput);
if (!nilaiInput || isNaN(nominal) || nominal < 10000) {
toast('⚠️ Minimal donasi uang adalah Rp 10.000', 'err');
return;
}
} else {
if (!nilaiInput) {
toast('⚠️ Mohon isi deskripsi barang yang akan didonasikan', 'err');
return;
}
}
const modalBody = document.getElementById('donasi-modal-body');
// Donasi barang: tampilkan halaman konfirmasi tanpa QRIS/Transfer
if (!isUang) {
modalBody.innerHTML = `
<div style="text-align:center; padding: 16px 0;">
<div style="font-size: 32px; margin-bottom: 8px;">📦</div>
<div style="font-weight:700; color:var(--navy); margin-bottom:6px;">Terima Kasih, ${nama}!</div>
<div style="font-size:12px; color:var(--text-2); margin-bottom:16px;">
Donasi <strong>${jenis}: ${nilaiInput}</strong> akan kami koordinasikan.
</div>
<div style="background:var(--border-l); border-radius:var(--r2); padding:12px; font-size:11px; color:var(--text-2); text-align:left; margin-bottom:16px;">
📞 Tim kami akan menghubungi Anda untuk koordinasi pengiriman.<br>
📍 Atau bisa langsung ke rumah ibadah/pos bantuan terdekat.
</div>
</div>
<div class="ff">
<label>Upload Foto Barang (Opsional)</label>
<input type="file" id="donasi_bukti" accept="image/*" style="background:white; padding:6px;">
</div>
<button class="btn-sv g" style="width:100%; margin-top:10px; padding:10px;"
onclick="konfirmasiDonasi('Barang', 0, '${nama.replace(/'/g,"\\'")}', '${pesan.replace(/'/g,"\\'")}', ${tujuanId}, '${jenis}', '${nilaiInput.replace(/'/g,"\\'")}')">
Konfirmasi Donasi Barang ✅
</button>
<button class="btn-cncl" style="width:100%; margin-top:8px; border:none; background:transparent;"
onclick="closeModalDonasi()">Batalkan</button>
`;
return;
}
// Donasi uang: tampilkan instruksi QRIS atau Transfer
const nominal = parseInt(nilaiInput);
let instruksiHtml = '';
if (metode === 'QRIS') {
instruksiHtml = `
<div style="text-align:center; padding-top:10px;">
<div style="font-weight:700; color:var(--navy); margin-bottom:10px;">Scan QRIS di bawah ini</div>
<div style="background:#fff; padding:10px; border-radius:var(--r2); display:inline-block; border:1.5px solid var(--border); margin-bottom:10px;">
<img src="https://upload.wikimedia.org/wikipedia/commons/d/d0/QR_code_for_mobile_English_Wikipedia.svg" width="160" height="160" alt="QRIS">
</div>
<div style="font-size:11px; color:var(--text-3);">Total Pembayaran</div>
<div style="font-size:22px; font-weight:800; color:var(--green);">Rp ${nominal.toLocaleString('id-ID')}</div>
</div>`;
} else {
instruksiHtml = `
<div style="text-align:center; padding-top:10px;">
<div style="font-weight:700; color:var(--navy); margin-bottom:10px;">Transfer ke Rekening Bank</div>
<div style="background:var(--blue-l); border:1.5px dashed var(--blue); padding:15px; border-radius:var(--r2); margin-bottom:10px;">
<div style="font-size:11px; color:var(--navy);">Bank Mandiri</div>
<div style="font-size:18px; font-weight:800; color:var(--blue-d); letter-spacing:1px; margin:4px 0;">123-456-789-0123</div>
<div style="font-size:11px; font-weight:700; color:var(--navy);">a.n. Webgis Poverty</div>
</div>
<div style="font-size:11px; color:var(--text-3);">Total Pembayaran</div>
<div style="font-size:22px; font-weight:800; color:var(--green);">Rp ${nominal.toLocaleString('id-ID')}</div>
</div>`;
}
modalBody.innerHTML = `
${instruksiHtml}
<div style="margin-top:20px; border-top:1.5px solid var(--border-l); padding-top:15px;">
<div class="ff">
<label>Upload Bukti Transfer</label>
<input type="file" id="donasi_bukti" accept="image/*" style="background:white; padding:6px;">
</div>
<button class="btn-sv g" style="width:100%; margin-top:10px; padding:10px;"
onclick="konfirmasiDonasi('${metode}', ${nominal}, '${nama.replace(/'/g,"\\'")}', '${pesan.replace(/'/g,"\\'")}', ${tujuanId}, 'Uang Tunai', '')">
Selesai & Konfirmasi ✅
</button>
<button class="btn-cncl" style="width:100%; margin-top:8px; border:none; background:transparent;"
onclick="closeModalDonasi()">Batalkan</button>
</div>
`;
}
// =========================================
// FITUR DONASI DIGITAL (PILIHAN RUMAH IBADAH)
// =========================================
function openModalDonasi() {
const icon = document.getElementById('mdl-icon');
const title = document.getElementById('mdl-title-container');
if(icon) icon.textContent = '💖';
if(title) title.innerHTML = `<div style="font-size:14px;font-weight:700;color:var(--navy)">Salurkan Donasi</div>`;
let opsiIbadah = `<option value="0">— Umum (Bebaskan Penyaluran) —</option>`;
if (ibData && ibData.length > 0) {
ibData.forEach(ib => {
opsiIbadah += `<option value="${ib.id}"> ${ib.nama_tempat}</option>`;
});
}
document.getElementById('donasi-modal-body').innerHTML = `
<div class="ff">
<label>Jenis Donasi</label>
<select id="donasi_jenis" style="width:100%; padding:8px 11px; font-size:13px; border:1.5px solid var(--border); border-radius:var(--r3); color:var(--navy); background:var(--border-l);" onchange="toggleJenisDonasi(this.value)">
<option value="Uang Tunai">💵 Uang Tunai</option>
<option value="Sembako">🛒 Sembako</option>
<option value="Bantuan Kesehatan">🏥 Bantuan Kesehatan</option>
<option value="Bantuan Pendidikan">📚 Bantuan Pendidikan</option>
<option value="Pakaian">👕 Pakaian</option>
<option value="Lainnya">📦 Lainnya</option>
</select>
</div>
<div id="metode-pembayaran-box" style="background: var(--border-l); border: 1.5px dashed rgba(16,185,129,0.3); border-radius: var(--r2); padding: 16px; text-align: center; margin-bottom: 16px;">
<div style="font-size: 12px; font-weight: 700; color: var(--navy); margin-bottom: 4px;">Pilih Metode Pembayaran</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;">
<label style="border: 1.5px solid var(--blue); border-radius: var(--r3); padding: 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px; background: var(--blue-l);">
<input type="radio" name="metode_donasi" value="QRIS" checked style="margin: 0;">
<span style="font-size: 11px; font-weight: 700; color: var(--navy);">QRIS</span>
</label>
<label style="border: 1.5px solid var(--border); border-radius: var(--r3); padding: 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px; background: white;">
<input type="radio" name="metode_donasi" value="Transfer Bank" style="margin: 0;">
<span style="font-size: 11px; font-weight: 700; color: var(--navy);">Transfer Bank</span>
</label>
</div>
</div>
<div class="ff">
<label>Tujuan Penyaluran Bantuan</label>
<select id="donasi_tujuan" style="width:100%; padding:8px 11px; font-size:13px; border:1.5px solid var(--border); border-radius:var(--r3); color:var(--navy); background:var(--border-l);">
${opsiIbadah}
</select>
</div>
<div class="ff">
<label id="label_nominal">Nominal Donasi (Rp)</label>
<input type="text" id="donasi_nominal" placeholder="Contoh: 50000">
</div>
<div class="ff"><label>Nama Donatur</label><input type="text" id="donasi_nama" placeholder="Nama Anda (Boleh Anonim)"></div>
<div class="ff"><label>Pesan Dukungan (Opsional)</label><input type="text" id="donasi_pesan" placeholder="Tuliskan doa atau pesan..."></div>
<button class="btn-sv g" style="width: 100%; margin-top: 14px; padding: 10px;" onclick="prosesDonasi()">Lanjutkan Pembayaran</button>
`;
document.getElementById('modal-donasi').style.display = 'flex';
}
function toggleJenisDonasi(jenis) {
const boxMetode = document.getElementById('metode-pembayaran-box');
const labelNominal = document.getElementById('label_nominal');
const inputNominal = document.getElementById('donasi_nominal');
if (jenis === 'Uang Tunai') {
boxMetode.style.display = 'block';
labelNominal.textContent = 'Nominal Donasi (Rp)';
inputNominal.placeholder = 'Contoh: 50000';
} else {
boxMetode.style.display = 'none';
labelNominal.textContent = 'Deskripsi & Jumlah Barang';
inputNominal.placeholder = 'Contoh: 3 Paket Sembako / 5 Karung Beras';
}
}
function bukaFormBantuan(wargaId) {
// Tutup popup marker agar tampilan lebih bersih
map.closePopup();
// AMANKAN NAMA: Cari data nama_kk secara mandiri dari array miData berdasarkan wargaId
const d = miData.find(x => x.id == wargaId);
if (!d) {
toast('⚠️ Data warga tidak ditemukan', 'err');
return;
}
const namaKk = d.nama_kk;
const modalBody = document.getElementById('donasi-modal-body');
// Ubah ikon dan judul modal agar sesuai dengan konteks Pencatatan Bantuan
const icon = document.getElementById('mdl-icon');
const title = document.getElementById('mdl-title-container');
if(icon) icon.textContent = '📝';
const namaAman = namaKk.replace(/'/g, "\\'");
if(title) title.innerHTML = `<div style="font-size:14px;font-weight:700;color:var(--navy)">Catat Bantuan:<br><span style="color:var(--blue); font-size:12px;">${namaAman}</span></div>`;
// Tarik data rumah ibadah untuk dijadikan opsi penyalur
let opsiPenyalur = '';
if (USER_ROLE === 'pengurus_masjid') {
// Kunci dropdown HANYA untuk masjid pengurus tersebut
opsiPenyalur = `<option value="${USER_MASJID}" selected>🕌 ${USER_MASJID}</option>`;
} else {
// Jika Admin, tampilkan semua pilihan
opsiPenyalur = `<option value="">— Pilih Penyalur (Rumah Ibadah) —</option>`;
if (ibData && ibData.length > 0) {
ibData.forEach(ib => {
opsiPenyalur += `<option value="${ib.nama_tempat}">🕌${ib.nama_tempat}</option>`;
});
}
opsiPenyalur += `<option value="Donatur Pribadi / Anonim">👤 Donatur Pribadi / Anonim</option>`;
opsiPenyalur += `<option value="Lainnya">📦 Lainnya</option>`;
}
modalBody.innerHTML = `
<div class="ff">
<label>Jenis Bantuan</label>
<select id="hb_jenis" style="width:100%; padding:8px 11px; font-size:13px; border:1.5px solid var(--border); border-radius:var(--r3); color:var(--navy); background:var(--border-l);">
<option value="">— Pilih Jenis —</option>
<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="5">📦 Lainnya</option>
</select>
</div>
<div class="ff">
<label>Tanggal Distribusi</label>
<input type="date" id="hb_tgl" value="${new Date().toISOString().split('T')[0]}">
</div>
<div class="ff">
<label>Nama Penyalur / Sumber Bantuan</label>
<select id="hb_penyalur" style="width:100%; padding:8px 11px; font-size:13px; border:1.5px solid var(--border); border-radius:var(--r3); color:var(--navy); background:var(--border-l);">
${opsiPenyalur}
</select>
</div>
<div class="ff">
<label>Deskripsi Bantuan</label>
<input type="text" id="hb_deskripsi" placeholder="Contoh: Rp 500.000 / 2 Paket Sembako / 5 Kg Beras">
</div>
<div class="ff">
<label>Keterangan / Catatan</label>
<input type="text" id="hb_catatan" placeholder="Contoh: Bantuan rutin bulanan">
</div>
<button class="btn-sv b" style="width:100%; margin-top:14px; padding:10px;"
onclick="simpanHistoriBantuan(${wargaId})">💾 Simpan Data Bantuan</button>
<button class="btn-cncl" style="width:100%; margin-top:8px; border:none; background:transparent;"
onclick="closeModalDonasi()">Batalkan</button>
`;
document.getElementById('modal-donasi').style.display = 'flex';
}
function closeModalDonasi() {
document.getElementById('modal-donasi').style.display = 'none';
}
// Klik area luar modal untuk menutup
document.getElementById('modal-donasi').addEventListener('click', function(e) {
if (e.target === this) closeModalDonasi();
});
function konfirmasiDonasi(metode, nominal, nama, pesan, tujuanId, jenisDonasi, deskripsiBarang) {
const fileInput = document.getElementById('donasi_bukti');
const isUang = (jenisDonasi === 'Uang Tunai');
// Bukti wajib hanya untuk donasi uang
if (isUang && (!fileInput.files || fileInput.files.length === 0)) {
toast('⚠️ Harap upload bukti transfer terlebih dahulu', 'err');
return;
}
const fd = new FormData();
fd.append('metode', metode);
fd.append('nominal', nominal);
fd.append('nama', nama);
fd.append('pesan', pesan);
fd.append('ibadah_id', tujuanId);
fd.append('jenis_donasi', jenisDonasi);
fd.append('deskripsi_barang', deskripsiBarang);
if (fileInput.files && fileInput.files.length > 0) {
fd.append('bukti', fileInput.files[0]);
}
const btn = event.target;
const originalText = btn.innerHTML;
btn.innerHTML = '⏳ Sedang Memproses...';
btn.disabled = true;
// Kirim ke api_donasi.php?aksi=simpan agar tidak ambigu
fetch('api_donasi.php?aksi=simpan', { method: 'POST', body: fd })
.then(r => r.json())
.then(res => {
btn.disabled = false;
if (res.status === 'sukses') {
toast('🎉 Terima kasih! Donasi Anda sedang diproses oleh admin.', 'ok');
setTimeout(() => closeModalDonasi(), 1500);
} else {
btn.innerHTML = originalText;
toast('❌ ' + res.pesan, 'err');
}
})
.catch(err => {
console.error(err);
btn.innerHTML = originalText;
btn.disabled = false;
toast('❌ Gagal menghubungi server', 'err');
});
}
function bukaTransparansi() {
openPanel('transparansi');
const listEl = document.getElementById('list-distribusi');
listEl.innerHTML = '<div class="empty-state"><div class="es-icon">⏳</div>Memuat data...</div>';
fetch('api_distribusi.php?aksi=global')
.then(r => r.json())
.then(res => {
window.dataLogGlobal = res.log_distribusi;
document.getElementById('trans-masuk').textContent = 'Rp ' + parseInt(res.total_donasi).toLocaleString('id-ID');
document.getElementById('trans-donatur').textContent = `Dari ${res.jumlah_donatur} Donatur`;
if (!res.log_distribusi || res.log_distribusi.length === 0) {
listEl.innerHTML = '<div class="empty-state"><div class="es-icon">📭</div>Belum ada riwayat penyaluran donasi.</div>';
return;
}
listEl.innerHTML = res.log_distribusi.map(d => {
// KUNCI FILTER: Tombol Edit & Hapus hanya dibuat jika USER_ROLE adalah admin
let tombolAksiAdmin = '';
if (USER_ROLE === 'admin') {
tombolAksiAdmin = `
<button onclick="event.stopPropagation(); editFormBantuan(${d.id})" style="border:none; background:var(--blue-m); color:var(--blue-d); padding: 3px 6px; border-radius:var(--r3); cursor:pointer; font-size:10px; font-weight:700;">✏️ Edit</button>
<button onclick="event.stopPropagation(); hapusHistoriBantuan(${d.id})" style="border:none; background:var(--red-l); color:var(--red); padding: 3px 6px; border-radius:var(--r3); cursor:pointer; font-size:10px; font-weight:700;">🗑️ Hapus</button>
`;
}
return `
<div style="background: white; border: 1px solid var(--border-l); border-radius: var(--r2); padding: 12px; margin-bottom: 10px; cursor: pointer; transition: all 0.2s; box-shadow: var(--sh-sm);"
onclick="flyClose(${d.lat}, ${d.lng})"
onmouseover="this.style.borderColor='var(--blue)'"
onmouseout="this.style.borderColor='var(--border-l)'">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px;">
<div style="font-weight: 700; color: var(--navy); font-size: 13px;">${d.nama_kk}</div>
<div style="display: flex; gap: 4px; align-items: center;">
${tombolAksiAdmin}
<div style="font-size: 9px; font-weight: 600; color: #b45309; background: #fef3c7; padding: 3px 8px; border-radius: 10px;">${d.tanggal_distribusi}</div>
</div>
</div>
<div style="font-size: 11px; color: var(--text-2); margin-bottom: 6px;">
<strong>Bantuan:</strong> ${d.satuan_custom} <br>
<strong>Oleh:</strong> ${d.nama_penyalur}
</div>
<div style="font-size: 10px; background: var(--border-l); padding: 6px 8px; border-radius: var(--r3); color: var(--text-2); font-style: italic;">
"${d.catatan || 'Distribusi reguler'}"
</div>
</div>
`;
}).join('');
})
.catch(err => {
console.error(err);
listEl.innerHTML = '<div class="empty-state" style="color:var(--red);"><div class="es-icon">❌</div>Gagal memuat data transparansi.</div>';
});
}
function simpanHistoriBantuan(wargaId) {
const jenisId = document.getElementById('hb_jenis').value;
const tgl = document.getElementById('hb_tgl').value;
const penyalur = document.getElementById('hb_penyalur').value.trim();
const deskripsi = document.getElementById('hb_deskripsi').value.trim(); // Tangkap deskripsi
const catatan = document.getElementById('hb_catatan').value.trim();
// Pastikan deskripsi divalidasi
if (!jenisId || !tgl || !penyalur || !deskripsi) {
toast('⚠️ Mohon lengkapi Jenis, Tanggal, Penyalur, dan Deskripsi Bantuan.', 'err');
return;
}
const fd = new FormData();
fd.append('warga_id', wargaId);
fd.append('jenis_id', jenisId);
fd.append('tanggal_distribusi', tgl);
fd.append('nama_penyalur', penyalur);
fd.append('deskripsi_bantuan', deskripsi); // Masukkan ke koper
fd.append('catatan', catatan);
const btn = event.target;
const originalText = btn.innerHTML;
btn.innerHTML = '⏳ Menyimpan...';
btn.disabled = true;
fetch('api_kemiskinan.php?aksi=simpan_bantuan', { method: 'POST', body: fd })
.then(r => r.text())
.then(text => {
btn.innerHTML = originalText;
btn.disabled = false;
if (text.includes('Sukses')) {
toast('✅ Data bantuan berhasil dicatat!', 'ok');
closeModalDonasi();
muatData();
} else {
toast('❌ Gagal menyimpan. ' + text, 'err');
console.error("Respon PHP:", text);
}
})
.catch(err => {
console.error("Fetch Error:", err);
btn.innerHTML = originalText;
btn.disabled = false;
toast('❌ Gagal menghubungi server.', 'err');
});
}
function editFormBantuan(logId) {
// Cari data log lama di memori global
const log = window.dataLogGlobal.find(x => x.id == logId);
if (!log) {
toast('⚠️ Data log tidak ditemukan', 'err');
return;
}
const modalBody = document.getElementById('donasi-modal-body');
const icon = document.getElementById('mdl-icon');
const title = document.getElementById('mdl-title-container');
if(icon) icon.textContent = '✏️';
const namaAman = (log.nama_kk || 'Warga').replace(/'/g, "\\'");
if(title) title.innerHTML = `<div style="font-size:14px;font-weight:700;color:var(--navy)">Edit Bantuan:<br><span style="color:var(--blue); font-size:12px;">${namaAman}</span></div>`;
// Susun opsi penyalur dengan menandai (selected) penyalur yang lama
let opsiPenyalur = `<option value="">— Pilih Penyalur (Rumah Ibadah) —</option>`;
if (ibData && ibData.length > 0) {
ibData.forEach(ib => {
const isSel = (log.nama_penyalur === ib.nama_tempat) ? 'selected' : '';
opsiPenyalur += `<option value="${ib.nama_tempat}" ${isSel}>🕌 ${ib.nama_tempat}</option>`;
});
}
opsiPenyalur += `<option value="Donatur Pribadi / Anonim" ${log.nama_penyalur === 'Donatur Pribadi / Anonim' ? 'selected' : ''}>👤 Donatur Pribadi / Anonim</option>`;
opsiPenyalur += `<option value="Lainnya" ${log.nama_penyalur === 'Lainnya' ? 'selected' : ''}>📦 Lainnya</option>`;
// Render form dengan value bawaan dari data lama
modalBody.innerHTML = `
<div class="ff">
<label>Jenis Bantuan</label>
<select id="hb_edit_jenis" style="width:100%; padding:8px 11px; font-size:13px; border:1.5px solid var(--border); border-radius:var(--r3); color:var(--navy); background:var(--border-l);">
<option value="">— Pilih Jenis —</option>
<option value="1" ${log.jenis_id == 1 ? 'selected' : ''}>💵 Uang Tunai</option>
<option value="2" ${log.jenis_id == 2 ? 'selected' : ''}>🛒 Sembako</option>
<option value="3" ${log.jenis_id == 3 ? 'selected' : ''}>🏥 Bantuan Kesehatan</option>
<option value="4" ${log.jenis_id == 4 ? 'selected' : ''}>📚 Bantuan Pendidikan</option>
<option value="6" ${log.jenis_id == 6 ? 'selected' : ''}>👕 Pakaian</option>
<option value="5" ${log.jenis_id == 5 ? 'selected' : ''}>📦 Lainnya</option>
</select>
</div>
<div class="ff">
<label>Tanggal Distribusi</label>
<input type="date" id="hb_edit_tgl" value="${log.tanggal_distribusi}">
</div>
<div class="ff">
<label>Nama Penyalur / Sumber Bantuan</label>
<select id="hb_edit_penyalur" style="width:100%; padding:8px 11px; font-size:13px; border:1.5px solid var(--border); border-radius:var(--r3); color:var(--navy); background:var(--border-l);">
${opsiPenyalur}
</select>
</div>
<div class="ff">
<label>Deskripsi Bantuan</label>
<input type="text" id="hb_edit_deskripsi" value="${log.satuan_custom || ''}" placeholder="Contoh: Rp 500.000 / 2 Paket Sembako">
</div>
<div class="ff">
<label>Keterangan / Catatan</label>
<input type="text" id="hb_edit_catatan" value="${log.catatan || ''}" placeholder="Contoh: Bantuan rutin bulanan">
</div>
<button class="btn-sv b" style="width:100%; margin-top:14px; padding:10px;"
onclick="simpanEditBantuan(${logId})">💾 Update Data Bantuan</button>
<button class="btn-cncl" style="width:100%; margin-top:8px; border:none; background:transparent;"
onclick="closeModalDonasi()">Batalkan</button>
`;
document.getElementById('modal-donasi').style.display = 'flex';
}
function simpanEditBantuan(logId) {
const jenisId = document.getElementById('hb_edit_jenis').value;
const tgl = document.getElementById('hb_edit_tgl').value;
const penyalur = document.getElementById('hb_edit_penyalur').value;
const deskripsi = document.getElementById('hb_edit_deskripsi').value.trim();
const catatan = document.getElementById('hb_edit_catatan').value.trim();
if (!jenisId || !tgl || !penyalur || !deskripsi) {
toast('⚠️ Mohon lengkapi Jenis, Tanggal, Penyalur, dan Deskripsi.', 'err');
return;
}
const fd = new FormData();
fd.append('id', logId);
fd.append('jenis_id', jenisId);
fd.append('tanggal_distribusi', tgl);
fd.append('nama_penyalur', penyalur);
fd.append('deskripsi_bantuan', deskripsi);
fd.append('catatan', catatan);
const btn = event.target;
const originalText = btn.innerHTML;
btn.innerHTML = '⏳ Mengupdate...';
btn.disabled = true;
fetch('api_kemiskinan.php?aksi=update_bantuan', { method: 'POST', body: fd })
.then(r => r.text())
.then(text => {
btn.innerHTML = originalText;
btn.disabled = false;
if (text.includes('Sukses')) {
toast('✅ Histori bantuan berhasil diperbarui!', 'ok');
closeModalDonasi();
bukaTransparansi(); // Refresh panel transparansi agar data baru muncul
muatData(); // Refresh peta
} else {
toast('❌ Gagal update. ' + text, 'err');
}
})
.catch(err => {
console.error(err);
btn.innerHTML = originalText;
btn.disabled = false;
toast('❌ Gagal menghubungi server.', 'err');
});
}
function hapusHistoriBantuan(logId) {
if (!confirm('Apakah Anda yakin ingin menghapus riwayat bantuan ini secara permanen?')) return;
const fd = new FormData();
fd.append('id', logId);
fetch('api_kemiskinan.php?aksi=hapus_bantuan', { method: 'POST', body: fd })
.then(r => r.text())
.then(text => {
if (text.includes('Sukses')) {
toast('🗑️ Riwayat bantuan berhasil dihapus!', 'ok');
bukaTransparansi(); // Segarkan isi panel transparansi secara real-time
muatData(); // Segarkan status penanda bantuan di peta utama
} else {
toast('❌ Gagal menghapus data log.', 'err');
console.error("Respon PHP:", text);
}
})
.catch(err => {
console.error("Fetch Error:", err);
toast('❌ Gagal berkomunikasi dengan server.', 'err');
});
}
onSlide(500);
muatData();
// ─── FITUR LAPORAN WARGA ──────────────────────────────────────────────────
let _laporanStep = 1;
let _tipeLaporan = 'orang_lain';
function bukaModalLaporan() {
_laporanStep = 1;
_tipeLaporan = 'orang_lain';
resetFormLaporan();
tampilkanLaporanStep(1);
document.getElementById('modal-laporan').style.display = 'flex';
}
function tutupModalLaporan() {
document.getElementById('modal-laporan').style.display = 'none';
}
function resetFormLaporan() {
['lap_nama_kk','lap_alamat','lap_lat','lap_lng','lap_pekerjaan','lap_keterangan'].forEach(id => {
const el = document.getElementById(id); if(el) el.value = '';
});
const jml = document.getElementById('lap_jml'); if(jml) jml.value = '1';
['lap_fakir','lap_dhuafa','lap_lansia','lap_yatim','lap_bencana','lap_disab'].forEach(id => {
const el = document.getElementById(id); if(el) el.checked = false;
});
const btnGps = document.getElementById('btn-gps');
if(btnGps) { btnGps.innerHTML = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/></svg> Gunakan Lokasi Saya (GPS)'; btnGps.style.background = 'var(--blue)'; btnGps.disabled = false; }
const lapSt = document.getElementById('lap-lokasi-status'); if(lapSt) lapSt.style.display = 'none';
pilihTipeLaporan('orang_lain');
}
function pilihTipeLaporan(tipe) {
_tipeLaporan = tipe;
const elDiri = document.getElementById('lbl-diri');
const elLain = document.getElementById('lbl-lain');
if(elDiri) { elDiri.style.border = tipe === 'diri_sendiri' ? '2px solid var(--blue)' : '2px solid var(--border)'; elDiri.style.background = tipe === 'diri_sendiri' ? 'var(--blue-l)' : 'white'; }
if(elLain) { elLain.style.border = tipe === 'orang_lain' ? '2px solid var(--green)' : '2px solid var(--border)'; elLain.style.background = tipe === 'orang_lain' ? 'var(--green-l)': 'white'; }
}
function tampilkanLaporanStep(step) {
_laporanStep = step;
[1,2,3].forEach(i => {
const el = document.getElementById('laporan-step-' + i);
if(el) el.style.display = (i === step) ? 'block' : 'none';
});
const labels = ['Langkah 1 dari 3', 'Langkah 2 dari 3', 'Selesai!'];
const widths = ['33%', '66%', '100%'];
document.getElementById('laporan-step-label').textContent = labels[step - 1];
document.getElementById('laporan-progress-bar').style.width = widths[step - 1];
const btnBack = document.getElementById('btn-lap-back');
const btnNext = document.getElementById('btn-lap-next');
if(step === 1) {
btnBack.style.display = 'none';
btnNext.style.display = 'flex';
btnNext.innerHTML = 'Lanjut →';
btnNext.style.background = 'var(--amber)';
btnNext.disabled = false;
} else if(step === 2) {
btnBack.style.display = 'flex';
btnNext.style.display = 'flex';
btnNext.innerHTML = 'Kirim Laporan';
btnNext.style.background = 'var(--green)';
btnNext.disabled = false;
} else {
btnBack.style.display = 'none';
btnNext.style.display = 'none';
}
}
function laporanNext() {
if(_laporanStep === 1) tampilkanLaporanStep(2);
else if(_laporanStep === 2) kirimLaporan();
}
function laporanBack() {
if(_laporanStep === 2) tampilkanLaporanStep(1);
}
function gunakanLokasiGPS() {
const btn = document.getElementById('btn-gps');
const status = document.getElementById('lap-lokasi-status');
btn.innerHTML = '⏳ Mendeteksi lokasi...';
btn.disabled = true;
status.style.display = 'block';
status.style.color = 'var(--text-3)';
status.textContent = 'Meminta izin akses lokasi...';
if(!navigator.geolocation) {
btn.innerHTML = '❌ GPS tidak didukung';
btn.disabled = false;
status.textContent = 'Browser tidak mendukung geolocation.';
return;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
const lat = pos.coords.latitude.toFixed(7);
const lng = pos.coords.longitude.toFixed(7);
document.getElementById('lap_lat').value = lat;
document.getElementById('lap_lng').value = lng;
btn.innerHTML = '✅ Lokasi berhasil didapat!';
btn.style.background = 'var(--green)';
btn.disabled = false;
status.style.color = 'var(--green-d)';
status.textContent = `Koordinat: ${lat}, ${lng}`;
toast('📍 Lokasi GPS berhasil diambil!', 'ok');
},
() => {
btn.innerHTML = '⚠️ Gagal Izinkan akses lokasi';
btn.style.background = 'var(--red)';
btn.disabled = false;
status.style.color = 'var(--red)';
status.textContent = 'Buka pengaturan browser dan izinkan akses lokasi.';
},
{ timeout: 10000, enableHighAccuracy: true }
);
}
function kirimLaporan() {
const nama = document.getElementById('lap_nama_kk').value.trim();
const alamat = document.getElementById('lap_alamat').value.trim();
const lat = parseFloat(document.getElementById('lap_lat').value);
const lng = parseFloat(document.getElementById('lap_lng').value);
if(!nama || !alamat || isNaN(lat) || isNaN(lng) || lat === 0 || lng === 0) {
toast('⚠️ Nama, alamat, dan titik lokasi GPS wajib diisi.', 'err');
return;
}
const btnNext = document.getElementById('btn-lap-next');
btnNext.innerHTML = '⏳ Mengirim...';
btnNext.disabled = true;
const fd = new FormData();
fd.append('tipe_laporan', _tipeLaporan);
fd.append('nama_kk', nama);
fd.append('jumlah_anggota', document.getElementById('lap_jml').value || 1);
fd.append('alamat', alamat);
fd.append('lat', lat);
fd.append('lng', lng);
fd.append('pekerjaan', document.getElementById('lap_pekerjaan').value.trim());
fd.append('keterangan', document.getElementById('lap_keterangan').value.trim());
fd.append('is_fakir_miskin', document.getElementById('lap_fakir').checked ? 1 : 0);
fd.append('is_dhuafa', document.getElementById('lap_dhuafa').checked ? 1 : 0);
fd.append('is_lansia_tunggal', document.getElementById('lap_lansia').checked ? 1 : 0);
fd.append('is_anak_yatim', document.getElementById('lap_yatim').checked ? 1 : 0);
fd.append('is_korban_bencana', document.getElementById('lap_bencana').checked ? 1 : 0);
fd.append('is_disabilitas', document.getElementById('lap_disab').checked ? 1 : 0);
fetch('api_laporan_warga.php?aksi=simpan', { method: 'POST', body: fd })
.then(r => r.json())
.then(res => {
btnNext.disabled = false;
if(res.status === 'sukses') {
document.getElementById('lap-ref-number').textContent = '#' + String(res.id).padStart(6, '0');
tampilkanLaporanStep(3);
muatBadgeLaporanNav();
} else {
btnNext.innerHTML = 'Kirim Laporan';
toast('❌ ' + res.pesan, 'err');
}
})
.catch(() => {
btnNext.disabled = false;
btnNext.innerHTML = 'Kirim Laporan';
toast('❌ Gagal menghubungi server.', 'err');
});
}
function bukaStatusLaporan() {
tutupModalLaporan();
openPanel('laporan-saya');
muatStatusLaporan();
}
function muatStatusLaporan() {
const listEl = document.getElementById('laporan-saya-list');
listEl.innerHTML = '<div class="empty-state"><div class="es-icon"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>Memuat...</div>';
fetch('api_laporan_warga.php?aksi=cek_status')
.then(r => r.json())
.then(data => {
if(!data.length) {
listEl.innerHTML = '<div class="empty-state"><div class="es-icon"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="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>Belum ada laporan yang dibuat.</div>';
return;
}
const stCfg = {
'menunggu': { color:'#F59E0B', bg:'#FFFBEB', label:'⏳ Menunggu' },
'diverifikasi':{ color:'#10B981', bg:'#ECFDF5', label:'✅ Diverifikasi' },
'ditolak': { color:'#EF4444', bg:'#FEF2F2', label:'❌ Ditolak' }
};
listEl.innerHTML = data.map(lap => {
const st = stCfg[lap.status] || stCfg['menunggu'];
const tgl = new Date(lap.created_at).toLocaleDateString('id-ID', {day:'2-digit',month:'short',year:'numeric'});
const tipeLbl = lap.tipe_laporan === 'diri_sendiri' ? 'Diri Sendiri' : 'Orang Lain';
return `
<div style="background:white;border:1px solid var(--border-l);border-radius:var(--r2);padding:12px 13px;margin-bottom:8px;border-left:4px solid ${st.color};">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px;">
<div style="font-size:13px;font-weight:700;color:var(--navy);">${lap.nama_kk}</div>
<span style="background:${st.bg};color:${st.color};font-size:9px;font-weight:700;padding:3px 8px;border-radius:10px;white-space:nowrap;">${st.label}</span>
</div>
<div style="font-size:11px;color:var(--text-3);">Ref: <strong>#${String(lap.id).padStart(6,'0')}</strong> · ${tipeLbl}</div>
<div style="font-size:11px;color:var(--text-3);margin-top:2px;">📍 ${lap.alamat}</div>
<div style="font-size:10px;color:var(--text-3);margin-top:3px;">Dikirim: ${tgl}</div>
${lap.catatan_admin ? `<div style="margin-top:7px;background:${st.bg};border-radius:var(--r3);padding:7px 9px;font-size:11px;color:${st.color};font-weight:600;">📝 Catatan Admin: ${lap.catatan_admin}</div>` : ''}
</div>`;
}).join('');
})
.catch(() => {
listEl.innerHTML = '<div class="empty-state" style="color:var(--red)">Gagal memuat data.</div>';
});
}
function muatBadgeLaporanNav() {
fetch('api_laporan_warga.php?aksi=hitung_menunggu')
.then(r => r.json())
.then(res => {
const badge = document.getElementById('badge-laporan-nav');
if(!badge) return;
if(res.jumlah > 0) { badge.textContent = res.jumlah; badge.style.display = 'flex'; }
else badge.style.display = 'none';
})
.catch(() => {});
}
// Tutup modal laporan jika klik di luar
document.getElementById('modal-laporan').addEventListener('click', function(e) {
if(e.target === this) tutupModalLaporan();
});
// Load badge laporan saat halaman pertama dibuka (admin/walikota saja)
<?php if (in_array($_SESSION['role'] ?? '', ['admin', 'walikota'])): ?>
muatBadgeLaporanNav();
<?php endif; ?>
</script>
</body>
</html>