pertama dan terakhir

This commit is contained in:
Tazyeuu
2026-06-10 22:07:47 +07:00
commit cd376094df
143 changed files with 12090 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT id,nama,jenis_jalan,created_at FROM jalan ORDER BY id DESC")->fetchAll();
$pageTitle='Data Jalan'; $activeNav='jalan';
require_once __DIR__ . '/partials/header.php';
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Data Jalan</span></div>
<h1>🛣️ Manajemen Data Jalan</h1>
<p>Kelola data ruas jalan yang terpetakan dalam sistem.</p>
</div>
<div class="page-toolbar">
<div class="search-box"><i class="fas fa-search search-icon"></i><input type="text" id="searchInput" placeholder="Cari jalan..." oninput="filterTable('searchInput','jalanTable')"></div>
<button class="btn btn-primary" onclick="openModal('modalTambah')"><i class="fas fa-plus"></i> Tambah Jalan</button>
</div>
<div class="table-wrap">
<table class="data-table" id="jalanTable">
<thead><tr><th>#</th><th>Nama Jalan</th><th>Jenis</th><th>Ditambahkan</th><th>Aksi</th></tr></thead>
<tbody>
<?php foreach($rows as $i=>$r): ?>
<tr>
<td style="color:var(--text-muted)"><?=$i+1?></td>
<td><strong><?=htmlspecialchars($r['nama'])?></strong></td>
<td><span class="badge badge-info"><?=htmlspecialchars($r['jenis_jalan']??'-')?></span></td>
<td style="color:var(--text-muted);font-size:0.8rem;"><?=date('d M Y',strtotime($r['created_at']))?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick="editRow(<?=htmlspecialchars(json_encode($r))?>)"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?=$r['id']?>,'<?=addslashes($r['nama'])?>')"><i class="fas fa-trash"></i></button>
</div></td>
</tr>
<?php endforeach; ?>
<?php if(!$rows):?><tr><td colspan="5" style="text-align:center;padding:40px;color:var(--text-muted);">Belum ada data jalan.</td></tr><?php endif;?>
</tbody>
</table></div>
<div class="modal-overlay" id="modalTambah"><div class="modal">
<div class="modal-header"><span class="modal-title">🛣️ Tambah Jalan</span><button class="modal-close" onclick="closeModal('modalTambah')">×</button></div>
<form id="formTambah">
<div class="form-group"><label class="form-label">Nama Jalan *</label><input type="text" name="nama" class="form-control" placeholder="Nama ruas jalan" required></div>
<div class="form-group"><label class="form-label">Jenis Jalan *</label>
<select name="jenis_jalan" class="form-control">
<option value="Jalan Arteri">Jalan Arteri</option>
<option value="Jalan Kolektor">Jalan Kolektor</option>
<option value="Jalan Lokal" selected>Jalan Lokal</option>
<option value="Jalan Lingkungan">Jalan Lingkungan</option>
<option value="Jalan Tol">Jalan Tol</option>
</select>
</div>
<p style="font-size:0.82rem;color:var(--text-muted);background:rgba(59,130,246,0.08);border:1px solid rgba(59,130,246,0.2);border-radius:8px;padding:10px;"><i class="fas fa-info-circle"></i> Geometri jalan (LineString) hanya bisa ditambahkan melalui <a href="<?= app_url('admin/peta.php') ?>" style="color:var(--primary-light);">Peta Interaktif</a>.</p>
</form>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalTambah')">Batal</button><button class="btn btn-primary" onclick="simpan()"><i class="fas fa-save"></i> Simpan (Tanpa Geometri)</button></div>
</div></div>
<div class="modal-overlay" id="modalEdit"><div class="modal">
<div class="modal-header"><span class="modal-title">✏️ Edit Jalan</span><button class="modal-close" onclick="closeModal('modalEdit')">×</button></div>
<input type="hidden" id="editId">
<div class="form-group"><label class="form-label">Nama Jalan *</label><input type="text" id="editNama" class="form-control" required></div>
<div class="form-group"><label class="form-label">Jenis Jalan</label>
<select id="editJenis" class="form-control">
<option value="Jalan Arteri">Jalan Arteri</option><option value="Jalan Kolektor">Jalan Kolektor</option>
<option value="Jalan Lokal">Jalan Lokal</option><option value="Jalan Lingkungan">Jalan Lingkungan</option><option value="Jalan Tol">Jalan Tol</option>
</select>
</div>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button><button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button></div>
</div></div>
<?php $extraScript = <<<'JS'
<script>
const API=APP_BASE + '/api/jalan.php';
function editRow(r){ document.getElementById('editId').value=r.id; document.getElementById('editNama').value=r.nama; document.getElementById('editJenis').value=r.jenis_jalan||'Jalan Lokal'; openModal('modalEdit'); }
async function simpan(){ const f=document.getElementById('formTambah'); const res=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama:f.nama.value,jenis_jalan:f.jenis_jalan.value,geometry:{type:'LineString',coordinates:[]}})}); const d=await res.json(); if(d.status==='success'){showToast('Jalan disimpan!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function update(){ const id=document.getElementById('editId').value; const res=await fetch(API+'?id='+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama:document.getElementById('editNama').value,jenis_jalan:document.getElementById('editJenis').value})}); const d=await res.json(); if(d.status==='success'){showToast('Jalan diperbarui!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function hapus(id,nama){ if(!confirm(`Hapus jalan "${nama}"?`))return; const res=await fetch(API+'?id='+id,{method:'DELETE'}); const d=await res.json(); if(d.status==='success'){showToast('Jalan dihapus!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+82
View File
@@ -0,0 +1,82 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT id,nama_pemilik,status_kepemilikan,luas,created_at FROM kavling ORDER BY id DESC")->fetchAll();
$pageTitle='Data Kavling'; $activeNav='kavling';
require_once __DIR__ . '/partials/header.php';
$statusColors = ['SHM'=>'badge-success','HGB'=>'badge-info','HGU'=>'badge-warning','HP'=>'badge-primary'];
$statusFull = ['SHM'=>'Sertifikat Hak Milik','HGB'=>'Hak Guna Bangunan','HGU'=>'Hak Guna Usaha','HP'=>'Hak Pakai'];
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Kavling</span></div>
<h1>🏘️ Manajemen Kavling / Parsil Tanah</h1>
<p>Kelola data bidang tanah berdasarkan status kepemilikan sertifikat.</p>
</div>
<div class="page-toolbar">
<div class="search-box"><i class="fas fa-search search-icon"></i><input type="text" id="searchInput" placeholder="Cari kavling..." oninput="filterTable('searchInput','kavTable')"></div>
<button class="btn btn-primary" onclick="openModal('modalTambah')"><i class="fas fa-plus"></i> Tambah Kavling</button>
</div>
<div class="table-wrap">
<table class="data-table" id="kavTable">
<thead><tr><th>#</th><th>Nama Pemilik</th><th>Status Kepemilikan</th><th>Luas (m²)</th><th>Ditambahkan</th><th>Aksi</th></tr></thead>
<tbody>
<?php foreach($rows as $i=>$r): $sk=$r['status_kepemilikan']; ?>
<tr>
<td style="color:var(--text-muted)"><?=$i+1?></td>
<td><strong><?=htmlspecialchars($r['nama_pemilik'])?></strong></td>
<td><span class="badge <?=$statusColors[$sk]??'badge-primary'?>" title="<?=$statusFull[$sk]??$sk?>"><?=$sk?></span></td>
<td><?=$r['luas'] ? number_format($r['luas'],0,',','.') : '-'?></td>
<td style="color:var(--text-muted);font-size:0.8rem;"><?=date('d M Y',strtotime($r['created_at']))?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick="editRow(<?=htmlspecialchars(json_encode($r))?>)"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?=$r['id']?>,'<?=addslashes($r['nama_pemilik'])?>')"><i class="fas fa-trash"></i></button>
</div></td>
</tr>
<?php endforeach; ?>
<?php if(!$rows):?><tr><td colspan="6" style="text-align:center;padding:40px;color:var(--text-muted);">Belum ada data kavling.</td></tr><?php endif;?>
</tbody>
</table></div>
<div class="modal-overlay" id="modalTambah"><div class="modal">
<div class="modal-header"><span class="modal-title">🏘️ Tambah Kavling</span><button class="modal-close" onclick="closeModal('modalTambah')">×</button></div>
<form id="formTambah">
<div class="form-group"><label class="form-label">Nama Pemilik *</label><input type="text" name="nama_pemilik" class="form-control" placeholder="Nama lengkap pemilik" required></div>
<div class="form-group"><label class="form-label">Status Kepemilikan *</label>
<select name="status_kepemilikan" class="form-control">
<option value="SHM">Sertifikat Hak Milik (SHM)</option>
<option value="HGB">Sertifikat Hak Guna Bangunan (HGB)</option>
<option value="HGU">Sertifikat Hak Guna Usaha (HGU)</option>
<option value="HP">Sertifikat Hak Pakai (HP)</option>
</select>
</div>
<div class="form-group"><label class="form-label">Luas (m²)</label><input type="number" name="luas" class="form-control" placeholder="Luas bidang tanah" min="0" step="0.01"></div>
<p style="font-size:0.82rem;color:var(--text-muted);background:rgba(59,130,246,0.08);border:1px solid rgba(59,130,246,0.2);border-radius:8px;padding:10px;"><i class="fas fa-info-circle"></i> Polygon kavling bisa digambar di <a href="<?= app_url('admin/peta.php') ?>" style="color:var(--primary-light);">Peta Interaktif</a>.</p>
</form>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalTambah')">Batal</button><button class="btn btn-primary" onclick="simpan()"><i class="fas fa-save"></i> Simpan</button></div>
</div></div>
<div class="modal-overlay" id="modalEdit"><div class="modal">
<div class="modal-header"><span class="modal-title">✏️ Edit Kavling</span><button class="modal-close" onclick="closeModal('modalEdit')">×</button></div>
<input type="hidden" id="editId">
<div class="form-group"><label class="form-label">Nama Pemilik *</label><input type="text" id="editNama" class="form-control" required></div>
<div class="form-group"><label class="form-label">Status Kepemilikan</label>
<select id="editStatus" class="form-control">
<option value="SHM">SHM</option><option value="HGB">HGB</option><option value="HGU">HGU</option><option value="HP">HP</option>
</select>
</div>
<div class="form-group"><label class="form-label">Luas (m²)</label><input type="number" id="editLuas" class="form-control" min="0" step="0.01"></div>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button><button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button></div>
</div></div>
<?php $extraScript = <<<'JS'
<script>
const API=APP_BASE + '/api/kavling.php';
function editRow(r){ document.getElementById('editId').value=r.id; document.getElementById('editNama').value=r.nama_pemilik; document.getElementById('editStatus').value=r.status_kepemilikan; document.getElementById('editLuas').value=r.luas||0; openModal('modalEdit'); }
async function simpan(){ const f=document.getElementById('formTambah'); if(!f.nama_pemilik.value){showToast('Nama pemilik wajib diisi!','error');return;} const res=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama_pemilik:f.nama_pemilik.value,status_kepemilikan:f.status_kepemilikan.value,luas:parseFloat(f.luas.value)||0,geometry:{type:'Polygon',coordinates:[[]]}})}); const d=await res.json(); if(d.status==='success'){showToast('Kavling disimpan!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function update(){ const id=document.getElementById('editId').value; const res=await fetch(API+'?id='+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama_pemilik:document.getElementById('editNama').value,status_kepemilikan:document.getElementById('editStatus').value,luas:parseFloat(document.getElementById('editLuas').value)||0})}); const d=await res.json(); if(d.status==='success'){showToast('Kavling diperbarui!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function hapus(id,nama){ if(!confirm(`Hapus kavling milik "${nama}"?`))return; const res=await fetch(API+'?id='+id,{method:'DELETE'}); const d=await res.json(); if(d.status==='success'){showToast('Kavling dihapus!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+57
View File
@@ -0,0 +1,57 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT k.id, k.nama_kawasan, k.created_at, COUNT(w.id) as jumlah_warga
FROM kawasan_kumuh k LEFT JOIN warga_miskin w ON ST_Contains(k.geom, w.geom)
GROUP BY k.id ORDER BY k.id DESC")->fetchAll();
$pageTitle='Kawasan Kumuh'; $activeNav='kawasan';
require_once __DIR__ . '/partials/header.php';
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Kawasan Kumuh</span></div>
<h1>⚠️ Manajemen Kawasan Kumuh</h1>
<p>Kelola data kawasan analisis spasial kepadatan warga miskin (choropleth).</p>
</div>
<div class="page-toolbar">
<div class="search-box"><i class="fas fa-search search-icon"></i><input type="text" id="searchInput" placeholder="Cari kawasan..." oninput="filterTable('searchInput','kawasanTable')"></div>
<a href="<?= app_url('admin/peta.php') ?>" class="btn btn-primary"><i class="fas fa-map"></i> Gambar di Peta</a>
</div>
<div class="table-wrap">
<table class="data-table" id="kawasanTable">
<thead><tr><th>#</th><th>Nama Kawasan</th><th>Warga Terdampak</th><th>Status</th><th>Ditambahkan</th><th>Aksi</th></tr></thead>
<tbody>
<?php foreach($rows as $i=>$r): $cnt=(int)$r['jumlah_warga']; $status=$cnt>3?'Rawan Kumuh':($cnt>0?'Perlu Perhatian':'Aman'); $badge=$cnt>3?'badge-danger':($cnt>0?'badge-warning':'badge-success'); ?>
<tr>
<td style="color:var(--text-muted)"><?=$i+1?></td>
<td><strong><?=htmlspecialchars($r['nama_kawasan'])?></strong></td>
<td style="text-align:center;font-size:1.1rem;font-weight:700;"><?=$cnt?></td>
<td><span class="badge <?=$badge?>"><?=$status?></span></td>
<td style="color:var(--text-muted);font-size:0.8rem;"><?=date('d M Y',strtotime($r['created_at']))?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick="editRow(<?=htmlspecialchars(json_encode($r))?>)"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?=$r['id']?>,'<?=addslashes($r['nama_kawasan'])?>')"><i class="fas fa-trash"></i></button>
</div></td>
</tr>
<?php endforeach; ?>
<?php if(!$rows):?><tr><td colspan="6" style="text-align:center;padding:40px;color:var(--text-muted);">Belum ada data kawasan. Gambar polygon di peta untuk menambahkan.</td></tr><?php endif;?>
</tbody>
</table></div>
<div class="modal-overlay" id="modalEdit"><div class="modal">
<div class="modal-header"><span class="modal-title">✏️ Edit Kawasan</span><button class="modal-close" onclick="closeModal('modalEdit')">×</button></div>
<input type="hidden" id="editId">
<div class="form-group"><label class="form-label">Nama Kawasan *</label><input type="text" id="editNama" class="form-control" required></div>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button><button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button></div>
</div></div>
<?php $extraScript = <<<'JS'
<script>
const API=APP_BASE + '/api/kawasan_kumuh.php';
function editRow(r){ document.getElementById('editId').value=r.id; document.getElementById('editNama').value=r.nama_kawasan; openModal('modalEdit'); }
async function update(){ const id=document.getElementById('editId').value; const res=await fetch(API+'?id='+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama_kawasan:document.getElementById('editNama').value})}); const d=await res.json(); if(d.status==='success'){showToast('Kawasan diperbarui!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function hapus(id,nama){ if(!confirm(`Hapus kawasan "${nama}"?`))return; const res=await fetch(API+'?id='+id,{method:'DELETE'}); const d=await res.json(); if(d.status==='success'){showToast('Kawasan dihapus!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+191
View File
@@ -0,0 +1,191 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$sql = "SELECT l.id, l.kategori, l.deskripsi, l.status, l.created_at,
u.nama_lengkap, u.username, ST_X(l.geometry) as lng, ST_Y(l.geometry) as lat
FROM laporan_warga l
JOIN users u ON l.user_id = u.id
ORDER BY l.id DESC";
$rows = $pdo->query($sql)->fetchAll();
$pageTitle = 'Laporan Warga';
$activeNav = 'laporan';
$extraHead = '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">';
require_once __DIR__ . '/partials/header.php';
function badgeStatus($status) {
if($status === 'menunggu') return 'badge-warning';
if($status === 'diproses') return 'badge-info';
if($status === 'selesai') return 'badge-success';
if($status === 'ditolak') return 'badge-danger';
return 'badge-primary';
}
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Laporan Warga</span></div>
<h1>📢 Validasi Laporan Warga</h1>
<p>Kelola dan perbarui status laporan kerusakan infrastruktur dari warga.</p>
</div>
<div class="page-toolbar">
<div class="search-box">
<i class="fas fa-search search-icon"></i>
<input type="text" id="searchInput" placeholder="Cari laporan..." oninput="filterTable('searchInput','lapTable')">
</div>
</div>
<div class="table-wrap">
<table class="data-table" id="lapTable">
<thead>
<tr>
<th>#</th>
<th>Pelapor</th>
<th>Kategori</th>
<th>Deskripsi</th>
<th>Status</th>
<th>Tanggal</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $i => $r): ?>
<tr>
<td style="color:var(--text-muted)"><?= $i+1 ?></td>
<td><strong><?= htmlspecialchars($r['nama_lengkap'] ?: $r['username']) ?></strong></td>
<td><?= htmlspecialchars($r['kategori']) ?></td>
<td style="color:var(--text-secondary); max-width: 250px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<?= htmlspecialchars($r['deskripsi']) ?>
</td>
<td><span class="badge <?= badgeStatus($r['status']) ?>"><?= ucfirst($r['status']) ?></span></td>
<td style="color:var(--text-muted); font-size:0.8rem;"><?= date('d M Y', strtotime($r['created_at'])) ?></td>
<td>
<div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick='lihatLokasi(<?= json_encode($r) ?>)' title="Lihat Lokasi"><i class="fas fa-map-marker-alt" style="color: #3B82F6;"></i></button>
<button class="btn btn-ghost btn-sm btn-icon" onclick='editStatus(<?= json_encode($r) ?>)' title="Ubah Status"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?= $r['id'] ?>)" title="Hapus Laporan"><i class="fas fa-trash"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$rows): ?>
<tr><td colspan="7" style="text-align:center;padding:40px;color:var(--text-muted);">Belum ada laporan dari warga.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Modal Ubah Status -->
<div class="modal-overlay" id="modalStatus">
<div class="modal">
<div class="modal-header">
<span class="modal-title">✏️ Perbarui Status Laporan</span>
<button class="modal-close" onclick="closeModal('modalStatus')">×</button>
</div>
<input type="hidden" id="editId">
<div class="form-group">
<label class="form-label">Ubah Status</label>
<select id="editStatusSelect" class="form-control">
<option value="menunggu">Menunggu</option>
<option value="diproses">Diproses</option>
<option value="selesai">Selesai</option>
<option value="ditolak">Ditolak</option>
</select>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modalStatus')">Batal</button>
<button class="btn btn-primary" onclick="updateStatus()"><i class="fas fa-save"></i> Simpan</button>
</div>
</div>
</div>
<!-- Modal Lihat Peta -->
<div class="modal-overlay" id="modalMap">
<div class="modal">
<div class="modal-header">
<span class="modal-title">📍 Lokasi Laporan</span>
<button class="modal-close" onclick="closeModal('modalMap')">×</button>
</div>
<div style="padding-bottom: 12px; font-size: 0.9rem;">
<strong id="mapKategori"></strong><br>
<span style="color: var(--text-secondary);" id="mapDeskripsi"></span>
</div>
<div id="miniMapLihat" style="height:300px;border-radius:8px;border:1px solid #E5E7EB;"></div>
</div>
</div>
<?php $extraScript = <<<'JS'
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const API = APP_BASE + '/api/laporan.php';
function editStatus(r) {
document.getElementById('editId').value = r.id;
document.getElementById('editStatusSelect').value = r.status;
openModal('modalStatus');
}
async function updateStatus() {
const id = document.getElementById('editId').value;
const status = document.getElementById('editStatusSelect').value;
const res = await fetch(API + '?id=' + id, {
method:'PUT',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ status: status })
});
const d = await res.json();
if(d.status === 'success') {
showToast('Status diperbarui!');
setTimeout(() => location.reload(), 900);
} else {
showToast(d.message,'error');
}
}
async function hapus(id) {
if(!confirm('Hapus laporan secara permanen?')) return;
const res = await fetch(API + '?id=' + id, { method:'DELETE' });
const d = await res.json();
if(d.status === 'success') {
showToast('Laporan dihapus!');
setTimeout(() => location.reload(), 900);
} else {
showToast(d.message,'error');
}
}
let mapInstance = null, mapMarker = null;
const originalOpenModal = window.openModal;
window.openModal = function(id) {
originalOpenModal(id);
if(id === 'modalMap') {
setTimeout(() => {
if(mapInstance) mapInstance.invalidateSize();
}, 100);
}
};
function lihatLokasi(r) {
document.getElementById('mapKategori').innerText = r.kategori;
document.getElementById('mapDeskripsi').innerText = r.deskripsi;
openModal('modalMap');
setTimeout(() => {
if(!mapInstance) {
mapInstance = L.map('miniMapLihat');
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png').addTo(mapInstance);
}
if(mapMarker) mapInstance.removeLayer(mapMarker);
const latLng = [parseFloat(r.lat), parseFloat(r.lng)];
mapMarker = L.marker(latLng).addTo(mapInstance);
mapInstance.setView(latLng, 16);
}, 150);
}
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+140
View File
@@ -0,0 +1,140 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT id,nama,agama,radius_bantuan_meter,created_at FROM rumah_ibadah ORDER BY id DESC")->fetchAll();
$pageTitle='Rumah Ibadah'; $activeNav='rumah';
$extraHead = '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">';
require_once __DIR__ . '/partials/header.php';
$agamaIcon = ['Islam'=>'🕌','Kristen'=>'⛪','Katolik'=>'⛪','Hindu'=>'🛕','Buddha'=>'☸️','Konghucu'=>'🏯'];
$agamaBadge = ['Islam'=>'badge-success','Kristen'=>'badge-info','Katolik'=>'badge-primary','Hindu'=>'badge-warning','Buddha'=>'badge-danger','Konghucu'=>'badge-warning'];
$grouped = [];
foreach($agamaIcon as $ag => $ic) { $grouped[$ag] = []; }
foreach($rows as $r) {
if(!isset($grouped[$r['agama']])) $grouped[$r['agama']] = [];
$grouped[$r['agama']][] = $r;
}
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Rumah Ibadah</span></div>
<h1>🕌 Manajemen Rumah Ibadah</h1>
<p>Kelola data rumah ibadah untuk 5 agama yang diakui di Indonesia.</p>
</div>
<div class="page-toolbar">
<div class="search-box"><i class="fas fa-search search-icon"></i><input type="text" id="searchInput" placeholder="Cari rumah ibadah..." oninput="filterTableMulti()"></div>
<button class="btn btn-primary" onclick="openModal('modalTambah')"><i class="fas fa-plus"></i> Tambah</button>
</div>
<?php foreach($grouped as $agama => $list): ?>
<h3 style="margin-top: 24px; margin-bottom: 12px; font-size: 1.1rem; color: var(--text-primary); border-bottom: 2px solid var(--border-light); padding-bottom: 8px;">
<?= $agamaIcon[$agama] ?? '🏛' ?> <?= $agama ?> <span class="badge badge-info" style="margin-left: 8px;"><?= count($list) ?></span>
</h3>
<div class="table-wrap" style="margin-bottom: 32px;">
<table class="data-table ri-table">
<thead><tr><th>#</th><th>Nama</th><th>Radius Bantuan</th><th>Ditambahkan</th><th>Aksi</th></tr></thead>
<tbody>
<?php foreach($list as $i=>$r): ?>
<tr>
<td style="color:var(--text-muted)"><?=$i+1?></td>
<td><strong><?=htmlspecialchars($r['nama'])?></strong></td>
<td><span class="badge badge-info"><?= number_format((int)$r['radius_bantuan_meter'], 0, ',', '.') ?> m</span></td>
<td style="color:var(--text-muted);font-size:0.8rem;"><?=date('d M Y',strtotime($r['created_at']))?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick="editRow(<?=htmlspecialchars(json_encode($r))?>)"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?=$r['id']?>,'<?=addslashes($r['nama'])?>')"><i class="fas fa-trash"></i></button>
</div></td>
</tr>
<?php endforeach; ?>
<?php if(!$list):?><tr><td colspan="5" style="text-align:center;padding:30px;color:var(--text-muted);">Belum ada data <?=$agama?>.</td></tr><?php endif;?>
</tbody>
</table></div>
<?php endforeach; ?>
<div class="modal-overlay" id="modalTambah"><div class="modal">
<div class="modal-header"><span class="modal-title">🕌 Tambah Rumah Ibadah</span><button class="modal-close" onclick="closeModal('modalTambah')">×</button></div>
<form id="formTambah">
<div class="form-group"><label class="form-label">Nama *</label><input type="text" name="nama" class="form-control" placeholder="Nama rumah ibadah" required></div>
<div class="form-group"><label class="form-label">Agama *</label>
<select name="agama" class="form-control">
<option value="Islam">🕌 Islam</option><option value="Kristen">⛪ Kristen</option>
<option value="Katolik">⛪ Katolik</option><option value="Hindu">🛕 Hindu</option>
<option value="Buddha">☸️ Buddha</option><option value="Konghucu">🏯 Konghucu</option>
</select>
</div>
<div class="form-group"><label class="form-label">Radius Bantuan (meter) *</label><input type="number" name="radius_bantuan_meter" class="form-control" min="100" max="10000" step="50" value="1000" required></div>
<div class="form-group"><label class="form-label">Pilih Lokasi di Peta</label>
<div id="miniMapTambah" style="height:200px;border-radius:8px;border:1px solid #E5E7EB;margin-bottom:10px;z-index:1;"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<input type="number" name="lat" id="latTambah" class="form-control" placeholder="Latitude" step="any" readonly style="background:#F9FAFB;">
<input type="number" name="lng" id="lngTambah" class="form-control" placeholder="Longitude" step="any" readonly style="background:#F9FAFB;">
</div>
</div>
</form>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalTambah')">Batal</button><button class="btn btn-primary" onclick="simpan()"><i class="fas fa-save"></i> Simpan</button></div>
</div></div>
<div class="modal-overlay" id="modalEdit"><div class="modal">
<div class="modal-header"><span class="modal-title">✏️ Edit Rumah Ibadah</span><button class="modal-close" onclick="closeModal('modalEdit')">×</button></div>
<input type="hidden" id="editId">
<div class="form-group"><label class="form-label">Nama *</label><input type="text" id="editNama" class="form-control" required></div>
<div class="form-group"><label class="form-label">Agama</label>
<select id="editAgama" class="form-control">
<option value="Islam">🕌 Islam</option><option value="Kristen">⛪ Kristen</option>
<option value="Katolik">⛪ Katolik</option><option value="Hindu">🛕 Hindu</option>
<option value="Buddha">☸️ Buddha</option><option value="Konghucu">🏯 Konghucu</option>
</select>
</div>
<div class="form-group"><label class="form-label">Radius Bantuan (meter) *</label><input type="number" id="editRadius" class="form-control" min="100" max="10000" step="50" required></div>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button><button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button></div>
</div></div>
<?php $extraScript = <<<'JS'
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const API=APP_BASE + '/api/rumah_ibadah.php';
let miniMap = null, miniMarker = null;
// Override openModal to initialize map
const originalOpenModal = window.openModal;
window.openModal = function(id) {
originalOpenModal(id);
if(id === 'modalTambah') {
setTimeout(() => {
if(!miniMap) {
miniMap = L.map('miniMapTambah').setView([-0.0583, 109.3448], 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png').addTo(miniMap);
miniMap.on('click', function(e) {
const lat = e.latlng.lat;
const lng = e.latlng.lng;
if(!miniMarker) miniMarker = L.marker([lat, lng]).addTo(miniMap);
else miniMarker.setLatLng([lat, lng]);
document.getElementById('latTambah').value = lat.toFixed(6);
document.getElementById('lngTambah').value = lng.toFixed(6);
});
} else {
miniMap.invalidateSize();
}
}, 100);
}
};
function filterTableMulti() {
const v = document.getElementById('searchInput').value.toLowerCase();
document.querySelectorAll('.ri-table').forEach(tbl => {
tbl.querySelectorAll('tbody tr').forEach(tr => {
if(tr.cells.length > 1) tr.style.display = tr.innerText.toLowerCase().includes(v) ? '' : 'none';
});
});
}
function normalizeRadius(v){ const n=parseInt(v,10); if(Number.isNaN(n)) return 1000; return Math.max(100, Math.min(10000, n)); }
function editRow(r){ document.getElementById('editId').value=r.id; document.getElementById('editNama').value=r.nama; document.getElementById('editAgama').value=r.agama; document.getElementById('editRadius').value=normalizeRadius(r.radius_bantuan_meter); openModal('modalEdit'); }
async function simpan(){ const f=document.getElementById('formTambah'); if(!f.nama.value){showToast('Nama wajib!','error');return;}
const lat=parseFloat(f.lat.value)||(-0.03), lng=parseFloat(f.lng.value)||(109.34);
const res=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama:f.nama.value,agama:f.agama.value,radius_bantuan_meter:normalizeRadius(f.radius_bantuan_meter.value),geometry:{type:'Point',coordinates:[lng,lat]}})}); const d=await res.json(); if(d.status==='success'){showToast('Rumah ibadah disimpan!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function update(){ const id=document.getElementById('editId').value; const res=await fetch(API+'?id='+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama:document.getElementById('editNama').value,agama:document.getElementById('editAgama').value,radius_bantuan_meter:normalizeRadius(document.getElementById('editRadius').value)})}); const d=await res.json(); if(d.status==='success'){showToast('Diperbarui!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function hapus(id,nama){ if(!confirm(`Hapus "${nama}"?`))return; const res=await fetch(API+'?id='+id,{method:'DELETE'}); const d=await res.json(); if(d.status==='success'){showToast('Dihapus!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+164
View File
@@ -0,0 +1,164 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT id, nama, deskripsi, buka_24_jam, created_at FROM spbu ORDER BY id DESC")->fetchAll();
$pageTitle = 'Data SPBU'; $activeNav = 'spbu';
$extraHead = '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">';
require_once __DIR__ . '/partials/header.php';
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Data SPBU</span></div>
<h1>⛽ Manajemen Data SPBU</h1>
<p>Kelola data Stasiun Pengisian Bahan Bakar Umum (SPBU) di wilayah pemetaan.</p>
</div>
<div class="page-toolbar">
<div class="search-box">
<i class="fas fa-search search-icon"></i>
<input type="text" id="searchInput" placeholder="Cari SPBU..." oninput="filterTable('searchInput','spbuTable')">
</div>
<button class="btn btn-primary" onclick="openModal('modalTambah')">
<i class="fas fa-plus"></i> Tambah SPBU
</button>
</div>
<div class="table-wrap">
<table class="data-table" id="spbuTable">
<thead><tr>
<th>#</th><th>Nama SPBU</th><th>Deskripsi</th><th>Status</th><th>Ditambahkan</th><th>Aksi</th>
</tr></thead>
<tbody>
<?php foreach ($rows as $i => $r): ?>
<tr>
<td style="color:var(--text-muted)"><?= $i+1 ?></td>
<td><strong><?= htmlspecialchars($r['nama']) ?></strong></td>
<td style="color:var(--text-secondary)"><?= htmlspecialchars($r['deskripsi'] ?: '-') ?></td>
<td><span class="badge <?= $r['buka_24_jam'] ? 'badge-success' : 'badge-warning' ?>"><?= $r['buka_24_jam'] ? '✅ 24 Jam' : '🕐 Terbatas' ?></span></td>
<td style="color:var(--text-muted); font-size:0.8rem;"><?= date('d M Y, H:i', strtotime($r['created_at'])) ?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick="editRow(<?= htmlspecialchars(json_encode($r)) ?>)" title="Edit"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?= $r['id'] ?>,'<?= addslashes($r['nama']) ?>')" title="Hapus"><i class="fas fa-trash"></i></button>
</div></td>
</tr>
<?php endforeach; ?>
<?php if (!$rows): ?><tr><td colspan="6" style="text-align:center;padding:40px;color:var(--text-muted);">Belum ada data SPBU. <a href="#" onclick="openModal('modalTambah')">Tambah sekarang</a></td></tr><?php endif; ?>
</tbody>
</table>
</div>
<!-- Modal Tambah -->
<div class="modal-overlay" id="modalTambah">
<div class="modal">
<div class="modal-header">
<span class="modal-title">⛽ Tambah SPBU Baru</span>
<button class="modal-close" onclick="closeModal('modalTambah')">×</button>
</div>
<form id="formTambah">
<div class="form-group"><label class="form-label">Nama SPBU *</label><input type="text" name="nama" class="form-control" placeholder="Nama SPBU" required></div>
<div class="form-group"><label class="form-label">Deskripsi</label><textarea name="deskripsi" class="form-control" placeholder="Keterangan tambahan..."></textarea></div>
<div class="form-group"><label class="form-label" style="display:flex;align-items:center;gap:10px;cursor:pointer;"><input type="checkbox" name="buka_24_jam" id="cb24jam" style="width:16px;height:16px;accent-color:var(--accent);"> Buka 24 Jam</label></div>
<div class="form-group">
<label class="form-label">Pilih Lokasi di Peta *</label>
<div id="miniMapTambah" style="height:200px;border-radius:8px;border:1px solid #E5E7EB;margin-bottom:10px;z-index:1;"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<input type="number" name="lat" id="latTambah" class="form-control" placeholder="Latitude" step="any" required readonly style="background:#F9FAFB;">
<input type="number" name="lng" id="lngTambah" class="form-control" placeholder="Longitude" step="any" required readonly style="background:#F9FAFB;">
</div>
</div>
</form>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modalTambah')">Batal</button>
<button class="btn btn-primary" onclick="simpan()"><i class="fas fa-save"></i> Simpan</button>
</div>
</div></div>
<!-- Modal Edit -->
<div class="modal-overlay" id="modalEdit">
<div class="modal">
<div class="modal-header">
<span class="modal-title">✏️ Edit Data SPBU</span>
<button class="modal-close" onclick="closeModal('modalEdit')">×</button>
</div>
<form id="formEdit">
<input type="hidden" name="id" id="editId">
<div class="form-group"><label class="form-label">Nama SPBU *</label><input type="text" name="nama" id="editNama" class="form-control" required></div>
<div class="form-group"><label class="form-label">Deskripsi</label><textarea name="deskripsi" id="editDesk" class="form-control"></textarea></div>
<div class="form-group"><label class="form-label" style="display:flex;align-items:center;gap:10px;cursor:pointer;"><input type="checkbox" id="editCb24" style="width:16px;height:16px;accent-color:var(--accent);"> Buka 24 Jam</label></div>
</form>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button>
<button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button>
</div>
</div></div>
<?php $extraScript = <<<'JS'
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const API = APP_BASE + '/api/spbu.php';
let miniMap = null, miniMarker = null;
const originalOpenModal = window.openModal;
window.openModal = function(id) {
originalOpenModal(id);
if(id === 'modalTambah') {
setTimeout(() => {
if(!miniMap) {
miniMap = L.map('miniMapTambah').setView([-0.0583, 109.3448], 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png').addTo(miniMap);
miniMap.on('click', function(e) {
const lat = e.latlng.lat;
const lng = e.latlng.lng;
if(!miniMarker) miniMarker = L.marker([lat, lng]).addTo(miniMap);
else miniMarker.setLatLng([lat, lng]);
document.getElementById('latTambah').value = lat.toFixed(6);
document.getElementById('lngTambah').value = lng.toFixed(6);
});
} else {
miniMap.invalidateSize();
}
}, 100);
}
};
function editRow(r) {
document.getElementById('editId').value = r.id;
document.getElementById('editNama').value = r.nama;
document.getElementById('editDesk').value = r.deskripsi || '';
document.getElementById('editCb24').checked = !!r.buka_24_jam;
openModal('modalEdit');
}
async function simpan() {
const f = document.getElementById('formTambah');
const lat = parseFloat(f.lat.value), lng = parseFloat(f.lng.value);
if (!f.nama.value || isNaN(lat) || isNaN(lng)) { showToast('Lengkapi nama dan pilih lokasi di peta!','error'); return; }
const res = await fetch(API, { method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ nama:f.nama.value, deskripsi:f.deskripsi.value,
buka_24_jam: f.querySelector('[name=buka_24_jam]').checked ? 1:0,
geometry:{ type:'Point', coordinates:[lng,lat] }}) });
const d = await res.json();
if(d.status==='success') { showToast('SPBU berhasil disimpan!'); setTimeout(()=>location.reload(),900); }
else showToast(d.message,'error');
}
async function update() {
const id = document.getElementById('editId').value;
const res = await fetch(API+'?id='+id, { method:'PUT', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ nama:document.getElementById('editNama').value,
deskripsi:document.getElementById('editDesk').value,
buka_24_jam: document.getElementById('editCb24').checked ? 1:0 }) });
const d = await res.json();
if(d.status==='success') { showToast('SPBU berhasil diperbarui!'); setTimeout(()=>location.reload(),900); }
else showToast(d.message,'error');
}
async function hapus(id, nama) {
if(!confirm(`Hapus SPBU "${nama}"?`)) return;
const res = await fetch(API+'?id='+id, { method:'DELETE' });
const d = await res.json();
if(d.status==='success') { showToast('SPBU dihapus!'); setTimeout(()=>location.reload(),900); }
else showToast(d.message,'error');
}
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+101
View File
@@ -0,0 +1,101 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT id,nama_kk,penghasilan,jumlah_tanggungan,created_at FROM warga_miskin ORDER BY id DESC")->fetchAll();
$pageTitle='Warga Miskin'; $activeNav='warga';
$extraHead = '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">';
require_once __DIR__ . '/partials/header.php';
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Warga Miskin</span></div>
<h1>👥 Manajemen Data Warga Miskin</h1>
<p>Kelola data kepala keluarga yang terdaftar sebagai penerima bantuan sosial.</p>
</div>
<div class="page-toolbar">
<div class="search-box"><i class="fas fa-search search-icon"></i><input type="text" id="searchInput" placeholder="Cari warga..." oninput="filterTable('searchInput','wargaTable')"></div>
<button class="btn btn-primary" onclick="openModal('modalTambah')"><i class="fas fa-plus"></i> Tambah Data</button>
</div>
<div class="table-wrap">
<table class="data-table" id="wargaTable">
<thead><tr><th>#</th><th>Nama KK</th><th>Penghasilan/Bln</th><th>Tanggungan</th><th>Ditambahkan</th><th>Aksi</th></tr></thead>
<tbody>
<?php foreach($rows as $i=>$r): ?>
<tr>
<td style="color:var(--text-muted)"><?=$i+1?></td>
<td><strong><?=htmlspecialchars($r['nama_kk'])?></strong></td>
<td>Rp <?=number_format($r['penghasilan'],0,',','.')?></td>
<td><span class="badge <?=$r['jumlah_tanggungan']>=4?'badge-danger':($r['jumlah_tanggungan']>=2?'badge-warning':'badge-success')?>"><?=$r['jumlah_tanggungan']?> orang</span></td>
<td style="color:var(--text-muted);font-size:0.8rem;"><?=date('d M Y',strtotime($r['created_at']))?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick="editRow(<?=htmlspecialchars(json_encode($r))?>)"><i class="fas fa-edit"></i></button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?=$r['id']?>,'<?=addslashes($r['nama_kk'])?>')"><i class="fas fa-trash"></i></button>
</div></td>
</tr>
<?php endforeach; ?>
<?php if(!$rows):?><tr><td colspan="6" style="text-align:center;padding:40px;color:var(--text-muted);">Belum ada data.</td></tr><?php endif;?>
</tbody>
</table></div>
<div class="modal-overlay" id="modalTambah"><div class="modal">
<div class="modal-header"><span class="modal-title">👥 Tambah Warga Miskin</span><button class="modal-close" onclick="closeModal('modalTambah')">×</button></div>
<form id="formTambah">
<div class="form-group"><label class="form-label">Nama Kepala Keluarga *</label><input type="text" name="nama_kk" class="form-control" placeholder="Nama lengkap KK" required></div>
<div class="form-group"><label class="form-label">Penghasilan per Bulan (Rp)</label><input type="number" name="penghasilan" class="form-control" placeholder="0" min="0"></div>
<div class="form-group"><label class="form-label">Jumlah Tanggungan (jiwa)</label><input type="number" name="jumlah_tanggungan" class="form-control" placeholder="0" min="0" max="20"></div>
<div class="form-group"><label class="form-label">Pilih Lokasi di Peta *</label>
<div id="miniMapTambah" style="height:200px;border-radius:8px;border:1px solid #E5E7EB;margin-bottom:10px;z-index:1;"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<input type="number" name="lat" id="latTambah" class="form-control" placeholder="Latitude" step="any" required readonly style="background:#F9FAFB;">
<input type="number" name="lng" id="lngTambah" class="form-control" placeholder="Longitude" step="any" required readonly style="background:#F9FAFB;">
</div>
</div>
</form>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalTambah')">Batal</button><button class="btn btn-primary" onclick="simpan()"><i class="fas fa-save"></i> Simpan</button></div>
</div></div>
<div class="modal-overlay" id="modalEdit"><div class="modal">
<div class="modal-header"><span class="modal-title">✏️ Edit Data Warga</span><button class="modal-close" onclick="closeModal('modalEdit')">×</button></div>
<input type="hidden" id="editId">
<div class="form-group"><label class="form-label">Nama KK *</label><input type="text" id="editNama" class="form-control" required></div>
<div class="form-group"><label class="form-label">Penghasilan/Bln (Rp)</label><input type="number" id="editPenghasilan" class="form-control" min="0"></div>
<div class="form-group"><label class="form-label">Jumlah Tanggungan</label><input type="number" id="editTanggungan" class="form-control" min="0" max="20"></div>
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button><button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button></div>
</div></div>
<?php $extraScript = <<<'JS'
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const API=APP_BASE + '/api/warga_miskin.php';
let miniMap = null, miniMarker = null;
const originalOpenModal = window.openModal;
window.openModal = function(id) {
originalOpenModal(id);
if(id === 'modalTambah') {
setTimeout(() => {
if(!miniMap) {
miniMap = L.map('miniMapTambah').setView([-0.0583, 109.3448], 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png').addTo(miniMap);
miniMap.on('click', function(e) {
const lat = e.latlng.lat;
const lng = e.latlng.lng;
if(!miniMarker) miniMarker = L.marker([lat, lng]).addTo(miniMap);
else miniMarker.setLatLng([lat, lng]);
document.getElementById('latTambah').value = lat.toFixed(6);
document.getElementById('lngTambah').value = lng.toFixed(6);
});
} else {
miniMap.invalidateSize();
}
}, 100);
}
};
function editRow(r){ document.getElementById('editId').value=r.id; document.getElementById('editNama').value=r.nama_kk; document.getElementById('editPenghasilan').value=r.penghasilan; document.getElementById('editTanggungan').value=r.jumlah_tanggungan; openModal('modalEdit'); }
async function simpan(){ const f=document.getElementById('formTambah'); if(!f.nama_kk.value){showToast('Nama KK wajib!','error');return;} const lat=parseFloat(f.lat.value), lng=parseFloat(f.lng.value); if(isNaN(lat)||isNaN(lng)){showToast('Silakan pilih lokasi di peta!','error');return;} const res=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama_kk:f.nama_kk.value,penghasilan:parseFloat(f.penghasilan.value)||0,jumlah_tanggungan:parseInt(f.jumlah_tanggungan.value)||0,geometry:{type:'Point',coordinates:[lng,lat]}})}); const d=await res.json(); if(d.status==='success'){showToast('Data disimpan!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function update(){ const id=document.getElementById('editId').value; const res=await fetch(API+'?id='+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama_kk:document.getElementById('editNama').value,penghasilan:parseFloat(document.getElementById('editPenghasilan').value)||0,jumlah_tanggungan:parseInt(document.getElementById('editTanggungan').value)||0})}); const d=await res.json(); if(d.status==='success'){showToast('Diperbarui!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
async function hapus(id,nama){ if(!confirm(`Hapus data "${nama}"?`))return; const res=await fetch(API+'?id='+id,{method:'DELETE'}); const d=await res.json(); if(d.status==='success'){showToast('Dihapus!');setTimeout(()=>location.reload(),900);}else showToast(d.message,'error'); }
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+172
View File
@@ -0,0 +1,172 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
// Fetch summary stats
$stats = $pdo->query("SELECT
(SELECT COUNT(*) FROM spbu) as spbu,
(SELECT COUNT(*) FROM jalan) as jalan,
(SELECT COUNT(*) FROM kavling) as kavling,
(SELECT COUNT(*) FROM rumah_ibadah) as rumah_ibadah,
(SELECT COUNT(*) FROM warga_miskin) as warga_miskin,
(SELECT COUNT(*) FROM kawasan_kumuh) as kawasan_kumuh,
(SELECT COUNT(*) FROM spbu WHERE buka_24_jam=1) as spbu_24jam,
(SELECT COUNT(*) FROM users) as total_users
")->fetch();
// Kawasan merah
$merahResult = $pdo->query("SELECT COUNT(*) as cnt FROM (
SELECT k.id FROM kawasan_kumuh k
LEFT JOIN warga_miskin w ON ST_Contains(k.geom, w.geom)
GROUP BY k.id HAVING COUNT(w.id) > 3
) sub")->fetch();
$kawasanMerah = (int)($merahResult['cnt'] ?? 0);
// Warga miskin di luar semua radius bantuan rumah ibadah
$blankCount = $pdo->query("
SELECT COUNT(*) as cnt
FROM warga_miskin w
WHERE NOT EXISTS (
SELECT 1
FROM rumah_ibadah ri
WHERE ST_Distance_Sphere(w.geom, ri.geom) <= ri.radius_bantuan_meter
)
")->fetch();
$blankSpot = (int)($blankCount['cnt'] ?? 0);
// Recent data
$recentSpbu = $pdo->query("SELECT nama, buka_24_jam, created_at FROM spbu ORDER BY created_at DESC LIMIT 5")->fetchAll();
$recentWarga = $pdo->query("SELECT nama_kk, penghasilan, jumlah_tanggungan, created_at FROM warga_miskin ORDER BY created_at DESC LIMIT 5")->fetchAll();
$pageTitle = 'Dashboard';
$activeNav = 'dashboard';
require_once __DIR__ . '/partials/header.php';
?>
<!-- ── Quick Access Map ── -->
<div class="card" style="background: #EFF6FF; border: 1px solid #BFDBFE; margin-bottom: 24px;">
<div style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:16px;">
<div>
<h3 style="font-size:1.1rem; font-weight:700; margin-bottom:6px; color:#1E3A8A;">🗺️ Buka Peta Interaktif</h3>
<p style="color:#3B82F6; font-size:0.88rem; max-width:500px;">
Tambahkan, edit, dan analisis data spasial langsung di peta. Mendukung drawing tool untuk semua layer data.
</p>
</div>
<div style="display:flex; gap:10px;">
<a href="<?= app_url('admin/peta.php') ?>" class="btn btn-primary" style="box-shadow:0 4px 6px -1px rgba(37,99,235,0.2);">
<i class="fas fa-map-marked-alt"></i> Buka Peta Admin
</a>
</div>
</div>
</div>
<!-- ── Stats Grid ── -->
<div class="stats-grid">
<div class="stat-card primary">
<div class="stat-icon"><i class="fas fa-gas-pump"></i></div>
<div class="stat-body">
<div class="stat-label">Total SPBU</div>
<div class="stat-value" id="cnt-spbu"><?= $stats['spbu'] ?></div>
<div class="stat-change"><?= $stats['spbu_24jam'] ?> buka 24 jam</div>
</div>
</div>
<div class="stat-card info">
<div class="stat-icon"><i class="fas fa-road"></i></div>
<div class="stat-body">
<div class="stat-label">Ruas Jalan</div>
<div class="stat-value"><?= $stats['jalan'] ?></div>
<div class="stat-change">Segmen terpetakan</div>
</div>
</div>
<div class="stat-card success">
<div class="stat-icon"><i class="fas fa-mosque"></i></div>
<div class="stat-body">
<div class="stat-label">Rumah Ibadah</div>
<div class="stat-value"><?= $stats['rumah_ibadah'] ?></div>
<div class="stat-change">5 agama tercakup</div>
</div>
</div>
<div class="stat-card warning">
<div class="stat-icon"><i class="fas fa-users"></i></div>
<div class="stat-body">
<div class="stat-label">Warga Miskin</div>
<div class="stat-value"><?= $stats['warga_miskin'] ?></div>
<div class="stat-change"><?= $blankSpot ?> blank spot</div>
</div>
</div>
<div class="stat-card danger">
<div class="stat-icon"><i class="fas fa-exclamation-triangle"></i></div>
<div class="stat-body">
<div class="stat-label">Kawasan Rawan</div>
<div class="stat-value"><?= $kawasanMerah ?></div>
<div class="stat-change">dari <?= $stats['kawasan_kumuh'] ?> kawasan</div>
</div>
</div>
<div class="stat-card violet">
<div class="stat-icon"><i class="fas fa-vector-square"></i></div>
<div class="stat-body">
<div class="stat-label">Kavling / Parsil</div>
<div class="stat-value"><?= $stats['kavling'] ?></div>
<div class="stat-change">Bidang tanah tercatat</div>
</div>
</div>
</div>
<!-- ── Content Row ── -->
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px;">
<!-- Recent SPBU -->
<div class="card">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px;">
<h3 style="font-size:0.95rem; font-weight:700;">⛽ SPBU Terbaru</h3>
<a href="<?= app_url('admin/data_spbu.php') ?>" class="btn btn-ghost btn-sm">Lihat Semua</a>
</div>
<?php if ($recentSpbu): ?>
<div style="display:flex; flex-direction:column; gap:8px;">
<?php foreach ($recentSpbu as $s): ?>
<div style="display:flex; align-items:center; justify-content:space-between; padding:10px 12px; background:#F9FAFB; border:1px solid #E5E7EB; border-radius:8px;">
<div>
<div style="font-weight:600; font-size:0.88rem;"><?= htmlspecialchars($s['nama']) ?></div>
<div style="font-size:0.75rem; color:var(--text-muted);"><?= date('d M Y', strtotime($s['created_at'])) ?></div>
</div>
<span class="badge <?= $s['buka_24_jam'] ? 'badge-success' : 'badge-danger' ?>">
<?= $s['buka_24_jam'] ? '24 Jam' : 'Terbatas' ?>
</span>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<p style="color:var(--text-muted); text-align:center; padding:20px 0; font-size:0.85rem;">Belum ada data SPBU</p>
<?php endif; ?>
</div>
<!-- Recent Warga Miskin -->
<div class="card">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px;">
<h3 style="font-size:0.95rem; font-weight:700;">👥 Warga Terdaftar</h3>
<a href="<?= app_url('admin/data_warga.php') ?>" class="btn btn-ghost btn-sm">Lihat Semua</a>
</div>
<?php if ($recentWarga): ?>
<div style="display:flex; flex-direction:column; gap:8px;">
<?php foreach ($recentWarga as $w): ?>
<div style="display:flex; align-items:center; justify-content:space-between; padding:10px 12px; background:#F9FAFB; border:1px solid #E5E7EB; border-radius:8px;">
<div>
<div style="font-weight:600; font-size:0.88rem;"><?= htmlspecialchars($w['nama_kk']) ?></div>
<div style="font-size:0.75rem; color:var(--text-muted);">Rp <?= number_format($w['penghasilan'],0,',','.') ?>/bln · <?= $w['jumlah_tanggungan'] ?> tanggungan</div>
</div>
<span class="badge badge-warning"><?= $w['jumlah_tanggungan'] ?> org</span>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<p style="color:var(--text-muted); text-align:center; padding:20px 0; font-size:0.85rem;">Belum ada data warga</p>
<?php endif; ?>
</div>
</div>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+119
View File
@@ -0,0 +1,119 @@
</div><!-- /admin-content -->
</main><!-- /admin-main -->
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
// ── Clock ──
function updateClock() {
const now = new Date();
document.getElementById('clockText').textContent =
now.toLocaleTimeString('id-ID', { hour:'2-digit', minute:'2-digit', second:'2-digit' });
}
updateClock();
setInterval(updateClock, 1000);
// ── Toast utility ──
window.showToast = function(msg, type = 'success') {
const t = document.createElement('div');
t.className = `toast ${type}`;
const icons = { success: 'check-circle', error: 'exclamation-circle', info: 'info-circle' };
t.innerHTML = `<i class="fas fa-${icons[type]||'info-circle'}" style="margin-right:8px;"></i>${msg}`;
document.getElementById('toastContainer').appendChild(t);
setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 400); }, 3000);
};
// ── Modal helpers ──
window.openModal = function(id) {
document.getElementById(id)?.classList.add('active');
document.body.style.overflow = 'hidden';
};
window.closeModal = function(id) {
document.getElementById(id)?.classList.remove('active');
document.body.style.overflow = '';
};
// Close modal on overlay click
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', e => {
if (e.target === overlay) closeModal(overlay.id);
});
});
// ── Search filter ──
window.filterTable = function(inputId, tableId) {
const q = document.getElementById(inputId)?.value.toLowerCase() || '';
document.querySelectorAll(`#${tableId} tbody tr`).forEach(row => {
row.style.display = row.textContent.toLowerCase().includes(q) ? '' : 'none';
});
};
// Realtime SPBU snackbar
(function initSpbuRealtimeSnackbar() {
const API_URL = APP_BASE + '/api/spbu_status.php';
const REFRESH_MS = 60000;
const SHOW_MS = 7000;
let lastOpen = null;
let hideTimer = null;
function ensureSnackbar() {
let el = document.getElementById('spbuRealtimeSnackbar');
if (el) return el;
el = document.createElement('div');
el.id = 'spbuRealtimeSnackbar';
el.className = 'spbu-snackbar';
el.innerHTML = `
<div class="spbu-snackbar-icon"><i class="fas fa-gas-pump"></i></div>
<div>
<div class="spbu-snackbar-title" id="spbuSnackbarTitle">SPBU sedang buka</div>
<div class="spbu-snackbar-meta" id="spbuSnackbarMeta">Memuat status terbaru...</div>
</div>
<div class="spbu-snackbar-count" id="spbuSnackbarCount">-</div>
`;
document.body.appendChild(el);
return el;
}
function showSnackbar(data) {
const el = ensureSnackbar();
const open = Number(data.spbu_buka || 0);
const total = Number(data.total_spbu || 0);
const closed = Number(data.spbu_tutup || Math.max(0, total - open));
const time = data.server_time ? data.server_time.slice(11, 16) : new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
document.getElementById('spbuSnackbarTitle').textContent = `${open} SPBU sedang buka`;
document.getElementById('spbuSnackbarMeta').textContent = `${closed} tutup/terbatas dari ${total} total - update ${time} WIB`;
document.getElementById('spbuSnackbarCount').textContent = open;
el.classList.add('show');
clearTimeout(hideTimer);
hideTimer = setTimeout(() => el.classList.remove('show'), SHOW_MS);
}
async function refreshStatus(forceShow = false) {
try {
const res = await fetch(API_URL, { cache: 'no-store' });
const json = await res.json();
if (json.status !== 'success' || !json.data) return;
const open = Number(json.data.spbu_buka || 0);
if (forceShow || lastOpen === null || open !== lastOpen) {
showSnackbar(json.data);
}
lastOpen = open;
} catch (e) {
// Snackbar is informational; keep the page quiet if polling fails.
}
}
document.addEventListener('DOMContentLoaded', () => {
refreshStatus(true);
setInterval(() => refreshStatus(false), REFRESH_MS);
});
})();
</script>
<?= $extraScript ?? '' ?>
</body>
</html>
+110
View File
@@ -0,0 +1,110 @@
<?php
/**
* Admin Layout — sidebar + topbar
* Include di awal setiap halaman admin setelah auth check.
* Variables expected from parent: $pageTitle, $activeNav
*/
$user = currentUser();
$initial = strtoupper(substr($user['nama_lengkap'] ?: $user['username'], 0, 1));
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($pageTitle ?? 'Admin Panel') ?> — WebGIS Smart City</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="<?= app_url('assets/css/main.css') ?>?v=20260610-spbu-snackbar">
<link rel="stylesheet" href="<?= app_url('assets/css/admin.css') ?>">
<script>const APP_BASE = '<?= app_url() ?>';</script>
<?= $extraHead ?? '' ?>
</head>
<body class="admin-page <?= $bodyClass ?? '' ?>">
<!-- ══ SIDEBAR ══ -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-brand">
<div class="brand-icon">🗺️</div>
<div class="brand-text">
<span class="brand-name">WebGIS</span>
<span class="brand-sub">Smart City Admin</span>
</div>
</div>
<nav class="sidebar-nav">
<div class="nav-section-title">Utama</div>
<a href="<?= app_url('admin/index.php') ?>"
class="nav-item <?= ($activeNav==='dashboard') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-chart-pie"></i></span> Dashboard
</a>
<a href="<?= app_url('admin/peta.php') ?>"
class="nav-item <?= ($activeNav==='peta') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-map-marked-alt"></i></span> Peta Interaktif
</a>
<div class="nav-section-title">Data Spasial</div>
<a href="<?= app_url('admin/data_spbu.php') ?>"
class="nav-item <?= ($activeNav==='spbu') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-gas-pump"></i></span> SPBU
</a>
<a href="<?= app_url('admin/data_jalan.php') ?>"
class="nav-item <?= ($activeNav==='jalan') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-road"></i></span> Jalan
</a>
<a href="<?= app_url('admin/data_kavling.php') ?>"
class="nav-item <?= ($activeNav==='kavling') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-vector-square"></i></span> Kavling / Parsil
</a>
<a href="<?= app_url('admin/data_rumah.php') ?>"
class="nav-item <?= ($activeNav==='rumah') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-mosque"></i></span> Rumah Ibadah
</a>
<a href="<?= app_url('admin/data_warga.php') ?>"
class="nav-item <?= ($activeNav==='warga') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-users"></i></span> Warga Miskin
</a>
<a href="<?= app_url('admin/data_kawasan.php') ?>"
class="nav-item <?= ($activeNav==='kawasan') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-draw-polygon"></i></span> Kawasan Kumuh
</a>
<a href="<?= app_url('admin/data_laporan.php') ?>"
class="nav-item <?= ($activeNav==='laporan') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-bullhorn"></i></span> Laporan Warga
</a>
<div class="nav-section-title">Manajemen</div>
<a href="<?= app_url('admin/users.php') ?>"
class="nav-item <?= ($activeNav==='users') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-user-shield"></i></span> Kelola Users
</a>
</nav>
<div class="sidebar-footer">
<div class="user-info">
<div class="user-avatar"><?= $initial ?></div>
<div class="user-details">
<div class="user-name"><?= htmlspecialchars($user['nama_lengkap'] ?: $user['username']) ?></div>
<div class="user-role">Administrator</div>
</div>
<a href="<?= app_url('logout.php') ?>" class="user-logout" title="Keluar">
<i class="fas fa-sign-out-alt"></i>
</a>
</div>
</div>
</aside>
<!-- ══ MAIN CONTENT ══ -->
<main class="admin-main">
<div class="topbar">
<span class="topbar-title"><?= htmlspecialchars($pageTitle ?? 'Dashboard') ?></span>
<div class="topbar-actions">
<div class="topbar-time" id="clock">
<i class="fas fa-clock"></i> <span id="clockText"></span>
</div>
<a href="<?= app_url('admin/peta.php') ?>" class="btn btn-primary btn-sm">
<i class="fas fa-map"></i> Buka Peta
</a>
</div>
</div>
<div class="admin-content">
+173
View File
@@ -0,0 +1,173 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pageTitle = 'Peta Interaktif';
$activeNav = 'peta';
$bodyClass = 'admin-map-page map-page';
$extraHead = '
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css">
<link rel="stylesheet" href="' . app_url('assets/css/map.css') . '?v=20260610-2">
';
require_once __DIR__ . '/partials/header.php';
?>
<div class="map-wrap">
<div id="admin-map"></div>
<aside class="map-sidebar">
<div class="map-panel-header">
<div class="map-panel-icon"><i class="fas fa-layer-group"></i></div>
<div>
<h3>Layer Peta</h3>
<p>Kontrol data yang tampil di peta</p>
</div>
</div>
<div class="layer-section">
<div class="layer-section-title">Infrastruktur</div>
<label class="layer-item">
<input type="checkbox" id="ly-spbu" checked>
<span class="layer-color layer-point" style="--layer-color:#F59E0B"><i class="fas fa-gas-pump"></i></span>
<span class="layer-text">
<span class="layer-name">SPBU</span>
<span class="layer-meta">Stasiun pengisian bahan bakar</span>
</span>
</label>
<label class="layer-item">
<input type="checkbox" id="ly-jalan" checked>
<span class="layer-color layer-line" style="--layer-color:#3B82F6"></span>
<span class="layer-text">
<span class="layer-name">Jalan</span>
<span class="layer-meta">Ruas jalan utama dan lokal</span>
</span>
</label>
<label class="layer-item">
<input type="checkbox" id="ly-kavling" checked>
<span class="layer-color layer-area" style="--layer-color:#7C3AED"></span>
<span class="layer-text">
<span class="layer-name">Kavling / Parsil</span>
<span class="layer-meta">Batas bidang tanah</span>
</span>
</label>
</div>
<div class="layer-section">
<div class="layer-section-title">Sosial</div>
<label class="layer-item">
<input type="checkbox" id="ly-rumah" checked>
<span class="layer-color layer-point" style="--layer-color:#8B5CF6"><i class="fas fa-mosque"></i></span>
<span class="layer-text">
<span class="layer-name">Rumah Ibadah</span>
<span class="layer-meta">Fasilitas keagamaan</span>
</span>
</label>
<label class="layer-item">
<input type="checkbox" id="ly-warga" checked>
<span class="layer-color layer-point" style="--layer-color:#EF4444"><i class="fas fa-users"></i></span>
<span class="layer-text">
<span class="layer-name">Warga Miskin</span>
<span class="layer-meta">Sebaran keluarga penerima bantuan</span>
</span>
</label>
<label class="layer-item">
<input type="checkbox" id="ly-kawasan">
<span class="layer-color layer-area layer-hatch" style="--layer-color:#DC2626"></span>
<span class="layer-text">
<span class="layer-name">Kawasan Kumuh</span>
<span class="layer-meta">Area prioritas penanganan</span>
</span>
</label>
</div>
<div class="layer-section">
<div class="layer-section-title">Analisis</div>
<label class="layer-item">
<input type="checkbox" id="ly-choropleth">
<span class="layer-color layer-gradient"></span>
<span class="layer-text">
<span class="layer-name">Choropleth Kepadatan</span>
<span class="layer-meta">Kepadatan warga miskin per kawasan</span>
</span>
</label>
<label class="layer-item">
<input type="checkbox" id="ly-blankspot">
<span class="layer-color layer-point" style="--layer-color:#F97316"><i class="fas fa-location-dot"></i></span>
<span class="layer-text">
<span class="layer-name">Blank Spot Bansos</span>
<span class="layer-meta">Titik jauh dari fasilitas bantuan</span>
</span>
</label>
</div>
<div class="map-quickstats">
<div class="qs-item"><div class="qs-value" id="qs-spbu" style="color:#F59E0B">-</div><div class="qs-label">SPBU</div></div>
<div class="qs-item"><div class="qs-value" id="qs-jalan" style="color:#3B82F6">-</div><div class="qs-label">Jalan</div></div>
<div class="qs-item"><div class="qs-value" id="qs-kavling" style="color:#7C3AED">-</div><div class="qs-label">Kavling</div></div>
<div class="qs-item"><div class="qs-value" id="qs-warga" style="color:#EF4444">-</div><div class="qs-label">Warga</div></div>
</div>
</aside>
<div class="map-analysis">
<button data-draw="spbu" title="Klik lalu klik di peta untuk menaruh SPBU"><i class="fas fa-gas-pump"></i> SPBU</button>
<button data-draw="jalan" title="Klik beberapa titik untuk menggambar jalan"><i class="fas fa-road"></i> Jalan</button>
<button data-draw="kavling" title="Klik titik sudut polygon untuk menggambar kavling"><i class="fas fa-vector-square"></i> Kavling</button>
<button data-draw="kawasan" title="Gambar polygon kawasan kumuh"><i class="fas fa-draw-polygon"></i> Kawasan</button>
<button data-draw="rumah" title="Taruh marker rumah ibadah"><i class="fas fa-mosque"></i> Rumah Ibadah</button>
<button data-draw="warga" title="Taruh marker warga miskin"><i class="fas fa-user"></i> Warga</button>
<button id="btn-find-spbu" class="map-tool-accent" title="Cari SPBU terdekat dari pusat peta"><i class="fas fa-search-location"></i> SPBU Terdekat</button>
</div>
<div class="spbu-panel" id="spbuPanel" style="display:none;">
<div class="spbu-panel-title">
<i class="fas fa-search-location"></i> SPBU Terdekat
<button class="modal-close" id="btn-close-spbu" style="margin-left:auto;background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:1.2rem;">&times;</button>
</div>
<div id="spbuPanelResult"></div>
</div>
<div class="map-legend">
<div class="map-legend-title"><i class="fas fa-list-ul"></i> Legenda</div>
<div class="legend-grid">
<div class="legend-item"><span class="legend-symbol legend-dot" style="--legend-color:#10B981"></span><span>SPBU 24 Jam</span></div>
<div class="legend-item"><span class="legend-symbol legend-dot" style="--legend-color:#F59E0B"></span><span>SPBU Terbatas</span></div>
<div class="legend-item"><span class="legend-symbol legend-line" style="--legend-color:#3B82F6"></span><span>Jalan</span></div>
<div class="legend-item"><span class="legend-symbol legend-area" style="--legend-color:#7C3AED"></span><span>Kavling</span></div>
<div class="legend-item"><span class="legend-symbol legend-dot" style="--legend-color:#8B5CF6"></span><span>Rumah Ibadah</span></div>
<div class="legend-item"><span class="legend-symbol legend-dot" style="--legend-color:#EF4444"></span><span>Warga Miskin</span></div>
<div class="legend-item"><span class="legend-symbol legend-hatch" style="--legend-color:#DC2626"></span><span>Kawasan Kumuh</span></div>
<div class="legend-item"><span class="legend-symbol legend-dot" style="--legend-color:#F97316"></span><span>Blank Spot</span></div>
</div>
</div>
<div class="map-loading" id="loadingOverlay" style="display:none;">
<div class="spinner"></div>
<span>Memuat data...</span>
</div>
<div class="map-modal" id="formModal">
<div class="map-modal-content">
<div class="modal-header" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<span class="modal-title" id="formModalTitle">Tambah Data</span>
<button class="modal-close" onclick="closeFormModal()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:1.3rem;">&times;</button>
</div>
<div id="formModalBody"></div>
</div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
<script src="<?= app_url('assets/js/admin-map.js') ?>?v=20260610-radius"></script>
<?php $extraScript = ''; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>
+179
View File
@@ -0,0 +1,179 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/auth_check.php';
requireRole('admin');
$pdo = Database::getConnection();
$rows = $pdo->query("SELECT id, username, role, nama_lengkap, created_at FROM users ORDER BY id ASC")->fetchAll();
$pageTitle = 'Kelola Users';
$activeNav = 'users';
require_once __DIR__ . '/partials/header.php';
?>
<div class="page-header">
<div class="breadcrumb"><span>Admin</span><span class="sep">/</span><span>Users</span></div>
<h1>👤 Manajemen User Sistem</h1>
<p>Kelola akun administrator dan pengguna yang dapat masuk ke WebGIS Smart City.</p>
</div>
<div class="page-toolbar">
<div class="search-box">
<i class="fas fa-search search-icon"></i>
<input type="text" id="searchInput" placeholder="Cari user..." oninput="filterTable('searchInput','usersTable')">
</div>
<button class="btn btn-primary" onclick="openModal('modalTambah')">
<i class="fas fa-plus"></i> Tambah User
</button>
</div>
<div class="table-wrap">
<table class="data-table" id="usersTable">
<thead><tr>
<th>#</th><th>Username</th><th>Nama Lengkap</th><th>Role</th><th>Dibuat</th><th>Aksi</th>
</tr></thead>
<tbody>
<?php foreach ($rows as $i => $r): ?>
<tr>
<td style="color:var(--text-muted)"><?= $i+1 ?></td>
<td><strong><?= htmlspecialchars($r['username']) ?></strong></td>
<td><?= htmlspecialchars($r['nama_lengkap'] ?: '-') ?></td>
<td>
<?php if ($r['role'] === 'admin'): ?>
<span class="badge badge-danger">👨‍💼 Admin</span>
<?php else: ?>
<span class="badge badge-info">👤 Pengguna</span>
<?php endif; ?>
</td>
<td style="color:var(--text-muted); font-size:0.8rem;"><?= date('d M Y', strtotime($r['created_at'])) ?></td>
<td><div class="actions">
<button class="btn btn-ghost btn-sm btn-icon" onclick='editRow(<?= json_encode($r, JSON_HEX_APOS|JSON_HEX_QUOT) ?>)' title="Edit">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-danger btn-sm btn-icon" onclick="hapus(<?= $r['id'] ?>,'<?= addslashes($r['username']) ?>')" title="Hapus">
<i class="fas fa-trash"></i>
</button>
</div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Modal Tambah -->
<div class="modal-overlay" id="modalTambah">
<div class="modal">
<div class="modal-header">
<span class="modal-title">👤 Tambah User Baru</span>
<button class="modal-close" onclick="closeModal('modalTambah')">×</button>
</div>
<form id="formTambah">
<div class="form-group"><label class="form-label">Username *</label><input type="text" name="username" id="tUsername" class="form-control" placeholder="username unik" required></div>
<div class="form-group"><label class="form-label">Nama Lengkap</label><input type="text" name="nama_lengkap" id="tNama" class="form-control" placeholder="Nama lengkap"></div>
<div class="form-group"><label class="form-label">Role *</label>
<select name="role" id="tRole" class="form-control">
<option value="user">👤 Pengguna (read-only)</option>
<option value="admin">👨‍💼 Administrator</option>
</select>
</div>
<div class="form-group"><label class="form-label">Password *</label><input type="password" name="password" id="tPassword" class="form-control" placeholder="Minimal 6 karakter" required minlength="6"></div>
</form>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modalTambah')">Batal</button>
<button class="btn btn-primary" onclick="simpan()"><i class="fas fa-save"></i> Simpan</button>
</div>
</div>
</div>
<!-- Modal Edit -->
<div class="modal-overlay" id="modalEdit">
<div class="modal">
<div class="modal-header">
<span class="modal-title">✏️ Edit User</span>
<button class="modal-close" onclick="closeModal('modalEdit')">×</button>
</div>
<input type="hidden" id="editId">
<div class="form-group"><label class="form-label">Username *</label><input type="text" id="editUsername" class="form-control" required></div>
<div class="form-group"><label class="form-label">Nama Lengkap</label><input type="text" id="editNama" class="form-control"></div>
<div class="form-group"><label class="form-label">Role *</label>
<select id="editRole" class="form-control">
<option value="user">👤 Pengguna</option>
<option value="admin">👨‍💼 Administrator</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Password Baru</label>
<input type="password" id="editPassword" class="form-control" placeholder="Kosongkan jika tidak ingin mengubah" minlength="6">
<div style="font-size:0.72rem;color:var(--text-muted);margin-top:4px;">Kosongkan jika tidak ingin mengubah password.</div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modalEdit')">Batal</button>
<button class="btn btn-primary" onclick="update()"><i class="fas fa-save"></i> Perbarui</button>
</div>
</div>
</div>
<?php $extraScript = <<<'JS'
<script>
const API = APP_BASE + '/api/users.php';
function editRow(r) {
document.getElementById('editId').value = r.id;
document.getElementById('editUsername').value = r.username;
document.getElementById('editNama').value = r.nama_lengkap || '';
document.getElementById('editRole').value = r.role;
document.getElementById('editPassword').value = '';
openModal('modalEdit');
}
async function simpan() {
const u = document.getElementById('tUsername').value.trim();
const p = document.getElementById('tPassword').value;
if (!u) return showToast('Username wajib!', 'error');
if (p.length < 6) return showToast('Password minimal 6 karakter!', 'error');
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: u,
nama_lengkap: document.getElementById('tNama').value,
role: document.getElementById('tRole').value,
password: p
})
});
const d = await res.json();
if (d.status === 'success') { showToast('User berhasil ditambahkan!'); setTimeout(() => location.reload(), 900); }
else showToast(d.message || 'Gagal', 'error');
}
async function update() {
const id = document.getElementById('editId').value;
const u = document.getElementById('editUsername').value.trim();
const p = document.getElementById('editPassword').value;
if (!u) return showToast('Username wajib!', 'error');
if (p && p.length < 6) return showToast('Password minimal 6 karakter!', 'error');
const body = {
username: u,
nama_lengkap: document.getElementById('editNama').value,
role: document.getElementById('editRole').value
};
if (p) body.password = p;
const res = await fetch(API + '?id=' + id, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const d = await res.json();
if (d.status === 'success') { showToast('User diperbarui!'); setTimeout(() => location.reload(), 900); }
else showToast(d.message || 'Gagal', 'error');
}
async function hapus(id, username) {
if (id == 1) return showToast('Admin utama (ID=1) tidak dapat dihapus!', 'error');
if (!confirm(`Hapus user "${username}"?`)) return;
const res = await fetch(API + '?id=' + id, { method: 'DELETE' });
const d = await res.json();
if (d.status === 'success') { showToast('User dihapus!'); setTimeout(() => location.reload(), 900); }
else showToast(d.message || 'Gagal', 'error');
}
</script>
JS; ?>
<?php require_once __DIR__ . '/partials/footer.php'; ?>