pertama dan terakhir

This commit is contained in:
Tazyeuu
2026-06-10 22:07:47 +07:00
commit cd376094df
143 changed files with 12090 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
</div><!-- /admin-content -->
</main><!-- /admin-main -->
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
// ── Clock ──
function updateClock() {
const now = new Date();
document.getElementById('clockText').textContent =
now.toLocaleTimeString('id-ID', { hour:'2-digit', minute:'2-digit', second:'2-digit' });
}
updateClock();
setInterval(updateClock, 1000);
// ── Toast utility ──
window.showToast = function(msg, type = 'success') {
const t = document.createElement('div');
t.className = `toast ${type}`;
const icons = { success: 'check-circle', error: 'exclamation-circle', info: 'info-circle' };
t.innerHTML = `<i class="fas fa-${icons[type]||'info-circle'}" style="margin-right:8px;"></i>${msg}`;
document.getElementById('toastContainer').appendChild(t);
setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 400); }, 3000);
};
// ── Modal helpers ──
window.openModal = function(id) {
document.getElementById(id)?.classList.add('active');
document.body.style.overflow = 'hidden';
};
window.closeModal = function(id) {
document.getElementById(id)?.classList.remove('active');
document.body.style.overflow = '';
};
// Close modal on overlay click
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', e => {
if (e.target === overlay) closeModal(overlay.id);
});
});
// ── Search filter ──
window.filterTable = function(inputId, tableId) {
const q = document.getElementById(inputId)?.value.toLowerCase() || '';
document.querySelectorAll(`#${tableId} tbody tr`).forEach(row => {
row.style.display = row.textContent.toLowerCase().includes(q) ? '' : 'none';
});
};
// Realtime SPBU snackbar
(function initSpbuRealtimeSnackbar() {
const API_URL = APP_BASE + '/api/spbu_status.php';
const REFRESH_MS = 60000;
const SHOW_MS = 7000;
let lastOpen = null;
let hideTimer = null;
function ensureSnackbar() {
let el = document.getElementById('spbuRealtimeSnackbar');
if (el) return el;
el = document.createElement('div');
el.id = 'spbuRealtimeSnackbar';
el.className = 'spbu-snackbar';
el.innerHTML = `
<div class="spbu-snackbar-icon"><i class="fas fa-gas-pump"></i></div>
<div>
<div class="spbu-snackbar-title" id="spbuSnackbarTitle">SPBU sedang buka</div>
<div class="spbu-snackbar-meta" id="spbuSnackbarMeta">Memuat status terbaru...</div>
</div>
<div class="spbu-snackbar-count" id="spbuSnackbarCount">-</div>
`;
document.body.appendChild(el);
return el;
}
function showSnackbar(data) {
const el = ensureSnackbar();
const open = Number(data.spbu_buka || 0);
const total = Number(data.total_spbu || 0);
const closed = Number(data.spbu_tutup || Math.max(0, total - open));
const time = data.server_time ? data.server_time.slice(11, 16) : new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
document.getElementById('spbuSnackbarTitle').textContent = `${open} SPBU sedang buka`;
document.getElementById('spbuSnackbarMeta').textContent = `${closed} tutup/terbatas dari ${total} total - update ${time} WIB`;
document.getElementById('spbuSnackbarCount').textContent = open;
el.classList.add('show');
clearTimeout(hideTimer);
hideTimer = setTimeout(() => el.classList.remove('show'), SHOW_MS);
}
async function refreshStatus(forceShow = false) {
try {
const res = await fetch(API_URL, { cache: 'no-store' });
const json = await res.json();
if (json.status !== 'success' || !json.data) return;
const open = Number(json.data.spbu_buka || 0);
if (forceShow || lastOpen === null || open !== lastOpen) {
showSnackbar(json.data);
}
lastOpen = open;
} catch (e) {
// Snackbar is informational; keep the page quiet if polling fails.
}
}
document.addEventListener('DOMContentLoaded', () => {
refreshStatus(true);
setInterval(() => refreshStatus(false), REFRESH_MS);
});
})();
</script>
<?= $extraScript ?? '' ?>
</body>
</html>
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* User Layout — sidebar + topbar (read-only mode, beda style dari admin)
* 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 ?? 'User Portal') ?> — 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 user-portal <?= $bodyClass ?? '' ?>">
<!-- ══ SIDEBAR ══ -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-brand">
<div class="brand-icon" style="background:linear-gradient(135deg,#10B981,#059669);">🗺️</div>
<div class="brand-text">
<span class="brand-name">WebGIS</span>
<span class="brand-sub">Pengguna</span>
</div>
</div>
<nav class="sidebar-nav">
<div class="nav-section-title">Menu</div>
<a href="<?= app_url('user/index.php') ?>"
class="nav-item <?= ($activeNav==='dashboard') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-chart-pie"></i></span> Beranda
</a>
<a href="<?= app_url('user/peta.php') ?>"
class="nav-item <?= ($activeNav==='peta') ? 'active' : '' ?>">
<span class="nav-icon"><i class="fas fa-map-marked-alt"></i></span> Peta Interaktif
</a>
<a href="<?= app_url('user/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">Informasi</div>
<div class="user-info" style="background:rgba(16,185,129,0.08);border-color:rgba(16,185,129,0.2);">
<i class="fas fa-info-circle" style="color:#059669;font-size:1.1rem;"></i>
<div style="font-size:0.78rem;color:var(--text-secondary);line-height:1.4;">
Anda masuk sebagai <strong style="color:#059669">Pengguna</strong>. Data hanya dapat dilihat, tidak dapat diubah.
</div>
</div>
</nav>
<div class="sidebar-footer">
<div class="user-info">
<div class="user-avatar" style="background:linear-gradient(135deg,#10B981,#059669);"><?= $initial ?></div>
<div class="user-details">
<div class="user-name"><?= htmlspecialchars($user['nama_lengkap'] ?: $user['username']) ?></div>
<div class="user-role">Pengguna</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 ?? 'Beranda') ?></span>
<div class="topbar-actions">
<div class="topbar-time" id="clock">
<i class="fas fa-clock"></i> <span id="clockText"></span>
</div>
<span class="badge badge-success" style="font-size:0.7rem;padding:5px 10px;">
<i class="fas fa-eye"></i> Mode Baca
</span>
</div>
</div>
<div class="admin-content">