feat: refresh UI and add sidebar navigation
This commit is contained in:
+55
-108
@@ -22,28 +22,25 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
* { font-family: 'Plus Jakarta Sans', sans-serif; }
|
||||
body { font-weight: 500; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
body { height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.map-wrapper { flex: 1; position: relative; min-height: 0; }
|
||||
#map { width: 100%; height: 100%; }
|
||||
|
||||
/* Floating Controls */
|
||||
.floating-panel {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
top: 24px;
|
||||
left: 24px;
|
||||
z-index: 1000;
|
||||
width: 300px;
|
||||
width: 316px;
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
.map-panel {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
||||
border: 1px solid #e8e8ec;
|
||||
}
|
||||
|
||||
/* Marker dragging hint */
|
||||
@@ -57,102 +54,27 @@
|
||||
|
||||
/* Custom Layer Control */
|
||||
.leaflet-control-layers {
|
||||
border-radius: 12px !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15) !important;
|
||||
border-radius: 8px !important;
|
||||
border: 1px solid #e8e8ec !important;
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(255, 255, 255, 0.85) !important;
|
||||
background: rgba(255, 255, 255, 0.92) !important;
|
||||
padding: 8px 12px !important;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<body class="app-body app-map-body">
|
||||
|
||||
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm flex-shrink-0 z-[1001]">
|
||||
<div class="flex-1">
|
||||
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
||||
<i class="fa-solid fa-house-chimney-crack"></i>
|
||||
WebGIS Pemetaan Kemiskinan
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<ul class="menu menu-horizontal px-1 gap-1">
|
||||
<li>
|
||||
<a href="{{ url('/map') }}" class="font-semibold bg-primary/10 text-primary rounded-lg">
|
||||
<i class="fa-solid fa-map"></i> Peta Interaktif
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('/data') }}" class="font-medium">
|
||||
<i class="fa-solid fa-table-list"></i> Data Tabular
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('/dashboard') }}" class="font-medium">
|
||||
<i class="fa-solid fa-chart-bar"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('/permohonan') }}" class="font-medium">
|
||||
<i class="fa-solid fa-right-left"></i> Transfer
|
||||
</a>
|
||||
</li>
|
||||
@if(auth()->user()->hasRole('administrator'))
|
||||
<li>
|
||||
<a href="{{ url('/admin/users') }}" class="font-medium">
|
||||
<i class="fa-solid fa-users-gear"></i> Admin
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('/admin/logs') }}" class="font-medium">
|
||||
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('/admin/settings') }}" class="font-medium">
|
||||
<i class="fa-solid fa-sliders"></i> Pengaturan
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
<!-- User dropdown -->
|
||||
<div class="dropdown dropdown-end ml-2">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost gap-2">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-primary text-primary-content rounded-full w-8">
|
||||
<span class="text-xs font-bold">{{ substr(auth()->user()->name, 0, 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="hidden sm:inline text-sm font-semibold">{{ auth()->user()->name }}</span>
|
||||
<i class="fa-solid fa-chevron-down text-xs opacity-50"></i>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-200 mt-2">
|
||||
<li class="menu-title">
|
||||
<span class="text-xs text-base-content/50 capitalize">{{ str_replace('_', ' ', auth()->user()->getRoleNames()->first() ?? 'user') }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="text-error w-full text-left flex items-center gap-2">
|
||||
<i class="fa-solid fa-right-from-bracket"></i>
|
||||
Keluar
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include('partials.sidebar')
|
||||
|
||||
<!-- ═══ MAP CONTAINER ════════════════════════════════════════════════════ -->
|
||||
<div class="map-wrapper">
|
||||
<div class="map-wrapper app-map-main">
|
||||
<div id="map"></div>
|
||||
|
||||
<!-- Floating Control Panel -->
|
||||
<div class="floating-panel flex flex-col gap-3">
|
||||
<!-- Mode Selector -->
|
||||
<div class="card glass-card p-4">
|
||||
<div class="card map-panel p-4">
|
||||
<h3 class="text-sm font-bold mb-3 flex items-center gap-2">
|
||||
<i class="fa-solid fa-layer-group text-primary"></i>
|
||||
Mode Operasi
|
||||
@@ -161,17 +83,17 @@
|
||||
<button class="btn btn-sm btn-outline btn-primary justify-start gap-2 active" id="btnModeView" onclick="setMode('view')">
|
||||
<i class="fa-solid fa-eye"></i> View & Analisis
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline btn-success justify-start gap-2" id="btnModeIbadah" onclick="setMode('ibadah')">
|
||||
<button class="btn btn-sm btn-outline justify-start gap-2" id="btnModeIbadah" onclick="setMode('ibadah')">
|
||||
<i class="fa-solid fa-plus"></i> Tambah Rumah Ibadah
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline btn-error justify-start gap-2" id="btnModeMiskin" onclick="setMode('miskin')">
|
||||
<button class="btn btn-sm btn-outline justify-start gap-2" id="btnModeMiskin" onclick="setMode('miskin')">
|
||||
<i class="fa-solid fa-plus"></i> Tambah Rumah Miskin
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis Panel (Conditional) -->
|
||||
<div id="analysisPanel" class="card glass-card p-4 hidden">
|
||||
<div id="analysisPanel" class="card map-panel p-4 hidden">
|
||||
<h3 class="text-sm font-bold mb-2 flex items-center gap-2">
|
||||
<i class="fa-solid fa-chart-pie text-secondary"></i>
|
||||
Statistik Radius
|
||||
@@ -219,11 +141,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Hints -->
|
||||
<div id="mapHint" class="badge badge-primary badge-lg py-4 px-6 gap-2 shadow-xl hidden">
|
||||
<div id="mapHint" class="badge badge-primary badge-lg py-4 px-6 gap-2 hidden">
|
||||
<i class="fa-solid fa-mouse-pointer"></i>
|
||||
<span>Klik peta untuk menentukan lokasi baru</span>
|
||||
</div>
|
||||
<div id="dragHint" class="badge badge-warning badge-lg py-4 px-6 gap-2 shadow-xl hidden">
|
||||
<div id="dragHint" class="badge badge-warning badge-lg py-4 px-6 gap-2 hidden">
|
||||
<i class="fa-solid fa-arrows-up-down-left-right"></i>
|
||||
<span>Lepaskan untuk menyimpan lokasi baru</span>
|
||||
</div>
|
||||
@@ -855,7 +777,7 @@
|
||||
function buildPopupIbadah(item) {
|
||||
const canEditThis = canEditIbadah && (authUserRole === 'administrator' || item.user_id === authUserId);
|
||||
return `<div class="p-2 min-w-[200px]">
|
||||
<div class="font-bold text-base text-primary">🕌 ${item.nama}</div>
|
||||
<div class="font-bold text-base">${item.nama}</div>
|
||||
<div class="badge badge-sm badge-outline mb-2 capitalize">${item.jenis || 'Ibadah'}</div>
|
||||
<div class="text-[10px] text-gray-500 mb-1"><i class="fa-solid fa-location-dot"></i> ${item.alamat}</div>
|
||||
<div class="grid grid-cols-2 gap-2 my-2 text-[10px] bg-base-200 p-2 rounded">
|
||||
@@ -871,7 +793,7 @@
|
||||
|
||||
function buildPopupMiskin(item) {
|
||||
return `<div class="p-2 min-w-[200px]">
|
||||
<div class="font-bold text-base text-error">🏠 ${item.nama_kk || 'Keluarga Miskin'}</div>
|
||||
<div class="font-bold text-base">${item.nama_kk || 'Keluarga Miskin'}</div>
|
||||
<div class="text-[10px] text-gray-400 mb-1">ID: ${item.id_rumah}</div>
|
||||
<div class="text-[10px] text-gray-500 mb-1"><i class="fa-solid fa-location-dot"></i> ${item.alamat || '-'}</div>
|
||||
<div class="grid grid-cols-2 gap-2 my-2 text-[10px] bg-base-200 p-2 rounded">
|
||||
@@ -1132,14 +1054,31 @@
|
||||
}
|
||||
|
||||
// ── NOTIFIKASI PERUBAHAN DATA ────────────────────────────────────────
|
||||
function updateTransferBadge(count) {
|
||||
document.querySelectorAll('a[href$="/permohonan"]').forEach(link => {
|
||||
let badge = link.querySelector('.transfer-pending-badge');
|
||||
if (count > 0) {
|
||||
if (!badge) {
|
||||
badge = document.createElement('span');
|
||||
badge.className = 'badge badge-error badge-xs transfer-pending-badge';
|
||||
link.appendChild(document.createTextNode(' '));
|
||||
link.appendChild(badge);
|
||||
}
|
||||
badge.textContent = count;
|
||||
badge.classList.remove('hidden');
|
||||
} else if (badge) {
|
||||
badge.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
(function initNotifPolling() {
|
||||
const POLL_MS = 30000;
|
||||
const KEY_IBADAH = 'notif_latest_ibadah';
|
||||
const KEY_MISKIN = 'notif_latest_miskin';
|
||||
const KEY_SEEN = 'notif_latest_seen';
|
||||
|
||||
async function pollNotifications() {
|
||||
try {
|
||||
const since = localStorage.getItem(KEY_IBADAH) || new Date(0).toISOString();
|
||||
const since = localStorage.getItem(KEY_SEEN) || new Date(0).toISOString();
|
||||
const res = await fetch(
|
||||
`{{ url('/api/notifications') }}?since=${encodeURIComponent(since)}`,
|
||||
{ headers: { 'X-CSRF-TOKEN': csrfToken } }
|
||||
@@ -1147,23 +1086,31 @@
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
|
||||
if (data.new_ibadah > 0 || data.new_miskin > 0) {
|
||||
updateTransferBadge(Number(data.pending_transfer || 0));
|
||||
|
||||
const hasChanges = data.new_ibadah > 0 || data.new_miskin > 0 || data.new_anggota > 0 || data.new_bantuan > 0 || data.new_transfer > 0;
|
||||
if (hasChanges) {
|
||||
if (data.new_ibadah > 0) showToast(`${data.new_ibadah} rumah ibadah baru ditambahkan`, 'info');
|
||||
if (data.new_miskin > 0) showToast(`${data.new_miskin} keluarga miskin baru ditambahkan`, 'info');
|
||||
if (data.new_anggota > 0) showToast(`${data.new_anggota} anggota keluarga baru ditambahkan`, 'info');
|
||||
if (data.new_bantuan > 0) showToast(`${data.new_bantuan} bantuan baru dicatat`, 'info');
|
||||
if (data.new_transfer > 0) showToast(`${data.new_transfer} permohonan transfer baru`, 'info');
|
||||
const fn = typeof loadAllData !== 'undefined' ? loadAllData
|
||||
: typeof loadData !== 'undefined' ? loadData
|
||||
: typeof loadDashboard !== 'undefined' ? loadDashboard
|
||||
: null;
|
||||
if (fn) fn();
|
||||
}
|
||||
if (data.latest_ibadah) localStorage.setItem(KEY_IBADAH, data.latest_ibadah);
|
||||
if (data.latest_miskin) localStorage.setItem(KEY_MISKIN, data.latest_miskin);
|
||||
const latest = [data.latest_ibadah, data.latest_miskin, data.latest_anggota, data.latest_bantuan, data.latest_transfer]
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.pop();
|
||||
if (latest) localStorage.setItem(KEY_SEEN, latest);
|
||||
} catch (e) { /* silent */ }
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!localStorage.getItem(KEY_IBADAH)) localStorage.setItem(KEY_IBADAH, new Date().toISOString());
|
||||
if (!localStorage.getItem(KEY_MISKIN)) localStorage.setItem(KEY_MISKIN, new Date().toISOString());
|
||||
if (!localStorage.getItem(KEY_SEEN)) localStorage.setItem(KEY_SEEN, new Date().toISOString());
|
||||
setInterval(pollNotifications, POLL_MS);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user