837 lines
45 KiB
PHP
837 lines
45 KiB
PHP
<?php
|
||
require_once '../config.php';
|
||
require_once '../auth/helper.php';
|
||
require_once '../koneksi.php';
|
||
if (!is_logged_in() || !has_role('operator')) { header('Location: ../auth/login.php'); exit; }
|
||
require_password_changed('../auth/change_password.php');
|
||
$is_admin = has_role('administrator');
|
||
$ibadah_id = get_ibadah_id();
|
||
$page_title = 'Penduduk Miskin';
|
||
$page_subtitle = 'Daftar dan manajemen data penduduk miskin';
|
||
$active_nav = 'penduduk';
|
||
include '../includes/page-start.php';
|
||
?>
|
||
<script>
|
||
window._IBADAH_ID = <?= $ibadah_id ? (int)$ibadah_id : 'null' ?>;
|
||
window._IS_ADMIN = <?= $is_admin ? 'true' : 'false' ?>;
|
||
</script>
|
||
|
||
<!-- Leaflet -->
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" crossorigin="">
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" crossorigin=""></script>
|
||
|
||
<style>
|
||
.pd-filter-bar{display:flex;flex-direction:column;gap:8px;margin-bottom:16px;}
|
||
.pd-input{width:100%;box-sizing:border-box;padding:8px 12px;border:1px solid var(--card-border);border-radius:8px;font-family:var(--font);font-size:13px;background:#fafaf9;color:var(--text-primary);outline:none;}
|
||
.pd-input:focus{border-color:#0d7490;}
|
||
.pd-filter-chips{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
|
||
.pd-chip{width:auto;height:auto;padding:7px 11px;border:1px solid var(--card-border);border-radius:8px;font-family:var(--font);font-size:13px;background:#fafaf9;color:var(--text-primary);cursor:pointer;white-space:nowrap;}
|
||
.pd-chip:focus{outline:none;border-color:#0d7490;}
|
||
.pd-chip-spacer{flex:1;}
|
||
.kat-badge{display:inline-block;padding:2px 9px;border-radius:9999px;font-size:11px;font-weight:500;}
|
||
.kat-sm{background:#fef2f2;color:#ef4444;}
|
||
.kat-m{background:#fffbeb;color:#f59e0b;}
|
||
.kat-hm{background:#eff6ff;color:#3b82f6;}
|
||
.bs-badge{display:inline-block;padding:2px 9px;border-radius:9999px;font-size:11px;font-weight:500;}
|
||
.bs-blank{background:#fef2f2;color:#ef4444;}
|
||
.bs-cover{background:#f0fdf4;color:#16a34a;}
|
||
.sv-badge{display:inline-flex;align-items:center;gap:3px;padding:2px 9px;border-radius:9999px;font-size:11px;font-weight:500;}
|
||
.sv-pending{background:#fef3c7;color:#d97706;}
|
||
.sv-ok{background:#d1fae5;color:#16a34a;}
|
||
.sv-tolak{background:#fee2e2;color:#dc2626;}
|
||
.stat-sel{padding:5px 10px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;border:1px solid var(--card-border);background:#fafaf9;font-family:var(--font);color:var(--text-secondary);}
|
||
.stat-sel.belum{background:#f5f0eb;color:#7a7067;border-color:#c5bdb5;}
|
||
.stat-sel.proses{background:#fffbeb;color:#d97706;border-color:#fcd34d;}
|
||
.stat-sel.sudah{background:#f0fdf4;color:#16a34a;border-color:#86efac;}
|
||
.modal-dark{position:fixed;inset:0;background:rgba(32,21,21,.55);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px;z-index:9999;opacity:0;pointer-events:none;transition:opacity .2s;}
|
||
.modal-dark.show{opacity:1;pointer-events:auto;}
|
||
.modal-box{width:min(620px,100%);background:#fafaf9;border:1px solid var(--card-border);border-radius:12px;padding:24px;box-shadow:0 4px 16px rgba(32,21,21,.1);max-height:90vh;display:flex;flex-direction:column;}
|
||
.modal-box h3{font-size:15px;font-weight:600;margin-bottom:14px;color:var(--text-primary);flex-shrink:0;}
|
||
.modal-scroll{overflow-y:auto;flex:1;padding-right:4px;}
|
||
.mfg{margin-bottom:14px;}
|
||
.mfg label{display:block;font-size:11px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:5px;}
|
||
.mfg input,.mfg select,.mfg textarea{width:100%;padding:9px 12px;border:1px solid var(--card-border);border-radius:8px;font-family:var(--font);font-size:13px;color:var(--text-primary);background:#fafaf9;outline:none;transition:border-color .2s;}
|
||
.mfg input:focus,.mfg select:focus,.mfg textarea:focus{border-color:#0d7490;}
|
||
.mfg textarea{resize:vertical;min-height:70px;}
|
||
.mfg small{font-size:11px;color:var(--text-muted);margin-top:4px;display:block;}
|
||
.modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px;flex-shrink:0;padding-top:14px;border-top:1px solid var(--card-border);}
|
||
.btn-cancel{padding:9px 16px;background:transparent;border:1px solid var(--card-border);border-radius:8px;font-family:var(--font);font-size:13px;font-weight:600;color:var(--text-secondary);cursor:pointer;transition:background .15s;}
|
||
.btn-cancel:hover{background:#ede8e2;}
|
||
.btn-primary{padding:9px 18px;background:#0d7490;border:none;border-radius:8px;font-family:var(--font);font-size:14px;font-weight:600;color:#fff;cursor:pointer;transition:background .15s;}
|
||
.btn-primary:hover{background:#0a5f7a;}
|
||
.btn-primary:disabled{opacity:.5;cursor:not-allowed;}
|
||
.btn-approve{padding:4px 10px;background:#f0fdf4;border:1px solid #86efac;border-radius:9999px;font-size:11px;font-weight:500;color:#16a34a;cursor:pointer;white-space:nowrap;}
|
||
.btn-approve:hover{background:#dcfce7;}
|
||
.btn-reject{padding:4px 10px;background:#fef2f2;border:1px solid #fca5a5;border-radius:9999px;font-size:11px;font-weight:500;color:#dc2626;cursor:pointer;white-space:nowrap;}
|
||
.btn-reject:hover{background:#fee2e2;}
|
||
.msg-err{font-size:12px;color:#ef4444;margin-top:6px;min-height:16px;}
|
||
.map-picker-wrap{border:1px solid var(--card-border);border-radius:10px;overflow:hidden;}
|
||
.map-picker-wrap #pdMapPicker{height:240px;cursor:crosshair;}
|
||
.map-picker-hint{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-muted);margin-top:6px;}
|
||
.map-geocoding{font-size:11px;color:var(--text-muted);margin-top:4px;min-height:16px;font-style:italic;}
|
||
</style>
|
||
|
||
<!-- Filter Bar -->
|
||
<div class="pd-filter-bar">
|
||
<input type="text" class="pd-input" id="pdSearch" placeholder="Cari nama KK..." oninput="applyFilter()">
|
||
<div class="pd-filter-chips">
|
||
<select class="pd-chip" id="pdKat" onchange="applyFilter()">
|
||
<option value="">Semua Kategori</option>
|
||
<option value="Sangat Miskin">Sangat Miskin</option>
|
||
<option value="Miskin">Miskin</option>
|
||
<option value="Hampir Miskin">Hampir Miskin</option>
|
||
</select>
|
||
<select class="pd-chip" id="pdStatus" onchange="applyFilter()">
|
||
<option value="">Semua Status</option>
|
||
<option value="Belum Ditangani">Belum Ditangani</option>
|
||
<option value="Dalam Proses">Dalam Proses</option>
|
||
<option value="Sudah Ditangani">Sudah Ditangani</option>
|
||
</select>
|
||
<select class="pd-chip" id="pdBlank" onchange="applyFilter()">
|
||
<option value="">Semua Spot</option>
|
||
<option value="1">Blank Spot</option>
|
||
<option value="0">Terjangkau</option>
|
||
</select>
|
||
<select class="pd-chip" id="pdVerif" onchange="applyFilter()">
|
||
<option value="">Semua Verifikasi</option>
|
||
<option value="Pending">Pending</option>
|
||
<option value="Terverifikasi">Terverifikasi</option>
|
||
<option value="Ditolak">Ditolak</option>
|
||
</select>
|
||
<div style="width:1px;height:20px;background:var(--card-border);margin:0 2px;align-self:center;flex-shrink:0;"></div>
|
||
<select class="pd-chip" id="pdSort" onchange="applyFilter()">
|
||
<option value="">Urutkan</option>
|
||
<option value="nama_asc">Nama A→Z</option>
|
||
<option value="nama_desc">Nama Z→A</option>
|
||
<option value="jiwa_desc">Jiwa Terbanyak</option>
|
||
<option value="jiwa_asc">Jiwa Tersedikit</option>
|
||
<option value="jarak_asc">Jarak Terdekat</option>
|
||
<option value="jarak_desc">Jarak Terjauh</option>
|
||
</select>
|
||
<div class="pd-chip-spacer"></div>
|
||
<a href="map.php" class="btn-see-all" style="text-decoration:none;padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;"><i data-lucide="map"></i> Peta</a>
|
||
<button class="btn-primary" onclick="openAddPd()" style="white-space:nowrap;">+ Tambah Penduduk</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-card">
|
||
<div class="table-card-header">
|
||
<div>
|
||
<div class="table-card-title">Daftar Penduduk Miskin</div>
|
||
<div class="table-card-count" id="pdCount"></div>
|
||
</div>
|
||
</div>
|
||
<div style="overflow-x:auto;">
|
||
<table class="admin-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:40px;">No</th>
|
||
<th>Nama KK</th>
|
||
<th>Kategori</th>
|
||
<th style="text-align:right;">Jiwa</th>
|
||
<th>Rumah Ibadah</th>
|
||
<th>Status Bantuan</th>
|
||
<th>Keterjangkauan</th>
|
||
<th>Verifikasi</th>
|
||
<th style="width:100px;text-align:center;">Aksi</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="pdTbody">
|
||
<tr><td colspan="8" style="text-align:center;padding:28px;color:var(--text-muted);"><span class="spinner"></span> Memuat...</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pagination">
|
||
<span id="pd-info"></span>
|
||
<div class="page-btns" id="pd-pages"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Riwayat Modal -->
|
||
<div class="modal-dark" id="riwayatModal">
|
||
<div class="modal-box">
|
||
<h3 id="riwayatTitle">Riwayat Bantuan</h3>
|
||
<div class="modal-scroll" id="riwayatBody" style="font-size:13px;"></div>
|
||
<div style="margin-top:14px;flex-shrink:0;">
|
||
<button style="padding:8px 16px;background:transparent;border:1px solid var(--card-border);border-radius:8px;font-family:var(--font);font-size:13px;font-weight:600;color:var(--text-secondary);cursor:pointer;" onclick="document.getElementById('riwayatModal').classList.remove('show')">Tutup</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const PD_PER = 15;
|
||
let _pdAll = [], _pdFiltered = [], _pdPage = 1;
|
||
|
||
function esc(s) { const d = document.createElement('div'); d.appendChild(document.createTextNode(s ?? '')); return d.innerHTML; }
|
||
function attr(s) { return String(s ?? '').replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>'); }
|
||
function fmt(n) { return Number(n || 0).toLocaleString('id-ID'); }
|
||
|
||
async function loadPd() {
|
||
try {
|
||
const r = await fetch('../api/penduduk/ambil.php?_=' + Date.now());
|
||
const j = await r.json();
|
||
if (j.status !== 'success') return;
|
||
// Operator: filter to own ibadah only
|
||
_pdAll = (window._IBADAH_ID !== null && !window._IS_ADMIN)
|
||
? j.data.filter(p => parseInt(p.ibadah_id) === window._IBADAH_ID)
|
||
: j.data;
|
||
document.getElementById('pdCount').textContent = _pdAll.length + ' penduduk';
|
||
applyFilter();
|
||
} catch(e) {
|
||
document.getElementById('pdTbody').innerHTML =
|
||
'<tr><td colspan="8" style="text-align:center;padding:24px;color:#ef4444;">Gagal memuat data.</td></tr>';
|
||
}
|
||
}
|
||
|
||
function applyFilter() {
|
||
const q = document.getElementById('pdSearch').value.toLowerCase();
|
||
const kat = document.getElementById('pdKat').value;
|
||
const stat = document.getElementById('pdStatus').value;
|
||
const blank = document.getElementById('pdBlank').value;
|
||
const verif = document.getElementById('pdVerif').value;
|
||
const sort = document.getElementById('pdSort').value;
|
||
_pdFiltered = _pdAll.filter(p =>
|
||
(!q || (p.nama_kk || '').toLowerCase().includes(q)) &&
|
||
(!kat || p.kategori === kat) &&
|
||
(!stat || p.status_bantuan === stat) &&
|
||
(blank === '' || String(p.is_blank_spot) === blank) &&
|
||
(!verif || (p.status_verifikasi || 'Pending') === verif)
|
||
);
|
||
if (sort) {
|
||
_pdFiltered.sort((a, b) => {
|
||
switch (sort) {
|
||
case 'nama_asc': return (a.nama_kk||'').localeCompare(b.nama_kk||'', 'id');
|
||
case 'nama_desc': return (b.nama_kk||'').localeCompare(a.nama_kk||'', 'id');
|
||
case 'jiwa_desc': return (b.jumlah_jiwa||0) - (a.jumlah_jiwa||0);
|
||
case 'jiwa_asc': return (a.jumlah_jiwa||0) - (b.jumlah_jiwa||0);
|
||
case 'jarak_asc': return (a.jarak_m||0) - (b.jarak_m||0);
|
||
case 'jarak_desc': return (b.jarak_m||0) - (a.jarak_m||0);
|
||
default: return 0;
|
||
}
|
||
});
|
||
}
|
||
_pdPage = 1;
|
||
renderPage();
|
||
}
|
||
|
||
function renderPage() {
|
||
const start = (_pdPage - 1) * PD_PER;
|
||
const slice = _pdFiltered.slice(start, start + PD_PER);
|
||
const pages = Math.ceil(_pdFiltered.length / PD_PER) || 1;
|
||
const tbody = document.getElementById('pdTbody');
|
||
|
||
const katCls = { 'Sangat Miskin': 'kat-sm', 'Miskin': 'kat-m', 'Hampir Miskin': 'kat-hm' };
|
||
const statCls = { 'Belum Ditangani': 'belum', 'Dalam Proses': 'proses', 'Sudah Ditangani': 'sudah' };
|
||
|
||
if (!slice.length) {
|
||
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:24px;color:var(--text-muted);">Tidak ada data.</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = slice.map((p, i) => {
|
||
const ibadahText = p.nama_ibadah
|
||
? `${esc(p.nama_ibadah)}<br><small style="color:var(--text-muted);">${fmt(Math.round(p.jarak_m || 0))} m</small>`
|
||
: '<span style="color:var(--text-muted);">—</span>';
|
||
const nonaktifBtn = window._IS_ADMIN
|
||
? `<button class="tbl-action-btn" title="Nonaktifkan" style="color:#d97706;" data-id="${attr(parseInt(p.id, 10) || 0)}" onclick="doNonaktifkan(this.dataset.id)"><i data-lucide="ban"></i></button>`
|
||
: '';
|
||
const sc = statCls[p.status_bantuan] || '';
|
||
const sv = p.status_verifikasi || 'Pending';
|
||
const isVerified = sv === 'Terverifikasi';
|
||
const svCls = { Terverifikasi: 'sv-ok', Pending: 'sv-pending', Ditolak: 'sv-tolak' }[sv] || 'sv-pending';
|
||
const svIcon = {
|
||
Terverifikasi: '<i data-lucide="check-circle-2" style="width:12px;height:12px;vertical-align:middle;margin-right:2px;"></i>',
|
||
Pending: '<i data-lucide="clock" style="width:12px;height:12px;vertical-align:middle;margin-right:2px;"></i>',
|
||
Ditolak: '<i data-lucide="x-circle" style="width:12px;height:12px;vertical-align:middle;margin-right:2px;"></i>'
|
||
}[sv] || '';
|
||
const verifBtns = (window._IS_ADMIN && sv === 'Pending')
|
||
? `<div style="display:flex;gap:4px;margin-top:4px;">
|
||
<button class="btn-approve" data-id="${attr(parseInt(p.id,10)||0)}" onclick="doVerifRow(this,'approve')"><i data-lucide="check" style="width:10px;height:10px;"></i></button>
|
||
<button class="btn-reject" data-id="${attr(parseInt(p.id,10)||0)}" onclick="doVerifRow(this,'reject')"><i data-lucide="x" style="width:10px;height:10px;"></i></button>
|
||
</div>` : '';
|
||
const statusControl = isVerified
|
||
? `<select class="stat-sel ${esc(sc)}"
|
||
onchange="updateStatus(${p.id}, this)"
|
||
data-orig="${esc(p.status_bantuan || '')}">
|
||
<option ${p.status_bantuan === 'Belum Ditangani' ? 'selected' : ''}>Belum Ditangani</option>
|
||
<option ${p.status_bantuan === 'Dalam Proses' ? 'selected' : ''}>Dalam Proses</option>
|
||
<option ${p.status_bantuan === 'Sudah Ditangani' ? 'selected' : ''}>Sudah Ditangani</option>
|
||
</select>`
|
||
: `<div>
|
||
<select class="stat-sel ${esc(sc)}" disabled data-orig="${esc(p.status_bantuan || '')}">
|
||
<option selected>${esc(p.status_bantuan || 'Belum Ditangani')}</option>
|
||
</select>
|
||
<div style="font-size:11px;color:var(--text-muted);margin-top:4px;">Menunggu verifikasi admin</div>
|
||
</div>`;
|
||
return `<tr>
|
||
<td style="color:var(--text-muted);font-size:12px;">${start + i + 1}</td>
|
||
<td style="font-weight:600;">${esc(p.nama_kk || '—')}</td>
|
||
<td><span class="kat-badge ${esc(katCls[p.kategori] || '')}">${esc(p.kategori || '—')}</span></td>
|
||
<td style="text-align:right;font-family:var(--font-mono);">${esc(String(p.jumlah_jiwa || 0))}</td>
|
||
<td style="font-size:12px;">${ibadahText}</td>
|
||
<td>
|
||
${statusControl}
|
||
</td>
|
||
<td><span class="bs-badge ${p.is_blank_spot ? 'bs-blank' : 'bs-cover'}">${p.is_blank_spot ? 'Blank Spot' : 'Terjangkau'}</span></td>
|
||
<td>
|
||
<span class="sv-badge ${svCls}">${svIcon}${sv}</span>
|
||
${verifBtns}
|
||
</td>
|
||
<td style="text-align:center;display:flex;align-items:center;justify-content:center;gap:4px;">
|
||
<button class="tbl-action-btn" title="Edit Data" style="color:#d97706;" data-id="${attr(parseInt(p.id,10)||0)}" onclick="openEditPd(parseInt(this.dataset.id,10))"><i data-lucide="pencil"></i></button>
|
||
<button class="tbl-action-btn" title="Riwayat Bantuan" data-id="${attr(parseInt(p.id, 10) || 0)}" data-nama="${attr(p.nama_kk || '?')}" onclick="loadRiwayat(this.dataset.id, this.dataset.nama)"><i data-lucide="clipboard-list"></i></button>
|
||
${isVerified ? `<button class="tbl-action-btn" title="Kelola Kebutuhan" style="color:#0d7490;" data-id="${attr(parseInt(p.id,10)||0)}" data-nama="${attr(p.nama_kk||'?')}" onclick="openKebPd(parseInt(this.dataset.id,10), this.dataset.nama)"><i data-lucide="package"></i></button>` : ''}
|
||
${nonaktifBtn}
|
||
</td>
|
||
</tr>`;
|
||
}).join('');
|
||
}
|
||
|
||
const end = Math.min(start + PD_PER, _pdFiltered.length);
|
||
document.getElementById('pd-info').textContent = _pdFiltered.length > 0
|
||
? `Menampilkan ${start + 1}–${end} dari ${_pdFiltered.length}`
|
||
: '';
|
||
|
||
let btns = `<button class="page-btn" onclick="goPage(${_pdPage - 1})" ${_pdPage === 1 ? 'disabled' : ''}><i data-lucide="chevron-left"></i></button>`;
|
||
for (let p = 1; p <= pages; p++) {
|
||
btns += `<button class="page-btn ${p === _pdPage ? 'active' : ''}" onclick="goPage(${p})">${p}</button>`;
|
||
}
|
||
btns += `<button class="page-btn" onclick="goPage(${_pdPage + 1})" ${_pdPage === pages ? 'disabled' : ''}><i data-lucide="chevron-right"></i></button>`;
|
||
document.getElementById('pd-pages').innerHTML = btns;
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
}
|
||
|
||
function goPage(p) {
|
||
const pages = Math.ceil(_pdFiltered.length / PD_PER) || 1;
|
||
if (p < 1 || p > pages) return;
|
||
_pdPage = p;
|
||
renderPage();
|
||
}
|
||
|
||
async function updateStatus(id, sel) {
|
||
const newStatus = sel.value;
|
||
const orig = sel.dataset.orig;
|
||
const item = _pdAll.find(p => String(p.id) === String(id));
|
||
if (item && (item.status_verifikasi || 'Pending') !== 'Terverifikasi') {
|
||
alert('Menunggu verifikasi admin');
|
||
sel.value = orig;
|
||
return;
|
||
}
|
||
const fd = new FormData();
|
||
fd.append('id', id);
|
||
fd.append('status', newStatus);
|
||
try {
|
||
const r = await fetch('../api/penduduk/update_status.php', { method: 'POST', body: appendCsrf(fd) });
|
||
const j = await r.json();
|
||
if (j.status === 'success') {
|
||
sel.dataset.orig = newStatus;
|
||
const statCls = { 'Belum Ditangani': 'belum', 'Dalam Proses': 'proses', 'Sudah Ditangani': 'sudah' };
|
||
sel.className = 'stat-sel ' + (statCls[newStatus] || '');
|
||
// Update local data so filter stays consistent
|
||
if (item) item.status_bantuan = newStatus;
|
||
} else {
|
||
alert(j.message || 'Gagal update status.');
|
||
sel.value = orig;
|
||
}
|
||
} catch(e) {
|
||
alert('Gagal terhubung ke server.');
|
||
sel.value = orig;
|
||
}
|
||
}
|
||
|
||
async function loadRiwayat(id, nama) {
|
||
document.getElementById('riwayatTitle').textContent = 'Riwayat Bantuan — ' + nama;
|
||
document.getElementById('riwayatBody').innerHTML = '<span class="spinner"></span> Memuat...';
|
||
document.getElementById('riwayatModal').classList.add('show');
|
||
try {
|
||
const r = await fetch('../api/penduduk/riwayat.php?id=' + id + '&_=' + Date.now());
|
||
const j = await r.json();
|
||
if (!j.data || !j.data.length) {
|
||
document.getElementById('riwayatBody').innerHTML = '<p style="color:var(--text-muted);display:inline-flex;align-items:center;gap:6px;"><i data-lucide="info"></i> Belum ada riwayat perubahan status.</p>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
return;
|
||
}
|
||
document.getElementById('riwayatBody').innerHTML =
|
||
'<table class="admin-table" style="font-size:12px;">' +
|
||
'<thead><tr><th>Waktu</th><th>Oleh</th><th>Perubahan</th><th>Catatan</th></tr></thead>' +
|
||
'<tbody>' + j.data.map(rv => `<tr>
|
||
<td style="white-space:nowrap;color:var(--text-muted);">${esc(rv.created_at ? rv.created_at.slice(0, 16) : '—')}</td>
|
||
<td>${esc(rv.user_nama || '—')}</td>
|
||
<td style="white-space:nowrap;"><span style="color:var(--text-muted);">${esc(rv.status_lama || '—')}</span> → <strong>${esc(rv.status_baru || '—')}</strong></td>
|
||
<td style="color:var(--text-muted);">${esc(rv.catatan || '—')}</td>
|
||
</tr>`).join('') + '</tbody></table>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
} catch(e) {
|
||
document.getElementById('riwayatBody').innerHTML = '<span style="color:#ef4444;display:inline-flex;align-items:center;gap:6px;"><i data-lucide="x-circle"></i> Gagal memuat riwayat.</span>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
}
|
||
}
|
||
|
||
function showConfirm({ title, message, confirmLabel = 'Ya, Lanjutkan', cancelLabel = 'Batal', danger = false }) {
|
||
return new Promise(resolve => {
|
||
const el = document.createElement('div');
|
||
el.className = 'confirm-overlay';
|
||
el.innerHTML = `<div class="confirm-box">
|
||
<div class="confirm-title">${title}</div>
|
||
<p class="confirm-msg">${message}</p>
|
||
<div class="confirm-actions">
|
||
<button class="btn-secondary" id="_sc_cancel">${cancelLabel}</button>
|
||
<button class="${danger ? 'btn-danger' : 'btn-primary'}" id="_sc_ok">${confirmLabel}</button>
|
||
</div></div>`;
|
||
document.body.appendChild(el);
|
||
const done = val => { el.remove(); resolve(val); };
|
||
el.querySelector('#_sc_ok').onclick = () => done(true);
|
||
el.querySelector('#_sc_cancel').onclick = () => done(false);
|
||
el.addEventListener('click', e => { if (e.target === el) done(false); });
|
||
});
|
||
}
|
||
|
||
async function doNonaktifkan(id) {
|
||
const ok = await showConfirm({
|
||
title: 'Nonaktifkan Warga',
|
||
message: 'Tandai warga ini sebagai tidak aktif (meninggal/pindah)? Data akan diarsipkan.',
|
||
confirmLabel: 'Ya, Nonaktifkan', danger: true
|
||
});
|
||
if (!ok) return;
|
||
const fd = new FormData();
|
||
fd.append('id', id);
|
||
try {
|
||
const r = await fetch('../api/penduduk/nonaktifkan.php', { method: 'POST', body: appendCsrf(fd) });
|
||
const j = await r.json();
|
||
if (j.status === 'success') {
|
||
loadPd();
|
||
} else {
|
||
alert(j.message || 'Gagal menonaktifkan warga.');
|
||
}
|
||
} catch(e) {
|
||
alert('Gagal terhubung ke server.');
|
||
}
|
||
}
|
||
|
||
// ── Verifikasi langsung dari tabel ────────────────────────────
|
||
async function doVerifRow(btn, aksi) {
|
||
const id = btn.dataset.id;
|
||
const item = _pdAll.find(p => String(p.id) === String(id));
|
||
const label = aksi === 'approve' ? 'Terverifikasi' : 'Ditolak';
|
||
const nama = item?.nama_kk ? `"${item.nama_kk}"` : `#${id}`;
|
||
const ok = await showConfirm({
|
||
title: aksi === 'approve' ? 'Konfirmasi Verifikasi' : 'Konfirmasi Tolak',
|
||
message: `Ubah status verifikasi ${nama} menjadi <strong>${label}</strong>?`,
|
||
confirmLabel: aksi === 'approve' ? 'Ya, Verifikasi' : 'Ya, Tolak',
|
||
danger: aksi === 'reject'
|
||
});
|
||
if (!ok) return;
|
||
|
||
btn.disabled = true;
|
||
const fd = new FormData();
|
||
fd.append('id', id); fd.append('aksi', aksi);
|
||
try {
|
||
const r = await fetch('../api/penduduk/verifikasi.php', { method:'POST', body: appendCsrf(fd) });
|
||
const j = await r.json();
|
||
if (j.status === 'success') {
|
||
if (item) item.status_verifikasi = j.status_verifikasi;
|
||
applyFilter();
|
||
} else { alert(j.message || 'Gagal.'); btn.disabled = false; }
|
||
} catch { alert('Gagal terhubung.'); btn.disabled = false; }
|
||
}
|
||
|
||
// ── Map Picker untuk form tambah ──────────────────────────────
|
||
const PD_DEF_LAT = <?= CENTER_LAT ?>;
|
||
const PD_DEF_LNG = <?= CENTER_LNG ?>;
|
||
const PD_DEF_ZOOM = <?= ZOOM_LEVEL ?>;
|
||
let _pdMap = null, _pdMarker = null, _pdGeoTimer = null;
|
||
|
||
function initPdMap() {
|
||
if (_pdMap) { _pdMap.remove(); _pdMap = null; _pdMarker = null; }
|
||
_pdMap = L.map('pdMapPicker').setView([PD_DEF_LAT, PD_DEF_LNG], PD_DEF_ZOOM);
|
||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
maxZoom:19, attribution:'© OpenStreetMap'
|
||
}).addTo(_pdMap);
|
||
_pdMap.on('click', e => { pdPlaceMarker(e.latlng.lat, e.latlng.lng); pdFillCoords(e.latlng.lat, e.latlng.lng); pdGeocode(e.latlng.lat, e.latlng.lng); });
|
||
setTimeout(() => _pdMap && _pdMap.invalidateSize(), 80);
|
||
}
|
||
|
||
function pdPlaceMarker(lat, lng) {
|
||
if (_pdMarker) { _pdMarker.setLatLng([lat, lng]); }
|
||
else {
|
||
_pdMarker = L.marker([lat, lng], { draggable: true }).addTo(_pdMap);
|
||
_pdMarker.on('dragend', e => {
|
||
const ll = e.target.getLatLng();
|
||
pdFillCoords(ll.lat, ll.lng);
|
||
pdGeocode(ll.lat, ll.lng);
|
||
});
|
||
}
|
||
_pdMap.panTo([lat, lng]);
|
||
}
|
||
|
||
function pdFillCoords(lat, lng) {
|
||
document.getElementById('pdLat').value = lat.toFixed(8);
|
||
document.getElementById('pdLng').value = lng.toFixed(8);
|
||
}
|
||
|
||
function pdCoordsManual() {
|
||
const lat = parseFloat(document.getElementById('pdLat').value);
|
||
const lng = parseFloat(document.getElementById('pdLng').value);
|
||
if (!isNaN(lat) && !isNaN(lng) && _pdMap) { pdPlaceMarker(lat, lng); pdGeocode(lat, lng); }
|
||
}
|
||
|
||
async function pdGeocode(lat, lng) {
|
||
clearTimeout(_pdGeoTimer);
|
||
document.getElementById('pdGeoStatus').innerHTML = '<span class="spinner"></span> Mencari alamat...';
|
||
_pdGeoTimer = setTimeout(async () => {
|
||
try {
|
||
const r = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`, { headers:{'Accept-Language':'id'} });
|
||
const j = await r.json();
|
||
const alamatEl = document.getElementById('pdAlamat');
|
||
if (j && j.display_name && !alamatEl.dataset.userEdited) {
|
||
alamatEl.value = j.display_name;
|
||
}
|
||
document.getElementById('pdGeoStatus').innerHTML = j.display_name
|
||
? '<i data-lucide="check" style="color:var(--text-success);margin-right:4px;"></i> ' + esc(j.address?.road || j.address?.suburb || j.display_name.split(',')[0])
|
||
: '';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
} catch { document.getElementById('pdGeoStatus').textContent = ''; }
|
||
}, 600);
|
||
}
|
||
|
||
function openAddPd() {
|
||
['pdNama','pdNik','pdAlamat','pdCatatan','pdLat','pdLng'].forEach(id => {
|
||
const el = document.getElementById(id);
|
||
el.value = '';
|
||
delete el.dataset.userEdited;
|
||
});
|
||
document.getElementById('pdJiwa').value = 1;
|
||
document.getElementById('pdKategori').value = 'Miskin';
|
||
document.getElementById('pdMsg').textContent = '';
|
||
document.getElementById('pdGeoStatus').textContent = '';
|
||
document.getElementById('pdSaveBtn').disabled = false;
|
||
document.getElementById('pdSaveBtn').textContent = 'Simpan';
|
||
document.getElementById('pdModal').classList.add('show');
|
||
initPdMap();
|
||
}
|
||
|
||
function closeAddPd() {
|
||
document.getElementById('pdModal').classList.remove('show');
|
||
clearTimeout(_pdGeoTimer);
|
||
if (_pdMap) { _pdMap.remove(); _pdMap = null; _pdMarker = null; }
|
||
}
|
||
|
||
async function savePd() {
|
||
const btn = document.getElementById('pdSaveBtn');
|
||
const msg = document.getElementById('pdMsg');
|
||
const lat = document.getElementById('pdLat').value.trim();
|
||
const lng = document.getElementById('pdLng').value.trim();
|
||
const nama = document.getElementById('pdNama').value.trim();
|
||
|
||
if (!nama) { msg.textContent = 'Nama Kepala Keluarga wajib diisi.'; return; }
|
||
if (!lat || !lng) { msg.textContent = 'Klik peta untuk menentukan lokasi.'; return; }
|
||
|
||
btn.disabled = true; btn.innerHTML = '<span class="spinner"></span> Menyimpan...'; msg.textContent = '';
|
||
|
||
const fd = new FormData();
|
||
fd.append('nama_kk', nama);
|
||
fd.append('nik', document.getElementById('pdNik').value.trim());
|
||
fd.append('jumlah_jiwa',document.getElementById('pdJiwa').value);
|
||
fd.append('kategori', document.getElementById('pdKategori').value);
|
||
fd.append('alamat', document.getElementById('pdAlamat').value.trim());
|
||
fd.append('catatan', document.getElementById('pdCatatan').value.trim());
|
||
fd.append('lat', lat); fd.append('lng', lng);
|
||
|
||
try {
|
||
const r = await fetch('../api/penduduk/simpan.php', { method:'POST', body: appendCsrf(fd) });
|
||
const j = await r.json();
|
||
btn.disabled = false; btn.innerHTML = 'Simpan';
|
||
if (j.status === 'success') {
|
||
closeAddPd();
|
||
loadPd();
|
||
} else { msg.textContent = j.message || 'Terjadi kesalahan.'; }
|
||
} catch { btn.disabled = false; btn.innerHTML = 'Simpan'; msg.textContent = 'Gagal terhubung ke server.'; }
|
||
}
|
||
|
||
// ── Edit data penduduk ────────────────────────────────────────
|
||
function openEditPd(id) {
|
||
const p = _pdAll.find(x => String(x.id) === String(id));
|
||
if (!p) return;
|
||
document.getElementById('editPdId').value = p.id;
|
||
document.getElementById('editPdNama').value = p.nama_kk || '';
|
||
document.getElementById('editPdNik').value = p.nik || '';
|
||
document.getElementById('editPdJiwa').value = p.jumlah_jiwa || 1;
|
||
document.getElementById('editPdKategori').value = p.kategori || 'Miskin';
|
||
document.getElementById('editPdAlamat').value = p.alamat || '';
|
||
document.getElementById('editPdCatatan').value = p.catatan || '';
|
||
document.getElementById('editPdMsg').textContent = '';
|
||
document.getElementById('editPdSaveBtn').disabled = false;
|
||
document.getElementById('editPdSaveBtn').textContent = 'Simpan Perubahan';
|
||
document.getElementById('editPdModal').classList.add('show');
|
||
}
|
||
|
||
function closeEditPd() {
|
||
document.getElementById('editPdModal').classList.remove('show');
|
||
}
|
||
|
||
async function saveEditPd() {
|
||
const btn = document.getElementById('editPdSaveBtn');
|
||
const msg = document.getElementById('editPdMsg');
|
||
const nama = document.getElementById('editPdNama').value.trim();
|
||
if (!nama) { msg.textContent = 'Nama Kepala Keluarga wajib diisi.'; return; }
|
||
|
||
btn.disabled = true; btn.innerHTML = '<span class="spinner"></span> Menyimpan...'; msg.textContent = '';
|
||
|
||
const fd = new FormData();
|
||
fd.append('id', document.getElementById('editPdId').value);
|
||
fd.append('nama_kk', nama);
|
||
fd.append('nik', document.getElementById('editPdNik').value.trim());
|
||
fd.append('jumlah_jiwa', document.getElementById('editPdJiwa').value);
|
||
fd.append('kategori', document.getElementById('editPdKategori').value);
|
||
fd.append('alamat', document.getElementById('editPdAlamat').value.trim());
|
||
fd.append('catatan', document.getElementById('editPdCatatan').value.trim());
|
||
|
||
try {
|
||
const r = await fetch('../api/penduduk/update.php', { method: 'POST', body: appendCsrf(fd) });
|
||
const j = await r.json();
|
||
btn.disabled = false; btn.textContent = 'Simpan Perubahan';
|
||
if (j.status === 'success') {
|
||
closeEditPd();
|
||
loadPd();
|
||
} else {
|
||
msg.textContent = j.message || 'Terjadi kesalahan.';
|
||
}
|
||
} catch {
|
||
btn.disabled = false; btn.textContent = 'Simpan Perubahan';
|
||
msg.textContent = 'Gagal terhubung ke server.';
|
||
}
|
||
}
|
||
|
||
// ── Kelola Kebutuhan dari tabel ───────────────────────────────
|
||
const KEB_KAT_LIST = ['Sembako','Biaya Sekolah','Biaya Kesehatan','Modal Usaha','Renovasi Rumah','Perlengkapan Rumah','Pakaian','Lainnya'];
|
||
|
||
async function openKebPd(id, nama) {
|
||
document.getElementById('kebPdTitle').textContent = 'Kebutuhan — ' + nama;
|
||
document.getElementById('kebPdBody').innerHTML = '<span class="spinner"></span> Memuat...';
|
||
document.getElementById('kebPdModal').classList.add('show');
|
||
await renderKebPd(id);
|
||
}
|
||
|
||
async function renderKebPd(id) {
|
||
const clsMap = {'Belum Terpenuhi':'belum','Dalam Proses':'proses','Terpenuhi':'terpenuhi'};
|
||
try {
|
||
const r = await fetch('../api/kebutuhan/ambil.php?penduduk_id='+id+'&_='+Date.now());
|
||
const j = await r.json();
|
||
|
||
const listHtml = (!j.data || !j.data.length)
|
||
? '<p style="color:var(--text-muted);margin-bottom:4px;">Belum ada kebutuhan tercatat.</p>'
|
||
: j.data.map(k => `
|
||
<div style="padding:10px 0;border-bottom:1px solid var(--card-border);">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
|
||
<span style="font-weight:600;">${esc(k.kategori)}</span>
|
||
<select class="stat-sel ${clsMap[k.status]||''}"
|
||
data-id="${attr(parseInt(k.id,10)||0)}" data-orig="${attr(k.status||'')}" onchange="updateKebFromPd(this)">
|
||
<option ${k.status==='Belum Terpenuhi'?'selected':''}>Belum Terpenuhi</option>
|
||
<option ${k.status==='Dalam Proses'?'selected':''}>Dalam Proses</option>
|
||
<option ${k.status==='Terpenuhi'?'selected':''}>Terpenuhi</option>
|
||
</select>
|
||
</div>
|
||
${k.deskripsi?`<div style="font-size:12px;color:var(--text-muted);">${esc(k.deskripsi)}</div>`:''}
|
||
<div style="font-size:11px;color:var(--text-muted);margin-top:3px;">Oleh ${esc(k.created_by_nama||'—')} · ${esc((k.created_at||'').slice(0,10))}</div>
|
||
</div>`).join('');
|
||
|
||
const addFormHtml = `
|
||
<div style="margin-top:14px;padding-top:14px;border-top:2px dashed var(--card-border);">
|
||
<div style="font-size:11px;font-weight:700;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px;">Tambah Kebutuhan</div>
|
||
<select id="kebPdKat_${id}" style="width:100%;padding:8px 10px;border:1px solid var(--card-border);border-radius:7px;font-family:var(--font);font-size:13px;background:#fafaf9;color:var(--text-primary);outline:none;margin-bottom:8px;">
|
||
${KEB_KAT_LIST.map(k=>`<option value="${esc(k)}">${esc(k)}</option>`).join('')}
|
||
</select>
|
||
<input type="text" id="kebPdDesc_${id}" placeholder="Deskripsi (opsional, maks 300 karakter)" maxlength="300"
|
||
style="width:100%;padding:8px 10px;border:1px solid var(--card-border);border-radius:7px;font-family:var(--font);font-size:13px;background:#fafaf9;color:var(--text-primary);outline:none;margin-bottom:8px;box-sizing:border-box;">
|
||
<div style="display:flex;align-items:center;gap:8px;">
|
||
<button onclick="addKebFromPd(${id})" id="kebPdAddBtn_${id}" class="btn-primary" style="padding:7px 18px;font-size:13px;">Simpan</button>
|
||
<span id="kebPdAddMsg_${id}" style="font-size:12px;color:#ef4444;"></span>
|
||
</div>
|
||
</div>`;
|
||
|
||
document.getElementById('kebPdBody').innerHTML = listHtml + addFormHtml;
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
} catch(e) {
|
||
document.getElementById('kebPdBody').innerHTML = '<span style="color:#ef4444;">Gagal memuat kebutuhan.</span>';
|
||
}
|
||
}
|
||
|
||
async function updateKebFromPd(sel) {
|
||
const id = sel.dataset.id;
|
||
const orig = sel.dataset.orig;
|
||
const catatan = prompt('Catatan perubahan kebutuhan (opsional):', '');
|
||
if (catatan === null) { sel.value = orig; return; }
|
||
const fd = new FormData();
|
||
fd.append('id', id); fd.append('status', sel.value); fd.append('catatan', catatan);
|
||
try {
|
||
const r = await fetch('../api/kebutuhan/update_status.php', {method:'POST', body: appendCsrf(fd)});
|
||
const j = await r.json();
|
||
if (j.status === 'success') {
|
||
sel.dataset.orig = sel.value;
|
||
const clsMap = {'Belum Terpenuhi':'belum','Dalam Proses':'proses','Terpenuhi':'terpenuhi'};
|
||
sel.className = 'stat-sel ' + (clsMap[sel.value]||'');
|
||
} else { alert(j.message||'Gagal update.'); sel.value = orig; }
|
||
} catch(e) { alert('Gagal terhubung ke server.'); sel.value = orig; }
|
||
}
|
||
|
||
async function addKebFromPd(id) {
|
||
const kat = document.getElementById(`kebPdKat_${id}`).value;
|
||
const desc = document.getElementById(`kebPdDesc_${id}`).value.trim();
|
||
const btn = document.getElementById(`kebPdAddBtn_${id}`);
|
||
const msg = document.getElementById(`kebPdAddMsg_${id}`);
|
||
btn.disabled = true; btn.textContent = 'Menyimpan...'; msg.textContent = '';
|
||
const fd = new FormData();
|
||
fd.append('penduduk_id', id); fd.append('kategori', kat); fd.append('deskripsi', desc);
|
||
try {
|
||
const r = await fetch('../api/kebutuhan/simpan.php', {method:'POST', body: appendCsrf(fd)});
|
||
const j = await r.json();
|
||
if (j.status === 'success') {
|
||
await renderKebPd(id);
|
||
} else {
|
||
msg.textContent = j.message || 'Gagal menyimpan.';
|
||
btn.disabled = false; btn.textContent = 'Simpan';
|
||
}
|
||
} catch(e) {
|
||
msg.textContent = 'Gagal terhubung ke server.';
|
||
btn.disabled = false; btn.textContent = 'Simpan';
|
||
}
|
||
}
|
||
|
||
loadPd();
|
||
</script>
|
||
|
||
<!-- Modal Edit Penduduk -->
|
||
<div class="modal-dark" id="editPdModal">
|
||
<div class="modal-box">
|
||
<h3>Edit Data Penduduk</h3>
|
||
<div class="modal-scroll">
|
||
<input type="hidden" id="editPdId">
|
||
<div class="mfg">
|
||
<label>Nama Kepala Keluarga <span style="color:#ef4444;">*</span></label>
|
||
<input type="text" id="editPdNama" placeholder="Nama lengkap KK">
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||
<div class="mfg">
|
||
<label>NIK <small style="text-transform:none;">(opsional)</small></label>
|
||
<input type="text" id="editPdNik" placeholder="16 digit" maxlength="16">
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Jumlah Jiwa <small style="text-transform:none;">(orang)</small></label>
|
||
<input type="number" id="editPdJiwa" value="1" min="1" max="30">
|
||
</div>
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Kategori Kemiskinan</label>
|
||
<select id="editPdKategori">
|
||
<option value="Sangat Miskin">Sangat Miskin</option>
|
||
<option value="Miskin" selected>Miskin</option>
|
||
<option value="Hampir Miskin">Hampir Miskin</option>
|
||
</select>
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Alamat</label>
|
||
<input type="text" id="editPdAlamat" placeholder="Alamat lengkap">
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Catatan Kondisi <small style="text-transform:none;">(opsional)</small></label>
|
||
<textarea id="editPdCatatan" placeholder="Kondisi rumah, kebutuhan khusus, dll..."></textarea>
|
||
</div>
|
||
<div class="msg-err" id="editPdMsg"></div>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" onclick="closeEditPd()">Batal</button>
|
||
<button class="btn-primary" id="editPdSaveBtn" onclick="saveEditPd()">Simpan Perubahan</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal Kelola Kebutuhan -->
|
||
<div class="modal-dark" id="kebPdModal">
|
||
<div class="modal-box">
|
||
<h3 id="kebPdTitle">Kebutuhan</h3>
|
||
<div class="modal-scroll" id="kebPdBody" style="font-size:13px;"></div>
|
||
<div style="margin-top:14px;flex-shrink:0;display:flex;gap:8px;">
|
||
<button style="padding:8px 16px;background:transparent;border:1px solid var(--card-border);border-radius:8px;font-family:var(--font);font-size:13px;font-weight:600;color:var(--text-secondary);cursor:pointer;" onclick="document.getElementById('kebPdModal').classList.remove('show')">Tutup</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal Tambah Penduduk -->
|
||
<div class="modal-dark" id="pdModal">
|
||
<div class="modal-box">
|
||
<h3>Tambah Penduduk Miskin</h3>
|
||
<div class="modal-scroll">
|
||
|
||
<!-- Peta -->
|
||
<div class="mfg">
|
||
<label>Lokasi di Peta <span style="font-weight:400;color:var(--text-muted);text-transform:none;letter-spacing:0;">— klik untuk set titik</span></label>
|
||
<div class="map-picker-wrap"><div id="pdMapPicker"></div></div>
|
||
<div class="map-picker-hint" style="display:flex;align-items:center;gap:12px;margin-top:6px;">
|
||
<span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="mouse-pointer"></i> Klik peta</span> · <span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="map-pin"></i> Drag marker</span> · <span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="zoom-in"></i> Scroll zoom</span>
|
||
</div>
|
||
<div class="map-geocoding" id="pdGeoStatus"></div>
|
||
</div>
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||
<div class="mfg">
|
||
<label>Latitude</label>
|
||
<input type="number" id="pdLat" step="any" placeholder="-0.0557" oninput="pdCoordsManual()">
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Longitude</label>
|
||
<input type="number" id="pdLng" step="any" placeholder="109.3487" oninput="pdCoordsManual()">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Data Identitas -->
|
||
<div class="mfg">
|
||
<label>Nama Kepala Keluarga <span style="color:#ef4444;">*</span></label>
|
||
<input type="text" id="pdNama" placeholder="Nama lengkap KK">
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||
<div class="mfg">
|
||
<label>NIK <small style="text-transform:none;">(opsional)</small></label>
|
||
<input type="text" id="pdNik" placeholder="16 digit" maxlength="16">
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Jumlah Jiwa</label>
|
||
<input type="number" id="pdJiwa" value="1" min="1" max="30">
|
||
</div>
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Kategori Kemiskinan</label>
|
||
<select id="pdKategori">
|
||
<option value="Sangat Miskin">Sangat Miskin</option>
|
||
<option value="Miskin" selected>Miskin</option>
|
||
<option value="Hampir Miskin">Hampir Miskin</option>
|
||
</select>
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Alamat</label>
|
||
<input type="text" id="pdAlamat" placeholder="Alamat lengkap"
|
||
oninput="this.dataset.userEdited='1'">
|
||
</div>
|
||
<div class="mfg">
|
||
<label>Catatan Kondisi <small style="text-transform:none;">(opsional)</small></label>
|
||
<textarea id="pdCatatan" placeholder="Kondisi rumah, kebutuhan khusus, dll..."></textarea>
|
||
</div>
|
||
|
||
<?php if ($is_admin): ?>
|
||
<div style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;padding:10px 14px;font-size:12px;color:#16a34a;margin-bottom:4px;display:inline-flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;">
|
||
<i data-lucide="check-circle" style="color:#16a34a;flex-shrink:0;"></i> <span>Sebagai Admin, data yang disimpan langsung berstatus <strong>Terverifikasi</strong>.</span>
|
||
</div>
|
||
<?php else: ?>
|
||
<div style="background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:10px 14px;font-size:12px;color:#d97706;margin-bottom:4px;display:inline-flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;">
|
||
<i data-lucide="clock" style="color:#d97706;flex-shrink:0;"></i> <span>Data akan berstatus <strong>Pending</strong> hingga diverifikasi oleh Administrator.</span>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="msg-err" id="pdMsg"></div>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" onclick="closeAddPd()">Batal</button>
|
||
<button class="btn-primary" id="pdSaveBtn" onclick="savePd()">Simpan</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php include '../includes/page-end.php'; ?>
|