3374 lines
181 KiB
PHP
3374 lines
181 KiB
PHP
<?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> 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">—</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">—</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">—</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>
|