pertama dan terakhir
This commit is contained in:
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
@@ -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;">×</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;">×</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'; ?>
|
||||
@@ -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'; ?>
|
||||
Reference in New Issue
Block a user