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