925 lines
50 KiB
HTML
925 lines
50 KiB
HTML
<!doctype html>
|
||
<html lang="id">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Admin Poverty Mapping Gereja</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet" />
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||
<style>
|
||
:root {
|
||
--bg-dark: #0f172a;
|
||
--bg-panel: #111827;
|
||
--bg-soft: rgba(30, 41, 59, 0.72);
|
||
--bg-card: rgba(255, 255, 255, 0.92);
|
||
--text-main: #f8fafc;
|
||
--text-dark: #0f172a;
|
||
--text-muted: #64748b;
|
||
--border: rgba(148, 163, 184, 0.25);
|
||
--accent: #7c3aed;
|
||
--accent-2: #06b6d4;
|
||
--danger: #dc2626;
|
||
--orange: #f97316;
|
||
--yellow: #eab308;
|
||
--green: #22c55e;
|
||
--radius: 22px;
|
||
--shadow: 0 24px 70px rgba(15, 23, 42, 0.24);
|
||
}
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
font-family: "Inter", system-ui, sans-serif;
|
||
color: var(--text-dark);
|
||
min-height: 100vh;
|
||
background:
|
||
radial-gradient(circle at top left, rgba(124, 58, 237, 0.32), transparent 34%),
|
||
linear-gradient(135deg, #130f2e 0%, #111827 52%, #312e81 100%);
|
||
}
|
||
a { color: inherit; text-decoration: none; }
|
||
button, input, select, textarea { font: inherit; }
|
||
.shell { display: grid; grid-template-columns: 280px minmax(0, 1fr); min-height: 100vh; }
|
||
.sidebar {
|
||
background: linear-gradient(180deg, var(--bg-dark), var(--bg-panel));
|
||
color: var(--text-main);
|
||
padding: 24px 18px;
|
||
border-right: 1px solid rgba(255,255,255,.08);
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
overflow-y: auto;
|
||
}
|
||
.brand { margin-bottom: 26px; }
|
||
.brand small { color: #a5b4fc; font-weight: 700; text-transform: uppercase; }
|
||
.brand h1 { margin: 8px 0 6px; font-size: 1.45rem; }
|
||
.brand p { margin: 0; color: #cbd5e1; line-height: 1.45; font-size: .88rem; }
|
||
.nav { display: grid; gap: 9px; }
|
||
.nav button, .nav a {
|
||
border: 1px solid transparent;
|
||
background: rgba(255,255,255,.06);
|
||
color: #e5e7eb;
|
||
border-radius: 16px;
|
||
padding: 12px 14px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font-weight: 700;
|
||
}
|
||
.nav button.active, .nav button:hover, .nav a:hover {
|
||
background: rgba(124, 58, 237, .22);
|
||
border-color: rgba(167, 139, 250, .42);
|
||
}
|
||
.main { padding: 26px; min-width: 0; }
|
||
.topbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
align-items: center;
|
||
color: var(--text-main);
|
||
margin-bottom: 22px;
|
||
}
|
||
.topbar h2 { margin: 0; font-size: clamp(1.4rem, 2.3vw, 2.1rem); }
|
||
.topbar p { margin: 5px 0 0; color: #cbd5e1; }
|
||
.btn {
|
||
border: 0;
|
||
border-radius: 15px;
|
||
padding: 11px 16px;
|
||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||
color: white;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
box-shadow: 0 14px 32px rgba(37, 99, 235, .2);
|
||
}
|
||
.btn.secondary { background: rgba(15, 23, 42, .08); color: var(--text-dark); box-shadow: none; border: 1px solid var(--border); }
|
||
.btn.danger { background: #fee2e2; color: var(--danger); box-shadow: none; }
|
||
.btn.small { padding: 8px 10px; border-radius: 12px; font-size: .78rem; }
|
||
.section { display: none; }
|
||
.section.active { display: block; }
|
||
.stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-bottom: 18px; }
|
||
.stat, .card {
|
||
background: var(--bg-card);
|
||
border: 1px solid rgba(255,255,255,.5);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
backdrop-filter: blur(14px);
|
||
}
|
||
.stat { padding: 18px; }
|
||
.stat span { color: var(--text-muted); font-size: .84rem; font-weight: 700; }
|
||
.stat strong { display: block; margin-top: 8px; font-size: 1.7rem; }
|
||
.grid-2 { display: grid; grid-template-columns: minmax(340px, .78fr) minmax(0, 1.22fr); gap: 18px; }
|
||
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
|
||
.card { overflow: hidden; margin-bottom: 18px; }
|
||
.card-head { padding: 16px 18px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; gap: 10px; }
|
||
.card-head h3 { margin: 0; font-size: 1rem; }
|
||
.card-body { padding: 18px; }
|
||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 13px; }
|
||
.form-grid .full { grid-column: 1 / -1; }
|
||
label { display: grid; gap: 7px; color: #334155; font-size: .82rem; font-weight: 800; }
|
||
input, select, textarea {
|
||
width: 100%;
|
||
border: 1px solid #dbe2ea;
|
||
border-radius: 14px;
|
||
padding: 11px 12px;
|
||
outline: none;
|
||
background: rgba(248,250,252,.9);
|
||
color: var(--text-dark);
|
||
}
|
||
textarea { min-height: 78px; resize: vertical; }
|
||
input:focus, select:focus, textarea:focus { border-color: #a78bfa; box-shadow: 0 0 0 4px rgba(124,58,237,.12); }
|
||
.actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
|
||
.table-wrap { overflow: auto; }
|
||
table { width: 100%; border-collapse: collapse; min-width: 760px; }
|
||
th, td { padding: 12px 14px; border-bottom: 1px solid #e5e7eb; text-align: left; vertical-align: top; }
|
||
th { color: #475569; font-size: .75rem; text-transform: uppercase; background: #f8fafc; }
|
||
td { font-size: .88rem; }
|
||
.badge { display: inline-flex; border-radius: 999px; padding: 5px 10px; font-weight: 800; font-size: .72rem; }
|
||
.sangat { background: #fee2e2; color: #7f1d1d; }
|
||
.butuh { background: #ffedd5; color: #7c2d12; }
|
||
.rentan { background: #fef9c3; color: #713f12; }
|
||
.mampu { background: #dcfce7; color: #14532d; }
|
||
/* Status pengajuan */
|
||
.status-menunggu { background: #fef3c7; color: #92400e; }
|
||
.status-diverifikasi { background: #dbeafe; color: #1e40af; }
|
||
.status-disetujui { background: #dcfce7; color: #14532d; }
|
||
.status-ditolak { background: #fee2e2; color: #7f1d1d; }
|
||
.status-selesai { background: #e0e7ff; color: #3730a3; }
|
||
.bar-row { display: grid; grid-template-columns: 150px 1fr 48px; gap: 10px; align-items: center; margin: 10px 0; font-size: .86rem; }
|
||
.bar-track { height: 12px; border-radius: 99px; background: #e2e8f0; overflow: hidden; }
|
||
.bar-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--accent), var(--accent-2)); }
|
||
#adminMap { height: 520px; border-radius: var(--radius); overflow: hidden; }
|
||
.toast {
|
||
position: fixed;
|
||
right: 22px;
|
||
bottom: 22px;
|
||
background: #0f172a;
|
||
color: white;
|
||
padding: 13px 16px;
|
||
border-radius: 14px;
|
||
box-shadow: var(--shadow);
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
pointer-events: none;
|
||
transition: .2s ease;
|
||
z-index: 2000;
|
||
}
|
||
.toast.show { opacity: 1; transform: translateY(0); }
|
||
.ibadah-map { height: 300px; position: relative; border-radius: var(--radius); overflow: hidden; border: 1px solid #dbe2ea; background: #eef2f7; }
|
||
.coord-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
||
.ibadah-badge-gereja-katolik { background: #ede9fe; color: #4c1d95; }
|
||
.ibadah-badge-gpib { background: #dbeafe; color: #1e3a8a; }
|
||
.ibadah-badge-gki { background: #cffafe; color: #164e63; }
|
||
.ibadah-badge-hkbp { background: #dcfce7; color: #14532d; }
|
||
.ibadah-badge-pentakosta { background: #ffedd5; color: #7c2d12; }
|
||
.ibadah-pin {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 999px;
|
||
background: #7c3aed;
|
||
color: #fff;
|
||
border: 3px solid #fff;
|
||
box-shadow: 0 10px 22px rgba(15, 23, 42, .25);
|
||
font-size: 16px;
|
||
}
|
||
@media (max-width: 980px) {
|
||
.shell { grid-template-columns: 1fr; }
|
||
.sidebar { position: relative; height: auto; }
|
||
.stats, .grid-2, .grid-3 { grid-template-columns: 1fr; }
|
||
}
|
||
@media (max-width: 640px) {
|
||
.main { padding: 16px; }
|
||
.topbar { align-items: flex-start; flex-direction: column; }
|
||
.form-grid { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="toast" id="toast"></div>
|
||
<div class="shell">
|
||
<aside class="sidebar">
|
||
<div class="brand">
|
||
<small>WebGIS Gereja</small>
|
||
<h1>Admin Poverty Mapping</h1>
|
||
<p>Pengelolaan data keluarga, bantuan, kriteria SAW, dan ranking prioritas bantuan.</p>
|
||
</div>
|
||
<nav class="nav">
|
||
<button class="active" data-section="dashboard">Dashboard</button>
|
||
<button data-section="keluarga">Data Keluarga</button>
|
||
<button data-section="bantuan">Data Bantuan</button>
|
||
<button data-section="kriteria">Kriteria SAW</button>
|
||
<button data-section="ibadah">⛪ Tempat Ibadah</button>
|
||
<button data-section="pengajuan">📋 Pengajuan Bantuan</button>
|
||
<button data-section="peta">Preview Peta</button>
|
||
<a href="/poverty-map.html">Buka Peta Poverty</a>
|
||
<a href="/pengajuan.html">Form Pengajuan Publik</a>
|
||
<a href="/admin.html">Admin SPBU</a>
|
||
</nav>
|
||
</aside>
|
||
|
||
<main class="main">
|
||
<header class="topbar">
|
||
<div>
|
||
<h2>Dashboard Bantuan Gereja</h2>
|
||
<p>SAW otomatis: normalisasi, nilai terbobot, kategori, dan ranking prioritas.</p>
|
||
</div>
|
||
<button class="btn" id="recalculateBtn">Hitung Ulang SAW</button>
|
||
</header>
|
||
|
||
<section class="section active" id="dashboard">
|
||
<div class="stats">
|
||
<article class="stat"><span>Total keluarga</span><strong id="statTotal">0</strong></article>
|
||
<article class="stat"><span>Prioritas tinggi</span><strong id="statPrioritas">0</strong></article>
|
||
<article class="stat"><span>Bantuan tersalurkan</span><strong id="statBantuan">Rp0</strong></article>
|
||
<article class="stat"><span>Kriteria aktif</span><strong id="statKriteria">0</strong></article>
|
||
</div>
|
||
<div class="grid-3">
|
||
<article class="card"><div class="card-head"><h3>Grafik Kategori Ekonomi</h3></div><div class="card-body" id="chartKategori"></div></article>
|
||
<article class="card"><div class="card-head"><h3>Bantuan Tersalurkan</h3></div><div class="card-body" id="chartBantuan"></div></article>
|
||
<article class="card"><div class="card-head"><h3>Distribusi Wilayah</h3></div><div class="card-body" id="chartWilayah"></div></article>
|
||
</div>
|
||
<div class="grid-2">
|
||
<article class="card"><div class="card-head"><h3>Ranking 10 Prioritas</h3></div><div class="table-wrap"><table><thead><tr><th>Rank</th><th>Keluarga</th><th>Skor</th><th>Kategori</th></tr></thead><tbody id="rankBody"></tbody></table></div></article>
|
||
<article class="card"><div class="card-head"><h3>Tren Kondisi Ekonomi</h3></div><div class="card-body" id="chartTren"></div></article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section" id="keluarga">
|
||
<div class="grid-2">
|
||
<article class="card">
|
||
<div class="card-head"><h3>Form Data Keluarga</h3><button class="btn secondary small" id="resetKeluarga">Reset</button></div>
|
||
<div class="card-body">
|
||
<form id="keluargaForm">
|
||
<input type="hidden" id="id_keluarga" />
|
||
<div class="form-grid">
|
||
<label>Nomor KK<input id="nomor_kartu_keluarga" required /></label>
|
||
<label>Nama Kepala Keluarga<input id="nama_kepala_keluarga" required /></label>
|
||
<label class="full">Alamat<textarea id="alamat" required></textarea></label>
|
||
<label>Wilayah/Paroki<input id="wilayah" required /></label>
|
||
<label>Tanggal Update<input id="tanggal_update" type="date" required /></label>
|
||
<label>Latitude<input id="latitude" type="number" step="any" required /></label>
|
||
<label>Longitude<input id="longitude" type="number" step="any" required /></label>
|
||
<label>Jumlah Anggota<input id="jumlah_anggota" type="number" min="1" required /></label>
|
||
<label>Jumlah Tanggungan<input id="jumlah_tanggungan" type="number" min="0" required /></label>
|
||
<label>Pendapatan Bulanan<input id="pendapatan_bulanan" type="number" min="0" required /></label>
|
||
<label>Pekerjaan<select id="pekerjaan" required></select></label>
|
||
<label>Pendidikan<select id="pendidikan" required></select></label>
|
||
<label>Kondisi Rumah<select id="kondisi_rumah" required></select></label>
|
||
<label>Kondisi Kesehatan<select id="kondisi_kesehatan" required></select></label>
|
||
<label>Status Verifikasi<select id="status_verifikasi"><option value="pending">Pending</option><option value="terverifikasi">Terverifikasi</option><option value="ditolak">Ditolak</option></select></label>
|
||
</div>
|
||
<div class="actions"><button class="btn" type="submit">Simpan Keluarga</button></div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
<article class="card">
|
||
<div class="card-head"><h3>Daftar Keluarga</h3><input id="familySearch" placeholder="Cari nama atau wilayah" /></div>
|
||
<div class="table-wrap"><table><thead><tr><th>Ranking</th><th>Keluarga</th><th>Ekonomi</th><th>Skor</th><th>Aksi</th></tr></thead><tbody id="keluargaBody"></tbody></table></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section" id="bantuan">
|
||
<div class="grid-2">
|
||
<article class="card">
|
||
<div class="card-head"><h3>Form Bantuan</h3><button class="btn secondary small" id="resetBantuan">Reset</button></div>
|
||
<div class="card-body">
|
||
<form id="bantuanForm">
|
||
<input type="hidden" id="id_bantuan" />
|
||
<div class="form-grid">
|
||
<label>Jenis Bantuan<input id="jenis_bantuan" required /></label>
|
||
<label>Tanggal Bantuan<input id="tanggal_bantuan" type="date" required /></label>
|
||
<label>Nilai Bantuan<input id="nilai_bantuan" type="number" min="0" required /></label>
|
||
<label class="full">Keterangan<textarea id="keterangan_bantuan"></textarea></label>
|
||
</div>
|
||
<div class="actions"><button class="btn" type="submit">Simpan Bantuan</button></div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
<article class="card">
|
||
<div class="card-head"><h3>Catat Penerima Bantuan</h3><button class="btn secondary small" id="resetRiwayat">Reset</button></div>
|
||
<div class="card-body">
|
||
<form id="riwayatForm">
|
||
<input type="hidden" id="id_riwayat" />
|
||
<div class="form-grid">
|
||
<label>Penerima<select id="riwayat_keluarga" required></select></label>
|
||
<label>Jenis Bantuan<select id="riwayat_bantuan" required></select></label>
|
||
<label>Tanggal Diterima<input id="tanggal_diterima" type="date" required /></label>
|
||
<label>Jumlah/Nilai<input id="jumlah_bantuan" type="number" min="0" required /></label>
|
||
<label class="full">Keterangan<textarea id="keterangan_riwayat"></textarea></label>
|
||
</div>
|
||
<div class="actions"><button class="btn" type="submit">Simpan Riwayat</button></div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<div class="grid-2">
|
||
<article class="card"><div class="card-head"><h3>Data Bantuan</h3></div><div class="table-wrap"><table><thead><tr><th>Jenis</th><th>Tanggal</th><th>Nilai</th><th>Aksi</th></tr></thead><tbody id="bantuanBody"></tbody></table></div></article>
|
||
<article class="card"><div class="card-head"><h3>Riwayat Bantuan</h3></div><div class="table-wrap"><table><thead><tr><th>Penerima</th><th>Bantuan</th><th>Tanggal</th><th>Jumlah</th><th>Aksi</th></tr></thead><tbody id="riwayatBody"></tbody></table></div></article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section" id="kriteria">
|
||
<div class="grid-2">
|
||
<article class="card">
|
||
<div class="card-head"><h3>Form Kriteria SAW</h3><button class="btn secondary small" id="resetKriteria">Reset</button></div>
|
||
<div class="card-body">
|
||
<form id="kriteriaForm">
|
||
<input type="hidden" id="id_kriteria" />
|
||
<div class="form-grid">
|
||
<label>Kode<input id="kode" required /></label>
|
||
<label>Nama Kriteria<input id="nama_kriteria" required /></label>
|
||
<label>Bobot<input id="bobot" type="number" min="0" max="1" step="0.01" required /></label>
|
||
<label>Atribut<select id="atribut"><option value="benefit">Benefit</option><option value="cost">Cost</option></select></label>
|
||
</div>
|
||
<div class="actions"><button class="btn" type="submit">Simpan Kriteria</button></div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
<article class="card"><div class="card-head"><h3>Data Kriteria</h3></div><div class="table-wrap"><table><thead><tr><th>Kriteria</th><th>Bobot</th><th>Atribut</th><th>Status</th><th>Aksi</th></tr></thead><tbody id="kriteriaBody"></tbody></table></div></article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section" id="ibadah">
|
||
<article class="card">
|
||
<div class="card-head"><h3>Form Tempat Ibadah</h3><button class="btn secondary small" id="resetIbadah">Reset</button></div>
|
||
<div class="card-body">
|
||
<form id="ibadahForm">
|
||
<input type="hidden" id="id_ibadah" />
|
||
<div class="form-grid">
|
||
<label>Nama<input id="ibadah_nama" required /></label>
|
||
<label>Jenis<select id="ibadah_jenis" required></select></label>
|
||
<label>Alamat<input id="ibadah_alamat" required /></label>
|
||
<label>Kelurahan<input id="ibadah_kelurahan" required /></label>
|
||
<label>Kapasitas Bantuan (KK)<input id="ibadah_kapasitas" type="number" min="0" value="0" /></label>
|
||
<div></div>
|
||
<label class="full">Keterangan / Catatan<textarea id="ibadah_keterangan" placeholder="Jadwal bantuan, kontak, dll."></textarea></label>
|
||
<div class="full">
|
||
<label>Koordinat dari Peta</label>
|
||
<div id="ibadahMap" class="ibadah-map"></div>
|
||
</div>
|
||
<label>Lat<input id="ibadah_lat" type="number" step="any" readonly required /></label>
|
||
<label>Lng<input id="ibadah_lng" type="number" step="any" readonly required /></label>
|
||
<div class="full coord-actions">
|
||
<button class="btn secondary small" id="toggleManualIbadah" type="button">✏️ Edit Manual</button>
|
||
<button class="btn danger small" id="clearIbadahPin" type="button">× Hapus Pin</button>
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn" id="ibadahSubmit" type="submit">Simpan Tempat Ibadah</button>
|
||
<button class="btn secondary" id="cancelIbadahEdit" type="button" style="display:none">Batal Edit</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
<article class="card">
|
||
<div class="card-head"><h3>Daftar Tempat Ibadah</h3><span id="ibadahCount">0 data</span></div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>#</th><th>Nama</th><th>Jenis</th><th>Alamat</th><th>Kelurahan</th><th>Koordinat</th><th>Kapasitas</th><th>Aksi</th></tr></thead>
|
||
<tbody id="ibadahBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</article>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════ PENGAJUAN BANTUAN ═══════════════════════ -->
|
||
<section class="section" id="pengajuan">
|
||
<div class="stats" style="grid-template-columns:repeat(3,minmax(0,1fr))">
|
||
<article class="stat"><span>Total Pengajuan</span><strong id="statPengajuanTotal">0</strong></article>
|
||
<article class="stat"><span>Menunggu</span><strong id="statPengajuanMenunggu">0</strong></article>
|
||
<article class="stat"><span>Disetujui</span><strong id="statPengajuanDisetujui">0</strong></article>
|
||
</div>
|
||
<article class="card" style="margin-top:18px">
|
||
<div class="card-head">
|
||
<h3>Daftar Pengajuan Bantuan</h3>
|
||
<select id="filterPengajuanStatus" style="min-width:160px;border-radius:10px;padding:7px 10px;border:1px solid #dbe2ea;font-size:.82rem">
|
||
<option value="">Semua Status</option>
|
||
<option value="menunggu">Menunggu</option>
|
||
<option value="diverifikasi">Diverifikasi</option>
|
||
<option value="disetujui">Disetujui</option>
|
||
<option value="ditolak">Ditolak</option>
|
||
<option value="selesai">Selesai</option>
|
||
</select>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>#</th><th>Pemohon</th><th>NIK</th><th>Wilayah</th><th>Alasan</th><th>Tgl Pengajuan</th><th>Status</th><th>Aksi</th></tr></thead>
|
||
<tbody id="pengajuanBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Detail / Update Status Modal (inline) -->
|
||
<article class="card" id="pengajuanDetailCard" style="display:none;margin-top:18px">
|
||
<div class="card-head">
|
||
<h3>Update Status Pengajuan</h3>
|
||
<button class="btn secondary small" onclick="el('pengajuanDetailCard').style.display='none'">Tutup</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<p id="pengajuanDetailInfo" style="color:#475569;margin:0 0 14px"></p>
|
||
<div class="form-grid">
|
||
<label>Status Baru
|
||
<select id="pengajuanNewStatus">
|
||
<option value="menunggu">Menunggu</option>
|
||
<option value="diverifikasi">Diverifikasi</option>
|
||
<option value="disetujui">Disetujui</option>
|
||
<option value="ditolak">Ditolak</option>
|
||
<option value="selesai">Selesai</option>
|
||
</select>
|
||
</label>
|
||
<label>Hubungkan ke Keluarga (opsional)
|
||
<select id="pengajuanKeluarga"><option value="">— Tidak dihubungkan —</option></select>
|
||
</label>
|
||
<label class="full">Catatan Admin
|
||
<textarea id="pengajuanCatatan" placeholder="Alasan penolakan, catatan verifikasi, dll."></textarea>
|
||
</label>
|
||
</div>
|
||
<input type="hidden" id="pengajuanEditId" />
|
||
<div class="actions">
|
||
<button class="btn" id="savePengajuanStatus">Simpan Status</button>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</section>
|
||
|
||
<section class="section" id="peta">
|
||
<article class="card">
|
||
<div class="card-head"><h3>Preview Peta Keluarga Prioritas</h3><span>Klik peta untuk mengisi koordinat form keluarga.</span></div>
|
||
<div class="card-body"><div id="adminMap"></div></div>
|
||
</article>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script>
|
||
const API = "/api/poverty";
|
||
const state = { keluarga: [], bantuan: [], riwayat: [], kriteria: [], ibadah: [], pengajuan: [], dashboard: null, analytics: null };
|
||
const today = new Date().toISOString().slice(0, 10);
|
||
const DEFAULT_CENTER = [-0.0263, 109.3425];
|
||
const IBADAH_TYPES = ["Gereja Katolik", "GPIB", "GKI", "HKBP", "Pentakosta"];
|
||
const mapsInitialized = { ibadah: false };
|
||
const labels = {
|
||
pekerjaan: { pengangguran: "Pengangguran", buruh_harian: "Buruh Harian", petani: "Petani", nelayan: "Nelayan", pedagang_kecil: "Pedagang Kecil", karyawan: "Karyawan", wiraswasta: "Wiraswasta", pns: "PNS", pensiunan: "Pensiunan", lainnya: "Lainnya" },
|
||
pendidikan: { tidak_sekolah: "Tidak Sekolah", sd: "SD", smp: "SMP", sma: "SMA", diploma: "Diploma", sarjana: "Sarjana" },
|
||
kondisi_rumah: { sangat_buruk: "Sangat Buruk", buruk: "Buruk", semi_permanen: "Semi Permanen", sederhana: "Sederhana", layak: "Layak" },
|
||
kondisi_kesehatan: { disabilitas: "Disabilitas", kronis: "Kronis", lansia: "Lansia", sakit_sering: "Sakit Sering", sakit_ringan: "Sakit Ringan", sehat: "Sehat" }
|
||
};
|
||
|
||
const el = (id) => document.getElementById(id);
|
||
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]));
|
||
const rp = (value) => new Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR", maximumFractionDigits: 0 }).format(Number(value || 0));
|
||
const fmt = (value) => value ? String(value).slice(0, 10) : "";
|
||
const badgeClass = (kategori) => kategori === "Sangat Membutuhkan" ? "sangat" : kategori === "Membutuhkan" ? "butuh" : kategori === "Rentan" ? "rentan" : "mampu";
|
||
const ibadahBadgeClass = (jenis) => `ibadah-badge-${String(jenis || "").toLowerCase().replace(/\s+/g, "-")}`;
|
||
|
||
async function api(path, options = {}) {
|
||
const response = await fetch(`${API}${path}`, { headers: { "Content-Type": "application/json", ...(options.headers || {}) }, ...options });
|
||
const payload = await response.json();
|
||
if (!response.ok || payload.success === false) throw new Error(payload.message || "Request gagal");
|
||
return payload.data ?? payload;
|
||
}
|
||
|
||
function toast(message) {
|
||
el("toast").textContent = message;
|
||
el("toast").classList.add("show");
|
||
setTimeout(() => el("toast").classList.remove("show"), 2200);
|
||
}
|
||
|
||
function fillOptions(id, items) {
|
||
el(id).innerHTML = Object.entries(items).map(([value, label]) => `<option value="${value}">${label}</option>`).join("");
|
||
}
|
||
|
||
function renderBars(containerId, data, formatter = (value) => value) {
|
||
const entries = Object.entries(data || {});
|
||
const max = Math.max(1, ...entries.map(([, value]) => Number(value) || 0));
|
||
el(containerId).innerHTML = entries.length ? entries.map(([label, value]) => `
|
||
<div class="bar-row"><span>${esc(label)}</span><div class="bar-track"><div class="bar-fill" style="width:${Math.max(3, (Number(value) / max) * 100)}%"></div></div><strong>${formatter(value)}</strong></div>
|
||
`).join("") : "<p>Belum ada data.</p>";
|
||
}
|
||
|
||
function renderDashboard() {
|
||
const d = state.dashboard || {};
|
||
el("statTotal").textContent = d.total_keluarga || 0;
|
||
el("statPrioritas").textContent = d.prioritas_tinggi || 0;
|
||
el("statBantuan").textContent = rp(d.total_bantuan_tersalurkan || 0);
|
||
el("statKriteria").textContent = state.kriteria.filter((item) => item.aktif).length;
|
||
renderBars("chartKategori", d.kategori_ekonomi || {});
|
||
renderBars("chartWilayah", d.wilayah || {});
|
||
const bantuanBulanan = {};
|
||
(state.analytics?.bantuan_bulanan || []).forEach((item) => bantuanBulanan[item.bulan] = Number(item.total || 0));
|
||
renderBars("chartBantuan", bantuanBulanan, (value) => rp(value));
|
||
const tren = {};
|
||
(state.analytics?.tren_ekonomi || []).forEach((item) => tren[item.bulan] = Number(item.rata_skor || 0).toFixed(2));
|
||
renderBars("chartTren", tren);
|
||
el("rankBody").innerHTML = (d.ranking_top10 || []).map((item) => `
|
||
<tr><td><strong>#${item.ranking || "-"}</strong></td><td>${esc(item.nama_kepala_keluarga)}<br><small>${esc(item.wilayah)}</small></td><td>${Number(item.skor_saw || 0).toFixed(3)}</td><td><span class="badge ${badgeClass(item.kategori)}">${esc(item.kategori)}</span></td></tr>
|
||
`).join("") || `<tr><td colspan="4">Belum ada data.</td></tr>`;
|
||
}
|
||
|
||
function filteredFamilies() {
|
||
const query = el("familySearch").value.toLowerCase().trim();
|
||
return state.keluarga.filter((item) => !query || item.nama_kepala_keluarga.toLowerCase().includes(query) || item.wilayah.toLowerCase().includes(query));
|
||
}
|
||
|
||
function renderKeluarga() {
|
||
el("keluargaBody").innerHTML = filteredFamilies().map((item) => `
|
||
<tr>
|
||
<td><strong>#${item.ranking || "-"}</strong><br><small>${esc(item.status_verifikasi)}</small></td>
|
||
<td><strong>${esc(item.nama_kepala_keluarga)}</strong><br><small>${esc(item.alamat)}</small><br><small>${esc(item.wilayah)}</small></td>
|
||
<td>${rp(item.pendapatan_bulanan)}<br><small>${item.jumlah_tanggungan} tanggungan</small></td>
|
||
<td>${Number(item.skor_saw || 0).toFixed(3)}<br><span class="badge ${badgeClass(item.kategori)}">${esc(item.kategori)}</span></td>
|
||
<td><button class="btn secondary small" onclick="editKeluarga(${item.id_keluarga})">Edit</button> <button class="btn danger small" onclick="hapus('keluarga', ${item.id_keluarga})">Hapus</button></td>
|
||
</tr>
|
||
`).join("") || `<tr><td colspan="5">Belum ada data keluarga.</td></tr>`;
|
||
refreshSelects();
|
||
renderMap();
|
||
}
|
||
|
||
function refreshSelects() {
|
||
el("riwayat_keluarga").innerHTML = state.keluarga.map((item) => `<option value="${item.id_keluarga}">${esc(item.nama_kepala_keluarga)} - ${esc(item.kategori || "")}</option>`).join("");
|
||
el("riwayat_bantuan").innerHTML = state.bantuan.map((item) => `<option value="${item.id_bantuan}">${esc(item.jenis_bantuan)} (${rp(item.nilai_bantuan)})</option>`).join("");
|
||
// Populate pengajuan keluarga select (with leading empty option)
|
||
const pengajuanSel = el("pengajuanKeluarga");
|
||
if (pengajuanSel) {
|
||
pengajuanSel.innerHTML = `<option value="">— Tidak dihubungkan —</option>` +
|
||
state.keluarga.map((item) => `<option value="${item.id_keluarga}">#${item.ranking||'-'} ${esc(item.nama_kepala_keluarga)} (${esc(item.wilayah)})</option>`).join("");
|
||
}
|
||
}
|
||
|
||
function renderBantuan() {
|
||
el("bantuanBody").innerHTML = state.bantuan.map((item) => `
|
||
<tr><td><strong>${esc(item.jenis_bantuan)}</strong><br><small>${esc(item.keterangan || "-")}</small></td><td>${fmt(item.tanggal_bantuan)}</td><td>${rp(item.nilai_bantuan)}</td><td><button class="btn secondary small" onclick="editBantuan(${item.id_bantuan})">Edit</button> <button class="btn danger small" onclick="hapus('bantuan', ${item.id_bantuan})">Hapus</button></td></tr>
|
||
`).join("") || `<tr><td colspan="4">Belum ada data bantuan.</td></tr>`;
|
||
el("riwayatBody").innerHTML = state.riwayat.map((item) => `
|
||
<tr><td>${esc(item.nama_kepala_keluarga)}</td><td>${esc(item.jenis_bantuan)}</td><td>${fmt(item.tanggal_diterima)}</td><td>${rp(item.jumlah_bantuan)}</td><td><button class="btn secondary small" onclick="editRiwayat(${item.id_riwayat})">Edit</button> <button class="btn danger small" onclick="hapus('riwayat-bantuan', ${item.id_riwayat})">Hapus</button></td></tr>
|
||
`).join("") || `<tr><td colspan="5">Belum ada riwayat bantuan.</td></tr>`;
|
||
refreshSelects();
|
||
}
|
||
|
||
function renderKriteria() {
|
||
el("kriteriaBody").innerHTML = state.kriteria.map((item) => `
|
||
<tr><td><strong>${esc(item.nama_kriteria)}</strong><br><small>${esc(item.kode)}</small></td><td>${Number(item.bobot).toFixed(2)}</td><td>${esc(item.atribut)}</td><td>${item.aktif ? "Aktif" : "Nonaktif"}</td><td><button class="btn secondary small" onclick="editKriteria(${item.id_kriteria})">Edit</button> <button class="btn danger small" onclick="hapus('kriteria', ${item.id_kriteria})">Nonaktif</button></td></tr>
|
||
`).join("") || `<tr><td colspan="5">Belum ada data kriteria.</td></tr>`;
|
||
}
|
||
|
||
function renderIbadah() {
|
||
el("ibadahCount").textContent = `${state.ibadah.length} data`;
|
||
el("ibadahBody").innerHTML = state.ibadah.map((item, index) => `
|
||
<tr>
|
||
<td>${index + 1}</td>
|
||
<td><strong>${esc(item.nama)}</strong></td>
|
||
<td><span class="badge ${ibadahBadgeClass(item.jenis)}">${esc(item.jenis)}</span></td>
|
||
<td>${esc(item.alamat)}</td>
|
||
<td>${esc(item.kelurahan)}</td>
|
||
<td>${Number(item.lat).toFixed(6)}, ${Number(item.lng).toFixed(6)}</td>
|
||
<td>${item.kapasitas_bantuan || 0} KK</td>
|
||
<td><button class="btn secondary small" onclick="editIbadah(${item.id})">Edit</button> <button class="btn danger small" onclick="hapus('ibadah', ${item.id})">Hapus</button></td>
|
||
</tr>
|
||
`).join("") || `<tr><td colspan="8">Belum ada data tempat ibadah.</td></tr>`;
|
||
}
|
||
|
||
function pengajuanBadgeClass(status) {
|
||
return `status-${status || 'menunggu'}`;
|
||
}
|
||
|
||
function renderPengajuan() {
|
||
const rows = state.pengajuan;
|
||
el("statPengajuanTotal").textContent = rows.length;
|
||
el("statPengajuanMenunggu").textContent = rows.filter(r => r.status === "menunggu").length;
|
||
el("statPengajuanDisetujui").textContent = rows.filter(r => r.status === "disetujui").length;
|
||
|
||
el("pengajuanBody").innerHTML = rows.map((item, idx) => `
|
||
<tr>
|
||
<td>${idx + 1}</td>
|
||
<td><strong>${esc(item.nama_pemohon)}</strong></td>
|
||
<td>${esc(item.nik)}</td>
|
||
<td>${esc(item.wilayah)}</td>
|
||
<td style="max-width:200px;white-space:normal;font-size:.82rem">${esc(item.alasan)}</td>
|
||
<td>${fmt(item.tanggal_pengajuan)}</td>
|
||
<td><span class="badge ${pengajuanBadgeClass(item.status)}">${esc(item.status)}</span></td>
|
||
<td>
|
||
<button class="btn secondary small" onclick="openPengajuanEdit(${item.id_pengajuan})">Review</button>
|
||
<button class="btn danger small" onclick="hapusPengajuan(${item.id_pengajuan})">Hapus</button>
|
||
</td>
|
||
</tr>
|
||
`).join("") || `<tr><td colspan="8">Belum ada pengajuan.</td></tr>`;
|
||
}
|
||
|
||
let ibadahMap, ibadahMarker, ibadahManual = false;
|
||
const ibadahIcon = L.divIcon({
|
||
className: "",
|
||
html: `<div class="ibadah-pin">⛪</div>`,
|
||
iconSize: [32, 32],
|
||
iconAnchor: [16, 32],
|
||
});
|
||
|
||
function ensureIbadahMap() {
|
||
if (mapsInitialized.ibadah) return;
|
||
mapsInitialized.ibadah = true;
|
||
ibadahMap = L.map("ibadahMap").setView(DEFAULT_CENTER, 12);
|
||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap" }).addTo(ibadahMap);
|
||
ibadahMap.on("click", (event) => setIbadahPin(event.latlng.lat, event.latlng.lng));
|
||
setTimeout(() => ibadahMap.invalidateSize(), 80);
|
||
}
|
||
|
||
function setIbadahFields(lat, lng) {
|
||
el("ibadah_lat").value = Number(lat).toFixed(7);
|
||
el("ibadah_lng").value = Number(lng).toFixed(7);
|
||
}
|
||
|
||
function setIbadahPin(lat, lng, zoom = false) {
|
||
const point = [Number(lat), Number(lng)];
|
||
if (!Number.isFinite(point[0]) || !Number.isFinite(point[1])) return;
|
||
ensureIbadahMap();
|
||
setIbadahFields(point[0], point[1]);
|
||
if (!ibadahMarker) {
|
||
ibadahMarker = L.marker(point, { draggable: true, icon: ibadahIcon }).addTo(ibadahMap);
|
||
ibadahMarker.on("dragend", () => {
|
||
const latlng = ibadahMarker.getLatLng();
|
||
setIbadahFields(latlng.lat, latlng.lng);
|
||
});
|
||
} else {
|
||
ibadahMarker.setLatLng(point);
|
||
}
|
||
if (zoom) ibadahMap.setView(point, 15);
|
||
}
|
||
|
||
function clearIbadahPin() {
|
||
if (ibadahMarker) {
|
||
ibadahMarker.remove();
|
||
ibadahMarker = null;
|
||
}
|
||
el("ibadah_lat").value = "";
|
||
el("ibadah_lng").value = "";
|
||
}
|
||
|
||
function syncIbadahManualPin() {
|
||
const lat = Number(el("ibadah_lat").value);
|
||
const lng = Number(el("ibadah_lng").value);
|
||
if (!Number.isFinite(lat) || !Number.isFinite(lng)) return;
|
||
setIbadahPin(lat, lng, true);
|
||
}
|
||
|
||
function ibadahPayload() {
|
||
return {
|
||
nama: el("ibadah_nama").value.trim(),
|
||
jenis: el("ibadah_jenis").value,
|
||
alamat: el("ibadah_alamat").value.trim(),
|
||
kelurahan: el("ibadah_kelurahan").value.trim(),
|
||
lat: Number(el("ibadah_lat").value),
|
||
lng: Number(el("ibadah_lng").value),
|
||
kapasitas_bantuan: Number(el("ibadah_kapasitas").value) || 0,
|
||
keterangan: el("ibadah_keterangan").value.trim() || null,
|
||
};
|
||
}
|
||
|
||
let map, layer;
|
||
function ensureMap() {
|
||
if (map) return;
|
||
map = L.map("adminMap").setView([-0.0263, 109.3425], 12);
|
||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap" }).addTo(map);
|
||
layer = L.layerGroup().addTo(map);
|
||
map.on("click", (event) => {
|
||
el("latitude").value = event.latlng.lat.toFixed(7);
|
||
el("longitude").value = event.latlng.lng.toFixed(7);
|
||
toast("Koordinat keluarga diisi dari peta.");
|
||
});
|
||
}
|
||
function color(kategori) {
|
||
return kategori === "Sangat Membutuhkan" ? "#dc2626" : kategori === "Membutuhkan" ? "#f97316" : kategori === "Rentan" ? "#eab308" : "#22c55e";
|
||
}
|
||
function renderMap() {
|
||
ensureMap();
|
||
layer.clearLayers();
|
||
state.keluarga.forEach((item) => {
|
||
L.circleMarker([item.latitude, item.longitude], { radius: 8, color: color(item.kategori), fillColor: color(item.kategori), fillOpacity: .85 })
|
||
.bindPopup(`<strong>${esc(item.nama_kepala_keluarga)}</strong><br>${esc(item.alamat)}<br>Skor SAW: ${Number(item.skor_saw || 0).toFixed(3)}<br>${esc(item.kategori)}`)
|
||
.addTo(layer);
|
||
});
|
||
}
|
||
|
||
async function loadAll() {
|
||
const [dashboard, analytics, keluarga, bantuan, riwayat, kriteria, ibadah, pengajuan] = await Promise.all([
|
||
api("/dashboard"), api("/analytics"), api("/keluarga"), api("/bantuan"),
|
||
api("/riwayat-bantuan"), api("/kriteria"), api("/ibadah"), api("/pengajuan")
|
||
]);
|
||
Object.assign(state, { dashboard, analytics, keluarga, bantuan, riwayat, kriteria, ibadah, pengajuan });
|
||
renderDashboard(); renderKeluarga(); renderBantuan(); renderKriteria(); renderIbadah(); renderPengajuan();
|
||
}
|
||
|
||
function familyPayload() {
|
||
return {
|
||
nomor_kartu_keluarga: el("nomor_kartu_keluarga").value.trim(),
|
||
nama_kepala_keluarga: el("nama_kepala_keluarga").value.trim(),
|
||
alamat: el("alamat").value.trim(),
|
||
wilayah: el("wilayah").value.trim(),
|
||
latitude: Number(el("latitude").value),
|
||
longitude: Number(el("longitude").value),
|
||
jumlah_anggota: Number(el("jumlah_anggota").value),
|
||
jumlah_tanggungan: Number(el("jumlah_tanggungan").value),
|
||
pendapatan_bulanan: Number(el("pendapatan_bulanan").value),
|
||
pekerjaan: el("pekerjaan").value,
|
||
pendidikan: el("pendidikan").value,
|
||
kondisi_rumah: el("kondisi_rumah").value,
|
||
kondisi_kesehatan: el("kondisi_kesehatan").value,
|
||
status_verifikasi: el("status_verifikasi").value,
|
||
tanggal_update: el("tanggal_update").value
|
||
};
|
||
}
|
||
|
||
function resetKeluarga() {
|
||
el("keluargaForm").reset();
|
||
el("id_keluarga").value = "";
|
||
el("tanggal_update").value = today;
|
||
el("jumlah_anggota").value = 1;
|
||
el("jumlah_tanggungan").value = 0;
|
||
}
|
||
function resetBantuan() { el("bantuanForm").reset(); el("id_bantuan").value = ""; el("tanggal_bantuan").value = today; }
|
||
function resetRiwayat() { el("riwayatForm").reset(); el("id_riwayat").value = ""; el("tanggal_diterima").value = today; }
|
||
function resetKriteria() { el("kriteriaForm").reset(); el("id_kriteria").value = ""; }
|
||
function resetIbadah() {
|
||
el("ibadahForm").reset();
|
||
el("id_ibadah").value = "";
|
||
el("ibadah_kapasitas").value = 0;
|
||
el("ibadah_keterangan").value = "";
|
||
el("ibadah_lat").readOnly = true;
|
||
el("ibadah_lng").readOnly = true;
|
||
ibadahManual = false;
|
||
el("toggleManualIbadah").textContent = "✏️ Edit Manual";
|
||
el("ibadahSubmit").textContent = "Simpan Tempat Ibadah";
|
||
el("cancelIbadahEdit").style.display = "none";
|
||
clearIbadahPin();
|
||
if (ibadahMap) ibadahMap.setView(DEFAULT_CENTER, 12);
|
||
}
|
||
|
||
window.editKeluarga = (id) => {
|
||
const item = state.keluarga.find((row) => row.id_keluarga === id);
|
||
if (!item) return;
|
||
Object.keys(familyPayload()).forEach((key) => { if (el(key)) el(key).value = key === "tanggal_update" ? fmt(item[key]) : (item[key] ?? ""); });
|
||
el("id_keluarga").value = id;
|
||
document.querySelector('[data-section="keluarga"]').click();
|
||
};
|
||
window.editBantuan = (id) => {
|
||
const item = state.bantuan.find((row) => row.id_bantuan === id);
|
||
if (!item) return;
|
||
el("id_bantuan").value = id; el("jenis_bantuan").value = item.jenis_bantuan; el("tanggal_bantuan").value = fmt(item.tanggal_bantuan); el("nilai_bantuan").value = item.nilai_bantuan; el("keterangan_bantuan").value = item.keterangan || "";
|
||
};
|
||
window.editRiwayat = (id) => {
|
||
const item = state.riwayat.find((row) => row.id_riwayat === id);
|
||
if (!item) return;
|
||
el("id_riwayat").value = id; el("riwayat_keluarga").value = item.id_keluarga; el("riwayat_bantuan").value = item.id_bantuan; el("tanggal_diterima").value = fmt(item.tanggal_diterima); el("jumlah_bantuan").value = item.jumlah_bantuan; el("keterangan_riwayat").value = item.keterangan || "";
|
||
};
|
||
window.editKriteria = (id) => {
|
||
const item = state.kriteria.find((row) => row.id_kriteria === id);
|
||
if (!item) return;
|
||
el("id_kriteria").value = id; el("kode").value = item.kode; el("nama_kriteria").value = item.nama_kriteria; el("bobot").value = item.bobot; el("atribut").value = item.atribut;
|
||
};
|
||
window.editIbadah = (id) => {
|
||
const item = state.ibadah.find((row) => row.id === id);
|
||
if (!item) return;
|
||
document.querySelector('[data-section="ibadah"]').click();
|
||
el("id_ibadah").value = id;
|
||
el("ibadah_nama").value = item.nama;
|
||
el("ibadah_jenis").value = item.jenis;
|
||
el("ibadah_alamat").value = item.alamat;
|
||
el("ibadah_kelurahan").value = item.kelurahan;
|
||
el("ibadah_kapasitas").value = item.kapasitas_bantuan || 0;
|
||
el("ibadah_keterangan").value = item.keterangan || "";
|
||
el("ibadahSubmit").textContent = "Update Tempat Ibadah";
|
||
el("cancelIbadahEdit").style.display = "inline-flex";
|
||
setTimeout(() => {
|
||
setIbadahPin(item.lat, item.lng, true);
|
||
el("ibadah").scrollIntoView({ behavior: "smooth", block: "start" });
|
||
}, 100);
|
||
};
|
||
|
||
window.openPengajuanEdit = (id) => {
|
||
const item = state.pengajuan.find(r => r.id_pengajuan === id);
|
||
if (!item) return;
|
||
document.querySelector('[data-section="pengajuan"]').click();
|
||
el("pengajuanEditId").value = id;
|
||
el("pengajuanNewStatus").value = item.status;
|
||
el("pengajuanCatatan").value = item.catatan_admin || "";
|
||
el("pengajuanKeluarga").value = item.id_keluarga || "";
|
||
el("pengajuanDetailInfo").textContent =
|
||
`Pemohon: ${item.nama_pemohon} | NIK: ${item.nik} | Wilayah: ${item.wilayah} | Tgl: ${String(item.tanggal_pengajuan||'').slice(0,10)}`;
|
||
el("pengajuanDetailCard").style.display = "block";
|
||
el("pengajuanDetailCard").scrollIntoView({ behavior: "smooth" });
|
||
};
|
||
|
||
window.hapusPengajuan = async (id) => {
|
||
if (!confirm("Hapus pengajuan ini?")) return;
|
||
await api(`/pengajuan/${id}`, { method: "DELETE" });
|
||
toast("Pengajuan berhasil dihapus.");
|
||
await loadAll();
|
||
};
|
||
window.hapus = async (resource, id) => {
|
||
if (!confirm("Hapus/nonaktifkan data ini?")) return;
|
||
await api(`/${resource}/${id}`, { method: "DELETE" });
|
||
toast("Data berhasil diperbarui.");
|
||
await loadAll();
|
||
};
|
||
|
||
document.querySelectorAll(".nav button").forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
document.querySelectorAll(".nav button").forEach((item) => item.classList.remove("active"));
|
||
document.querySelectorAll(".section").forEach((item) => item.classList.remove("active"));
|
||
button.classList.add("active");
|
||
el(button.dataset.section).classList.add("active");
|
||
setTimeout(() => {
|
||
map?.invalidateSize();
|
||
if (button.dataset.section === "ibadah") {
|
||
ensureIbadahMap();
|
||
ibadahMap?.invalidateSize();
|
||
}
|
||
}, 80);
|
||
});
|
||
});
|
||
|
||
document.getElementById("keluargaForm").addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
const id = el("id_keluarga").value;
|
||
await api(id ? `/keluarga/${id}` : "/keluarga", { method: id ? "PUT" : "POST", body: JSON.stringify(familyPayload()) });
|
||
resetKeluarga(); toast("Data keluarga tersimpan."); await loadAll();
|
||
});
|
||
document.getElementById("bantuanForm").addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
const id = el("id_bantuan").value;
|
||
const payload = { jenis_bantuan: el("jenis_bantuan").value.trim(), tanggal_bantuan: el("tanggal_bantuan").value, nilai_bantuan: Number(el("nilai_bantuan").value), keterangan: el("keterangan_bantuan").value.trim() };
|
||
await api(id ? `/bantuan/${id}` : "/bantuan", { method: id ? "PUT" : "POST", body: JSON.stringify(payload) });
|
||
resetBantuan(); toast("Data bantuan tersimpan."); await loadAll();
|
||
});
|
||
document.getElementById("riwayatForm").addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
const id = el("id_riwayat").value;
|
||
const payload = { id_keluarga: el("riwayat_keluarga").value, id_bantuan: el("riwayat_bantuan").value, tanggal_diterima: el("tanggal_diterima").value, jumlah_bantuan: Number(el("jumlah_bantuan").value), keterangan: el("keterangan_riwayat").value.trim() };
|
||
await api(id ? `/riwayat-bantuan/${id}` : "/riwayat-bantuan", { method: id ? "PUT" : "POST", body: JSON.stringify(payload) });
|
||
resetRiwayat(); toast("Riwayat bantuan tersimpan."); await loadAll();
|
||
});
|
||
document.getElementById("kriteriaForm").addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
const id = el("id_kriteria").value;
|
||
const payload = { kode: el("kode").value.trim(), nama_kriteria: el("nama_kriteria").value.trim(), bobot: Number(el("bobot").value), atribut: el("atribut").value, aktif: true };
|
||
await api(id ? `/kriteria/${id}` : "/kriteria", { method: id ? "PUT" : "POST", body: JSON.stringify(payload) });
|
||
resetKriteria(); toast("Kriteria tersimpan."); await loadAll();
|
||
});
|
||
document.getElementById("ibadahForm").addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
const id = el("id_ibadah").value;
|
||
await api(id ? `/ibadah/${id}` : "/ibadah", { method: id ? "PUT" : "POST", body: JSON.stringify(ibadahPayload()) });
|
||
resetIbadah(); toast("Tempat ibadah tersimpan."); await loadAll();
|
||
});
|
||
|
||
el("resetKeluarga").addEventListener("click", resetKeluarga);
|
||
el("resetBantuan").addEventListener("click", resetBantuan);
|
||
el("resetRiwayat").addEventListener("click", resetRiwayat);
|
||
el("resetKriteria").addEventListener("click", resetKriteria);
|
||
el("resetIbadah").addEventListener("click", resetIbadah);
|
||
el("cancelIbadahEdit").addEventListener("click", resetIbadah);
|
||
el("clearIbadahPin").addEventListener("click", clearIbadahPin);
|
||
el("toggleManualIbadah").addEventListener("click", () => {
|
||
ibadahManual = !ibadahManual;
|
||
el("ibadah_lat").readOnly = !ibadahManual;
|
||
el("ibadah_lng").readOnly = !ibadahManual;
|
||
el("toggleManualIbadah").textContent = ibadahManual ? "🔒 Kunci Koordinat" : "✏️ Edit Manual";
|
||
});
|
||
el("ibadah_lat").addEventListener("blur", syncIbadahManualPin);
|
||
el("ibadah_lng").addEventListener("blur", syncIbadahManualPin);
|
||
el("familySearch").addEventListener("input", renderKeluarga);
|
||
el("recalculateBtn").addEventListener("click", async () => { await api("/recalculate", { method: "POST" }); toast("SAW berhasil dihitung ulang."); await loadAll(); });
|
||
|
||
el("filterPengajuanStatus").addEventListener("change", async () => {
|
||
const status = el("filterPengajuanStatus").value;
|
||
state.pengajuan = await api(`/pengajuan${status ? '?status='+status : ''}`);
|
||
renderPengajuan();
|
||
});
|
||
|
||
el("savePengajuanStatus").addEventListener("click", async () => {
|
||
const id = el("pengajuanEditId").value;
|
||
if (!id) return;
|
||
await api(`/pengajuan/${id}/status`, {
|
||
method: "PUT",
|
||
body: JSON.stringify({
|
||
status: el("pengajuanNewStatus").value,
|
||
catatan_admin: el("pengajuanCatatan").value.trim(),
|
||
id_keluarga: el("pengajuanKeluarga").value || null,
|
||
}),
|
||
});
|
||
toast("Status pengajuan berhasil diperbarui.");
|
||
el("pengajuanDetailCard").style.display = "none";
|
||
await loadAll();
|
||
});
|
||
|
||
fillOptions("pekerjaan", labels.pekerjaan);
|
||
fillOptions("pendidikan", labels.pendidikan);
|
||
fillOptions("kondisi_rumah", labels.kondisi_rumah);
|
||
fillOptions("kondisi_kesehatan", labels.kondisi_kesehatan);
|
||
el("ibadah_jenis").innerHTML = IBADAH_TYPES.map((jenis) => `<option value="${jenis}">${jenis}</option>`).join("");
|
||
resetKeluarga(); resetBantuan(); resetRiwayat(); resetKriteria(); resetIbadah();
|
||
loadAll().catch((error) => toast(error.message));
|
||
</script>
|
||
</body>
|
||
</html>
|