feat: refresh UI and add sidebar navigation
This commit is contained in:
+30
-12
@@ -1,9 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id" data-theme="light">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebGIS Miskin & Ibadah</title>
|
||||
<title>WebGIS Pemetaan Kemiskinan</title>
|
||||
|
||||
<!-- Leaflet -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||
@@ -16,19 +17,31 @@
|
||||
<!-- Plus Jakarta Sans Font -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
||||
|
||||
<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;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value {
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.btn,
|
||||
.card-title,
|
||||
.stat-value {
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
#bgMap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -39,11 +52,13 @@
|
||||
opacity: 0.5;
|
||||
filter: saturate(1.2) brightness(0.7);
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Glass effect for content */
|
||||
.hero-content {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
@@ -53,14 +68,16 @@
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .hero-content {
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen bg-base-200 flex flex-col items-center justify-center overflow-hidden">
|
||||
|
||||
|
||||
<!-- Background Map -->
|
||||
<div id="bgMap"></div>
|
||||
|
||||
@@ -68,9 +85,9 @@
|
||||
<div class="hero-content text-center flex-col gap-2">
|
||||
<!-- Logo -->
|
||||
<div class="text-7xl mb-2">🏘️</div>
|
||||
<h1 class="text-4xl font-bold tracking-tight text-primary">WebGIS Miskin & Ibadah</h1>
|
||||
<h1 class="text-4xl font-bold tracking-tight text-primary">WebGIS Pemetaan Kemiskinan</h1>
|
||||
<p class="max-w-md text-base-content/60 mt-2 mb-6 leading-relaxed">
|
||||
Sistem Informasi Geografis untuk pemetaan rumah ibadah dan
|
||||
Sistem Informasi Geografis untuk pemetaan rumah ibadah dan
|
||||
analisis sebaran keluarga kurang mampu.
|
||||
</p>
|
||||
|
||||
@@ -99,7 +116,7 @@
|
||||
let val = 0;
|
||||
const bar = document.getElementById('redirectProgress');
|
||||
const tick = setInterval(() => {
|
||||
val += 100 / 50;
|
||||
val += 100 / 50;
|
||||
bar.value = val;
|
||||
if (val >= 100) clearInterval(tick);
|
||||
}, 100);
|
||||
@@ -133,7 +150,7 @@
|
||||
fetch('api/get_miskin.php').then(r => r.json())
|
||||
]).then(([ibadah, miskin]) => {
|
||||
const markers = [];
|
||||
|
||||
|
||||
ibadah.forEach(item => {
|
||||
L.circleMarker([+item.latitude, +item.longitude], {
|
||||
radius: 7, fillColor: "#22c55e", color: "#fff", weight: 2, opacity: 1, fillOpacity: 0.8
|
||||
@@ -151,14 +168,15 @@
|
||||
if (markers.length) {
|
||||
map.fitBounds(markers, { padding: [50, 50] });
|
||||
}
|
||||
|
||||
|
||||
// Subtle animation: slow pan
|
||||
let lat = map.getCenter().lat;
|
||||
setInterval(() => {
|
||||
lat += 0.000005;
|
||||
map.panTo([lat, map.getCenter().lng], { animate: true, duration: 1 });
|
||||
}, 1000);
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
+38
-13
@@ -1,9 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id" data-theme="light">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Data Tabular - WebGIS Miskin & Ibadah</title>
|
||||
<title>Data Tabular - WebGIS Pemetaan Kemiskinan</title>
|
||||
|
||||
<!-- DaisyUI + Tailwind -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
|
||||
@@ -13,14 +14,34 @@
|
||||
<!-- Plus Jakarta Sans Font -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
||||
<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; background-color: #f8fafc; }
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
* {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-weight: 500;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.btn,
|
||||
.card-title,
|
||||
.stat-value {
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen">
|
||||
|
||||
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
||||
@@ -28,20 +49,21 @@
|
||||
<div class="flex-1">
|
||||
<a href="../index.html" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
||||
<i class="fa-solid fa-house-chimney-crack"></i>
|
||||
WebGIS Miskin & Ibadah
|
||||
WebGIS Pemetaan Kemiskinan
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<ul class="menu menu-horizontal px-1 gap-1">
|
||||
<li><a href="map.html" class="font-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
||||
<li><a href="data.html" class="font-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-table-list"></i> Data Tabular</a></li>
|
||||
<li><a href="data.html" class="font-semibold bg-primary/10 text-primary rounded-lg"><i
|
||||
class="fa-solid fa-table-list"></i> Data Tabular</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ MAIN CONTENT ════════════════════════════════════════════════════ -->
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||
<div>
|
||||
@@ -52,7 +74,8 @@
|
||||
<p class="text-base-content/50 mt-1">Daftar rumah ibadah dan keluarga miskin dalam matriks tabular.</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="map.html" class="btn btn-primary shadow-lg"><i class="fa-solid fa-plus"></i> Tambah Data di Peta</a>
|
||||
<a href="map.html" class="btn btn-primary shadow-lg"><i class="fa-solid fa-plus"></i> Tambah Data di
|
||||
Peta</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +100,8 @@
|
||||
|
||||
<!-- Tabs -->
|
||||
<div role="tablist" class="tabs tabs-lifted tabs-lg mb-6">
|
||||
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🕌 Rumah Ibadah" checked />
|
||||
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🕌 Rumah Ibadah"
|
||||
checked />
|
||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full" id="tableIbadah">
|
||||
@@ -189,8 +213,8 @@
|
||||
|
||||
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
||||
const { type, id } = deleteTarget;
|
||||
const res = await fetch(`../api/delete_${type}.php`, {
|
||||
method: 'POST', body: JSON.stringify(type === 'ibadah' ? { id } : { id_rumah: id })
|
||||
const res = await fetch(`../api/delete_${type}.php`, {
|
||||
method: 'POST', body: JSON.stringify(type === 'ibadah' ? { id } : { id_rumah: id })
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson.success) {
|
||||
@@ -210,4 +234,5 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
+104
-62
@@ -1,9 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id" data-theme="light">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Peta Interaktif - WebGIS Miskin & Ibadah</title>
|
||||
<title>Peta Interaktif - WebGIS Pemetaan Kemiskinan</title>
|
||||
|
||||
<!-- Leaflet -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||
@@ -18,15 +19,46 @@
|
||||
<!-- Plus Jakarta Sans Font -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
||||
<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; }
|
||||
.map-wrapper { flex: 1; position: relative; min-height: 0; }
|
||||
#map { width: 100%; height: 100%; }
|
||||
|
||||
* {
|
||||
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;
|
||||
}
|
||||
|
||||
.map-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Floating Controls */
|
||||
.floating-panel {
|
||||
position: absolute;
|
||||
@@ -37,7 +69,7 @@
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
@@ -46,7 +78,8 @@
|
||||
}
|
||||
|
||||
/* Marker dragging hint */
|
||||
#dragHint, #mapHint {
|
||||
#dragHint,
|
||||
#mapHint {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
@@ -65,6 +98,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
||||
@@ -72,7 +106,7 @@
|
||||
<div class="flex-1">
|
||||
<a href="../index.html" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
||||
<i class="fa-solid fa-house-chimney-crack"></i>
|
||||
WebGIS Miskin & Ibadah
|
||||
WebGIS Pemetaan Kemiskinan
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
@@ -104,13 +138,16 @@
|
||||
Mode Operasi
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<button class="btn btn-sm btn-outline btn-primary justify-start gap-2 active" id="btnModeView" onclick="setMode('view')">
|
||||
<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 btn-success 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 btn-error justify-start gap-2" id="btnModeMiskin"
|
||||
onclick="setMode('miskin')">
|
||||
<i class="fa-solid fa-plus"></i> Tambah Rumah Miskin
|
||||
</button>
|
||||
</div>
|
||||
@@ -130,12 +167,13 @@
|
||||
<div class="text-xs font-bold text-primary truncate" id="activeName">Nama Ibadah</div>
|
||||
<div class="text-[10px] text-base-content/40" id="activeAddr">Alamat</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-control mb-4">
|
||||
<label class="label py-1">
|
||||
<span class="label-text-alt font-bold">Radius: <span id="radiusVal">500</span>m</span>
|
||||
</label>
|
||||
<input type="range" min="50" max="2000" value="500" step="50" class="range range-xs range-primary" id="radiusSlider">
|
||||
<input type="range" min="50" max="2000" value="500" step="50"
|
||||
class="range range-xs range-primary" id="radiusSlider">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
@@ -168,7 +206,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ═══ MODALS ═══════════════════════════════════════════════════════════ -->
|
||||
|
||||
|
||||
<!-- Modal: Input Ibadah -->
|
||||
<dialog id="modalIbadah" class="modal">
|
||||
<div class="modal-box">
|
||||
@@ -179,11 +217,13 @@
|
||||
<div class="bg-base-200 p-2 rounded text-xs font-mono" id="ib_coords">Lat: -, Lng: -</div>
|
||||
<label class="form-control w-full">
|
||||
<div class="label"><span class="label-text font-bold">Nama Rumah Ibadah</span></div>
|
||||
<input type="text" id="ib_nama" placeholder="Contoh: Masjid Nurul Iman" class="input input-bordered w-full" />
|
||||
<input type="text" id="ib_nama" placeholder="Contoh: Masjid Nurul Iman"
|
||||
class="input input-bordered w-full" />
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label"><span class="label-text font-bold">Alamat</span></div>
|
||||
<textarea id="ib_alamat" class="textarea textarea-bordered h-20" placeholder="Alamat lengkap..."></textarea>
|
||||
<textarea id="ib_alamat" class="textarea textarea-bordered h-20"
|
||||
placeholder="Alamat lengkap..."></textarea>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label"><span class="label-text font-bold">Radius Default Analysis (Meter)</span></div>
|
||||
@@ -207,7 +247,8 @@
|
||||
<div class="bg-base-200 p-2 rounded text-xs font-mono" id="rm_coords">Lat: -, Lng: -</div>
|
||||
<label class="form-control w-full">
|
||||
<div class="label"><span class="label-text font-bold">Alamat</span></div>
|
||||
<textarea id="rm_alamat" class="textarea textarea-bordered h-20" placeholder="Alamat lengkap..."></textarea>
|
||||
<textarea id="rm_alamat" class="textarea textarea-bordered h-20"
|
||||
placeholder="Alamat lengkap..."></textarea>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label"><span class="label-text font-bold">ID Rumah / Nama Pemilik</span></div>
|
||||
@@ -255,7 +296,7 @@
|
||||
let activeCircle = null, activeIbadah = null;
|
||||
let ibadahData = [], miskinData = [];
|
||||
let tempMarker = null;
|
||||
|
||||
|
||||
let ibadahMarkers = new Map(); // id -> marker
|
||||
let miskinMarkers = new Map(); // id_rumah -> marker
|
||||
|
||||
@@ -286,7 +327,7 @@
|
||||
|
||||
function initMap() {
|
||||
map = L.map('map', { zoomControl: false }).setView([-0.0553, 109.3463], 17);
|
||||
|
||||
|
||||
const osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 });
|
||||
const satellite = L.layerGroup([
|
||||
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { maxZoom: 19 }),
|
||||
@@ -296,9 +337,9 @@
|
||||
|
||||
satellite.addTo(map);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
L.control.layers({ "Satelit": satellite, "Street": osm },
|
||||
{ "Rumah Ibadah": layerIbadah, "Rumah Miskin": layerMiskin },
|
||||
{ position: 'topright' }).addTo(map);
|
||||
L.control.layers({ "Satelit": satellite, "Street": osm },
|
||||
{ "Rumah Ibadah": layerIbadah, "Rumah Miskin": layerMiskin },
|
||||
{ position: 'topright' }).addTo(map);
|
||||
|
||||
layerIbadah.addTo(map);
|
||||
layerMiskin.addTo(map);
|
||||
@@ -315,7 +356,7 @@
|
||||
fetch('../api/get_ibadah.php').then(r => r.json()),
|
||||
fetch('../api/get_miskin.php').then(r => r.json())
|
||||
]);
|
||||
|
||||
|
||||
// Validate data is array
|
||||
if (!Array.isArray(ri) || !Array.isArray(rm)) {
|
||||
console.error("Invalid API response", { ri, rm });
|
||||
@@ -324,7 +365,7 @@
|
||||
|
||||
ibadahData = ri;
|
||||
miskinData = rm;
|
||||
|
||||
|
||||
// Sync activeIbadah to point to the new object in the fresh data
|
||||
if (activeIbadah) {
|
||||
const matched = ibadahData.find(i => i.id == activeIbadah.id);
|
||||
@@ -332,9 +373,9 @@
|
||||
}
|
||||
|
||||
renderMarkers();
|
||||
} catch (e) {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Gagal memuat data', 'error');
|
||||
showToast('Gagal memuat data', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,16 +389,16 @@
|
||||
const m = L.marker([item.latitude, item.longitude], { icon: icons.ibadah(), draggable: true })
|
||||
.bindPopup(buildPopupIbadah(item))
|
||||
.on('click', () => handleIbadahClick(item));
|
||||
|
||||
|
||||
m.on('dragend', e => updateLocation('ibadah', item.id, e.target.getLatLng(), item));
|
||||
layerIbadah.addLayer(m);
|
||||
ibadahMarkers.set(item.id, m);
|
||||
});
|
||||
|
||||
miskinData.forEach(item => {
|
||||
const marker = L.marker([item.latitude, item.longitude], {
|
||||
const marker = L.marker([item.latitude, item.longitude], {
|
||||
icon: icons.miskin('#ef4444'),
|
||||
draggable: true
|
||||
draggable: true
|
||||
}).bindPopup(buildPopupMiskin(item));
|
||||
|
||||
marker.on('dragend', e => updateLocation('miskin', item.id_rumah, e.target.getLatLng(), item));
|
||||
@@ -376,10 +417,10 @@
|
||||
currentMode = mode;
|
||||
document.querySelectorAll('.btn-outline').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('btnMode' + mode.charAt(0).toUpperCase() + mode.slice(1)).classList.add('active');
|
||||
|
||||
|
||||
document.getElementById('mapHint').classList.toggle('hidden', mode === 'view');
|
||||
document.getElementById('analysisPanel').classList.toggle('hidden', mode !== 'view');
|
||||
|
||||
|
||||
if (mode !== 'view') {
|
||||
if (activeCircle) { map.removeLayer(activeCircle); activeCircle = null; }
|
||||
document.getElementById('statsContent').classList.add('hidden');
|
||||
@@ -391,13 +432,13 @@
|
||||
function handleMapClick(latlng) {
|
||||
if (tempMarker) map.removeLayer(tempMarker);
|
||||
tempMarker = L.marker(latlng, { icon: icons.temp() }).addTo(map);
|
||||
|
||||
|
||||
if (currentMode === 'ibadah') {
|
||||
document.getElementById('ib_coords').innerText = `Lat: ${latlng.lat.toFixed(6)}, Lng: ${latlng.lng.toFixed(6)}`;
|
||||
document.getElementById('ib_nama').value = '';
|
||||
document.getElementById('ib_alamat').value = 'Mencari alamat...';
|
||||
modalIbadah.showModal();
|
||||
|
||||
|
||||
// Auto fetch address
|
||||
reverseGeocode(latlng.lat, latlng.lng, 'ib_alamat');
|
||||
} else {
|
||||
@@ -432,7 +473,7 @@
|
||||
document.getElementById('statsContent').classList.remove('hidden');
|
||||
document.getElementById('activeName').innerText = item.nama;
|
||||
document.getElementById('activeAddr').innerText = item.alamat;
|
||||
|
||||
|
||||
const radius = document.getElementById('radiusSlider').value = item.radius || 500;
|
||||
updateRadius(item.latitude, item.longitude, radius);
|
||||
}
|
||||
@@ -441,25 +482,25 @@
|
||||
function updateRadius(lat, lng, radius) {
|
||||
if (activeCircle) map.removeLayer(activeCircle);
|
||||
activeCircle = L.circle([lat, lng], {
|
||||
radius: parseInt(radius),
|
||||
color: '#2563eb',
|
||||
fillColor: '#3b82f6',
|
||||
fillOpacity: 0.2,
|
||||
radius: parseInt(radius),
|
||||
color: '#2563eb',
|
||||
fillColor: '#3b82f6',
|
||||
fillOpacity: 0.2,
|
||||
weight: 2,
|
||||
dashArray: '5, 10'
|
||||
}).addTo(map);
|
||||
|
||||
|
||||
document.getElementById('radiusVal').innerText = radius;
|
||||
updateAnalysisUI([lat, lng], radius);
|
||||
}
|
||||
|
||||
function updateAnalysisUI(center, radius) {
|
||||
let houses = 0, kk = 0, jiwa = 0;
|
||||
|
||||
|
||||
miskinData.forEach(h => {
|
||||
const houseMarker = miskinMarkers.get(h.id_rumah);
|
||||
const isH = isInside(h, center, radius);
|
||||
|
||||
|
||||
if (isH) {
|
||||
houses++;
|
||||
kk += parseInt(h.jumlah_kk);
|
||||
@@ -484,7 +525,7 @@
|
||||
// center can be [lat, lng] or {lat, lng}
|
||||
const cLat = center.lat !== undefined ? center.lat : center[0];
|
||||
const cLng = center.lng !== undefined ? center.lng : center[1];
|
||||
|
||||
|
||||
const p1 = turf.point([Number(point.longitude), Number(point.latitude)]);
|
||||
const p2 = turf.point([Number(cLng), Number(cLat)]);
|
||||
return turf.distance(p1, p2, { units: 'meters' }) <= radius;
|
||||
@@ -499,7 +540,7 @@
|
||||
// Save radius to DB when user finishes sliding
|
||||
document.getElementById('radiusSlider').addEventListener('change', async e => {
|
||||
if (!activeIbadah) return;
|
||||
|
||||
|
||||
const newRadius = e.target.value;
|
||||
try {
|
||||
const res = await fetch('../api/update_ibadah.php', {
|
||||
@@ -559,11 +600,11 @@
|
||||
};
|
||||
const btn = document.getElementById('btnSaveIbadah');
|
||||
btn.disabled = true;
|
||||
|
||||
const res = await fetch('../api/save_ibadah.php', {
|
||||
method: 'POST',
|
||||
|
||||
const res = await fetch('../api/save_ibadah.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson.success) {
|
||||
@@ -586,10 +627,10 @@
|
||||
const btn = document.getElementById('btnSaveMiskin');
|
||||
btn.disabled = true;
|
||||
|
||||
const res = await fetch('../api/save_miskin.php', {
|
||||
method: 'POST',
|
||||
const res = await fetch('../api/save_miskin.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson.success) {
|
||||
@@ -602,15 +643,15 @@
|
||||
|
||||
async function updateLocation(type, id, latlng, oldData) {
|
||||
const endpoint = `../api/update_${type}.php`;
|
||||
const payload = type === 'ibadah'
|
||||
const payload = type === 'ibadah'
|
||||
? { id, nama: oldData.nama, alamat: oldData.alamat, latitude: latlng.lat, longitude: latlng.lng, radius: oldData.radius }
|
||||
: { id_rumah: id, alamat: oldData.alamat, jumlah_kk: oldData.jumlah_kk, jumlah_orang: oldData.jumlah_orang, latitude: latlng.lat, longitude: latlng.lng };
|
||||
|
||||
|
||||
try {
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson.success) {
|
||||
@@ -635,10 +676,10 @@
|
||||
|
||||
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
||||
const { type, id } = deleteTarget;
|
||||
const res = await fetch(`../api/delete_${type}.php`, {
|
||||
method: 'POST',
|
||||
const res = await fetch(`../api/delete_${type}.php`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(type === 'ibadah' ? { id } : { id_rumah: id })
|
||||
body: JSON.stringify(type === 'ibadah' ? { id } : { id_rumah: id })
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson.success) {
|
||||
@@ -659,4 +700,5 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,788 @@
|
||||
@import url('https://api.fontshare.com/v2/css?f[]=general-sans@600,700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
|
||||
:root {
|
||||
--genesis-primary: #6366f1;
|
||||
--genesis-primary-hover: #4f46e5;
|
||||
--genesis-secondary: #20970b;
|
||||
--genesis-neutral: #9c9c9c;
|
||||
--genesis-bg: #fafafa;
|
||||
--genesis-surface: #ffffff;
|
||||
--genesis-text: #0a0a0a;
|
||||
--genesis-text-secondary: #6b6b6b;
|
||||
--genesis-border: #e8e8ec;
|
||||
--genesis-success: #10b981;
|
||||
--genesis-warning: #f59e0b;
|
||||
--genesis-error: #ef4444;
|
||||
--app-sidebar-width: 280px;
|
||||
--app-mobile-bar-height: 58px;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background: var(--genesis-bg) !important;
|
||||
color: var(--genesis-text);
|
||||
font-family: 'DM Sans', ui-sans-serif, system-ui, sans-serif !important;
|
||||
font-size: 15px;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
* {
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.card-title,
|
||||
.stat-value {
|
||||
color: var(--genesis-text);
|
||||
font-family: 'General Sans', 'DM Sans', ui-sans-serif, system-ui, sans-serif !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
.font-mono {
|
||||
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace !important;
|
||||
}
|
||||
|
||||
.text-primary,
|
||||
.btn-ghost.text-primary,
|
||||
.menu a.text-primary {
|
||||
color: var(--genesis-primary) !important;
|
||||
}
|
||||
|
||||
main h1 i.text-primary,
|
||||
main h2 i.text-primary,
|
||||
main h3 i.text-primary,
|
||||
.card h3 i.text-primary,
|
||||
.modal-box h3 i.text-primary,
|
||||
.stat-figure.text-success,
|
||||
.stat-figure.text-warning,
|
||||
.stat-figure.text-error,
|
||||
.stat-figure.text-info {
|
||||
color: var(--genesis-text) !important;
|
||||
}
|
||||
|
||||
.text-secondary,
|
||||
.text-info {
|
||||
color: var(--genesis-text-secondary) !important;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: var(--genesis-success) !important;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: var(--genesis-warning) !important;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: var(--genesis-error) !important;
|
||||
}
|
||||
|
||||
.bg-primary,
|
||||
.badge-primary {
|
||||
background-color: var(--genesis-primary) !important;
|
||||
border-color: var(--genesis-primary) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.bg-base-100,
|
||||
.bg-white {
|
||||
background-color: var(--genesis-surface) !important;
|
||||
}
|
||||
|
||||
.bg-base-200,
|
||||
.bg-base-200\/50 {
|
||||
background-color: var(--genesis-bg) !important;
|
||||
}
|
||||
|
||||
.border-base-200,
|
||||
.border-base-300,
|
||||
.border-base-content\/10 {
|
||||
border-color: var(--genesis-border) !important;
|
||||
}
|
||||
|
||||
.text-base-content\/50,
|
||||
.text-base-content\/60,
|
||||
.opacity-50 {
|
||||
color: var(--genesis-text-secondary) !important;
|
||||
}
|
||||
|
||||
.shadow,
|
||||
.shadow-sm,
|
||||
.shadow-lg,
|
||||
.shadow-xl,
|
||||
.shadow-2xl {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.rounded-box,
|
||||
.rounded-lg,
|
||||
.rounded-xl,
|
||||
.rounded-2xl,
|
||||
.rounded-3xl,
|
||||
.modal-box,
|
||||
.card,
|
||||
.stat,
|
||||
.tab-content {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.app-sidebar-toggle {
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-mobile-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar {
|
||||
position: fixed;
|
||||
inset: 0 auto 0 0;
|
||||
z-index: 1200;
|
||||
width: var(--app-sidebar-width);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
padding: 18px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border-right: 1px solid var(--genesis-border);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.app-sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-sidebar-brand,
|
||||
.app-mobile-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
gap: 10px;
|
||||
color: var(--genesis-text) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.app-brand-icon {
|
||||
display: grid;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
border: 1px solid var(--genesis-border);
|
||||
border-radius: 8px;
|
||||
background: var(--genesis-surface);
|
||||
color: var(--genesis-text);
|
||||
}
|
||||
|
||||
.app-brand-copy,
|
||||
.app-user-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-brand-title,
|
||||
.app-user-name {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: var(--genesis-text);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-brand-subtitle,
|
||||
.app-user-role {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: var(--genesis-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-sidebar-close {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.app-sidebar-nav {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
overflow-y: auto;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.app-nav-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app-nav-section-title {
|
||||
padding: 0 10px 4px;
|
||||
color: var(--genesis-neutral);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.app-nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 42px;
|
||||
padding: 0 11px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
color: var(--genesis-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
|
||||
}
|
||||
|
||||
.app-nav-link i {
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-nav-link span:not(.badge) {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-nav-link:hover {
|
||||
background: #f3f3f5;
|
||||
color: var(--genesis-text);
|
||||
}
|
||||
|
||||
.app-nav-link.is-active {
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
border-color: rgba(99, 102, 241, 0.2);
|
||||
color: var(--genesis-primary);
|
||||
}
|
||||
|
||||
.app-nav-badge {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.app-sidebar-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
border-top: 1px solid var(--genesis-border);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.app-user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.app-logout-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 38px;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--genesis-border);
|
||||
border-radius: 8px;
|
||||
background: var(--genesis-surface);
|
||||
color: var(--genesis-error);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-logout-button:hover {
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
border-color: rgba(239, 68, 68, 0.22);
|
||||
}
|
||||
|
||||
.app-main {
|
||||
min-height: 100vh;
|
||||
margin-left: var(--app-sidebar-width);
|
||||
}
|
||||
|
||||
.app-map-body {
|
||||
height: 100vh !important;
|
||||
display: block !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.map-wrapper.app-map-main {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
margin-left: var(--app-sidebar-width);
|
||||
}
|
||||
|
||||
.app-sidebar-backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu.menu-horizontal {
|
||||
gap: 4px !important;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
justify-content: center;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.menu.menu-horizontal::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu a,
|
||||
.dropdown-content .menu a {
|
||||
border-radius: 6px !important;
|
||||
color: var(--genesis-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500 !important;
|
||||
white-space: nowrap;
|
||||
flex-wrap: nowrap !important;
|
||||
}
|
||||
|
||||
.menu a:hover,
|
||||
.btn-ghost:hover {
|
||||
background: #f3f3f5 !important;
|
||||
color: var(--genesis-text) !important;
|
||||
}
|
||||
|
||||
.menu a.bg-primary\/10,
|
||||
.menu a.font-semibold {
|
||||
background: rgba(99, 102, 241, 0.1) !important;
|
||||
color: var(--genesis-primary) !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: 38px;
|
||||
border-radius: 6px !important;
|
||||
font-family: 'DM Sans', ui-sans-serif, system-ui, sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 600 !important;
|
||||
text-transform: none !important;
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease, border-color 160ms ease, color 160ms ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--genesis-primary) !important;
|
||||
border-color: var(--genesis-primary) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--genesis-primary-hover) !important;
|
||||
border-color: var(--genesis-primary-hover) !important;
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35) !important;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent !important;
|
||||
border-color: var(--genesis-border) !important;
|
||||
color: var(--genesis-text) !important;
|
||||
}
|
||||
|
||||
.btn-outline:hover,
|
||||
.btn-active {
|
||||
background: #f3f3f5 !important;
|
||||
border-color: var(--genesis-primary) !important;
|
||||
color: var(--genesis-primary) !important;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--genesis-success) !important;
|
||||
border-color: var(--genesis-success) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-warning,
|
||||
.btn-info {
|
||||
background: transparent !important;
|
||||
border-color: var(--genesis-border) !important;
|
||||
color: var(--genesis-text) !important;
|
||||
}
|
||||
|
||||
.btn-warning:hover,
|
||||
.btn-info:hover {
|
||||
background: rgba(99, 102, 241, 0.1) !important;
|
||||
border-color: rgba(99, 102, 241, 0.2) !important;
|
||||
color: var(--genesis-primary) !important;
|
||||
}
|
||||
|
||||
.btn-error {
|
||||
background: var(--genesis-error) !important;
|
||||
border-color: var(--genesis-error) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.card,
|
||||
.stat,
|
||||
.modal-box,
|
||||
.tab-content,
|
||||
.dropdown-content,
|
||||
.table-shell,
|
||||
.glass-card,
|
||||
.map-panel {
|
||||
background: var(--genesis-surface) !important;
|
||||
border: 1px solid var(--genesis-border) !important;
|
||||
}
|
||||
|
||||
.card,
|
||||
.stat,
|
||||
.tab-content,
|
||||
.table-shell,
|
||||
.glass-card,
|
||||
.map-panel {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.stat:hover,
|
||||
.table-shell:hover {
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.card,
|
||||
.stat,
|
||||
.table-shell {
|
||||
transition: transform 200ms ease, box-shadow 200ms ease;
|
||||
}
|
||||
|
||||
.stat {
|
||||
padding: 20px !important;
|
||||
}
|
||||
|
||||
.stat-figure {
|
||||
color: var(--genesis-text-secondary) !important;
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
color: var(--genesis-neutral) !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: 0 !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px !important;
|
||||
line-height: 1.1 !important;
|
||||
color: var(--genesis-text) !important;
|
||||
}
|
||||
|
||||
.input,
|
||||
.select,
|
||||
.textarea {
|
||||
min-height: 38px;
|
||||
border-radius: 6px !important;
|
||||
border-color: var(--genesis-border) !important;
|
||||
background: var(--genesis-surface) !important;
|
||||
color: var(--genesis-text) !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.select:focus,
|
||||
.textarea:focus {
|
||||
border-color: var(--genesis-primary) !important;
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12) !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: var(--genesis-text);
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.badge {
|
||||
border-radius: 9999px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: rgba(16, 185, 129, 0.12) !important;
|
||||
border-color: rgba(16, 185, 129, 0.25) !important;
|
||||
color: #047857 !important;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: rgba(245, 158, 11, 0.14) !important;
|
||||
border-color: rgba(245, 158, 11, 0.28) !important;
|
||||
color: #92400e !important;
|
||||
}
|
||||
|
||||
.badge-error {
|
||||
background: rgba(239, 68, 68, 0.12) !important;
|
||||
border-color: rgba(239, 68, 68, 0.25) !important;
|
||||
color: #b91c1c !important;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: var(--genesis-text);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table thead tr {
|
||||
border-color: var(--genesis-border);
|
||||
}
|
||||
|
||||
.table th {
|
||||
color: var(--genesis-neutral);
|
||||
font-size: 11px;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.table td,
|
||||
.table th {
|
||||
border-color: var(--genesis-border) !important;
|
||||
padding-block: 12px !important;
|
||||
}
|
||||
|
||||
.table-zebra tbody tr:nth-child(even) {
|
||||
background: #f7f7f8 !important;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border-radius: 6px !important;
|
||||
color: var(--genesis-text-secondary) !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.tab:is(:checked),
|
||||
.tab-active {
|
||||
color: var(--genesis-primary) !important;
|
||||
}
|
||||
|
||||
.genesis-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.genesis-tab-list {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
gap: 8px;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--genesis-border);
|
||||
border-radius: 8px;
|
||||
background: var(--genesis-surface);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.genesis-tab-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 148px;
|
||||
height: 38px;
|
||||
padding: 0 16px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: var(--genesis-text-secondary);
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
|
||||
}
|
||||
|
||||
.genesis-tab-button:hover {
|
||||
background: #f3f3f5;
|
||||
color: var(--genesis-text);
|
||||
}
|
||||
|
||||
.genesis-tab-button.is-active {
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
border-color: rgba(99, 102, 241, 0.2);
|
||||
color: var(--genesis-primary);
|
||||
}
|
||||
|
||||
.genesis-tab-panel {
|
||||
border: 1px solid var(--genesis-border);
|
||||
border-radius: 8px;
|
||||
background: var(--genesis-surface);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-box {
|
||||
box-shadow: 0 18px 55px rgba(0, 0, 0, 0.14) !important;
|
||||
}
|
||||
|
||||
.modal-box .text-5xl {
|
||||
font-size: 32px !important;
|
||||
line-height: 1.1 !important;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 8px !important;
|
||||
border: 1px solid var(--genesis-border) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.toast .alert {
|
||||
background: var(--genesis-surface) !important;
|
||||
color: var(--genesis-text) !important;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
border-radius: 9999px !important;
|
||||
border-color: #d4d4d8 !important;
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
background-color: var(--genesis-primary) !important;
|
||||
border-color: var(--genesis-primary) !important;
|
||||
}
|
||||
|
||||
.leaflet-control-layers,
|
||||
.leaflet-control-zoom a {
|
||||
border-color: var(--genesis-border) !important;
|
||||
box-shadow: none !important;
|
||||
font-family: 'DM Sans', ui-sans-serif, system-ui, sans-serif !important;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.genesis-overline {
|
||||
color: var(--genesis-neutral);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.genesis-section-title {
|
||||
color: var(--genesis-text);
|
||||
font-family: 'General Sans', 'DM Sans', ui-sans-serif, system-ui, sans-serif;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.app-mobile-bar {
|
||||
position: fixed;
|
||||
inset: 0 0 auto 0;
|
||||
z-index: 1150;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--app-mobile-bar-height);
|
||||
padding: 0 14px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border-bottom: 1px solid var(--genesis-border);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.app-mobile-brand {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.app-sidebar {
|
||||
width: min(82vw, var(--app-sidebar-width));
|
||||
transform: translateX(-100%);
|
||||
transition: transform 200ms ease;
|
||||
}
|
||||
|
||||
.app-sidebar-close {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
|
||||
.app-sidebar-toggle:checked ~ .app-sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.app-sidebar-toggle:checked ~ .app-sidebar-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1190;
|
||||
display: block;
|
||||
background: rgba(10, 10, 10, 0.36);
|
||||
}
|
||||
|
||||
.app-main {
|
||||
margin-left: 0;
|
||||
padding-top: var(--app-mobile-bar-height);
|
||||
}
|
||||
|
||||
.map-wrapper.app-map-main {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.floating-panel {
|
||||
top: calc(var(--app-mobile-bar-height) + 14px);
|
||||
left: 12px;
|
||||
width: min(316px, calc(100vw - 24px));
|
||||
max-height: calc(100vh - var(--app-mobile-bar-height) - 32px);
|
||||
}
|
||||
|
||||
.app-map-body .leaflet-top {
|
||||
top: var(--app-mobile-bar-height);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stat-value {
|
||||
font-size: 26px !important;
|
||||
}
|
||||
|
||||
.genesis-tabs {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.genesis-tabs > .tab {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
@@ -15,38 +15,13 @@
|
||||
body { font-weight: 500; background-color: #f8fafc; }
|
||||
h1, h2, h3, .btn { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||
<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-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
||||
<li><a href="{{ url('/data') }}" class="font-medium"><i class="fa-solid fa-table-list"></i> Data</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>
|
||||
<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-semibold bg-primary/10 text-primary rounded-lg"><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>
|
||||
</ul>
|
||||
<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>
|
||||
</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>
|
||||
<body class="min-h-screen app-body">
|
||||
@include('partials.sidebar')
|
||||
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<main class="app-main">
|
||||
<div class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold flex items-center gap-3"><i class="fa-solid fa-clock-rotate-left text-primary"></i> Log Aktivitas</h1>
|
||||
<p class="text-base-content/50 mt-1">200 aktivitas terakhir pengguna sistem.</p>
|
||||
@@ -97,6 +72,7 @@
|
||||
<tbody id="bodyLogs"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
|
||||
|
||||
@@ -14,38 +14,13 @@
|
||||
body { font-weight: 500; background-color: #f8fafc; }
|
||||
h1, h2, h3, .btn { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||
<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-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
||||
<li><a href="{{ url('/data') }}" class="font-medium"><i class="fa-solid fa-table-list"></i> Data</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>
|
||||
<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-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-sliders"></i> Pengaturan</a></li>
|
||||
</ul>
|
||||
<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>
|
||||
</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>
|
||||
<body class="min-h-screen app-body">
|
||||
@include('partials.sidebar')
|
||||
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<main class="app-main">
|
||||
<div class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold flex items-center gap-3"><i class="fa-solid fa-sliders text-primary"></i> Konfigurasi Sistem</h1>
|
||||
<p class="text-base-content/50 mt-1">Atur parameter peta dan analisis. Perubahan berlaku setelah halaman peta di-refresh.</p>
|
||||
@@ -54,6 +29,7 @@
|
||||
<div id="settingsForm" class="bg-white rounded-2xl shadow-sm border border-base-200 p-6 max-w-2xl">
|
||||
<p class="text-center py-8 opacity-30 italic" id="settingsLoading">Memuat pengaturan...</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
|
||||
|
||||
@@ -16,82 +16,15 @@
|
||||
body { font-weight: 500; background-color: #f8fafc; }
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<body class="min-h-screen app-body">
|
||||
|
||||
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||
<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-medium">
|
||||
<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>
|
||||
<li>
|
||||
<a href="{{ url('/admin/users') }}" class="font-semibold bg-primary/10 text-primary rounded-lg">
|
||||
<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>
|
||||
</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')
|
||||
|
||||
<!-- ═══ MAIN CONTENT ════════════════════════════════════════════════════ -->
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<main class="app-main">
|
||||
<div class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||
@@ -159,6 +92,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ═══ MODALS ═══════════════════════════════════════════════════════════ -->
|
||||
|
||||
@@ -20,44 +20,60 @@
|
||||
<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; }
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value {
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
#bgMap {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
z-index: 0;
|
||||
opacity: 0.5;
|
||||
filter: saturate(1.2) brightness(0.7);
|
||||
opacity: 0.62;
|
||||
filter: saturate(0.82) brightness(0.96) contrast(0.9);
|
||||
}
|
||||
.login-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.2);
|
||||
.login-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(250, 250, 250, 0.96) 0%, rgba(250, 250, 250, 0.86) 45%, rgba(250, 250, 250, 0.38) 100%),
|
||||
linear-gradient(180deg, rgba(250, 250, 250, 0.2) 0%, rgba(250, 250, 250, 0.88) 100%);
|
||||
}
|
||||
.login-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8ec;
|
||||
border-radius: 8px;
|
||||
padding: 32px;
|
||||
}
|
||||
.login-mark {
|
||||
display: inline-grid;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
place-items: center;
|
||||
border: 1px solid #e8e8ec;
|
||||
border-radius: 8px;
|
||||
color: #0a0a0a;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen bg-base-200 flex items-center justify-center overflow-hidden p-4">
|
||||
<body class="min-h-screen flex items-center justify-center overflow-hidden p-4">
|
||||
|
||||
<!-- Background Map -->
|
||||
<div id="bgMap"></div>
|
||||
<div class="login-backdrop"></div>
|
||||
|
||||
<div class="login-container w-full max-w-sm">
|
||||
<div class="glass-card rounded-3xl p-8">
|
||||
<div class="login-card">
|
||||
|
||||
<div class="text-center mb-7">
|
||||
<div class="text-5xl mb-3">🏘️</div>
|
||||
<h1 class="text-xl font-bold text-primary leading-tight">WebGIS Pemetaan Kemiskinan</h1>
|
||||
<p class="text-sm text-base-content/50 mt-1">Masuk ke akun Anda untuk melanjutkan</p>
|
||||
<div class="login-mark mx-auto mb-4">GIS</div>
|
||||
<p class="genesis-overline mb-2">Akses operator</p>
|
||||
<h1 class="text-2xl font-bold leading-tight">WebGIS Pemetaan Kemiskinan</h1>
|
||||
<p class="text-sm text-base-content/50 mt-2">Masuk untuk mengelola data spasial dan tabular.</p>
|
||||
</div>
|
||||
|
||||
@if($errors->any())
|
||||
|
||||
@@ -18,84 +18,15 @@
|
||||
body { font-weight: 500; background-color: #f8fafc; }
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<body class="min-h-screen app-body">
|
||||
|
||||
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||
<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-medium">
|
||||
<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-semibold bg-primary/10 text-primary rounded-lg">
|
||||
<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')
|
||||
|
||||
<!-- ═══ MAIN CONTENT ════════════════════════════════════════════════════ -->
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<main class="app-main">
|
||||
<div class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
@@ -131,6 +62,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fitur Dosen Stats -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
||||
<div class="stat-figure text-secondary text-3xl"><i class="fa-solid fa-people-roof"></i></div>
|
||||
<div class="stat-title text-xs uppercase font-bold opacity-50">Anggota Keluarga</div>
|
||||
<div class="stat-value text-secondary" id="statTotalAnggota">-</div>
|
||||
<div class="stat-desc" id="statAnggotaDesc">hidup / meninggal</div>
|
||||
</div>
|
||||
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
||||
<div class="stat-figure text-accent text-3xl"><i class="fa-solid fa-graduation-cap"></i></div>
|
||||
<div class="stat-title text-xs uppercase font-bold opacity-50">Pendidikan Terdata</div>
|
||||
<div class="stat-value text-accent" id="statPendidikanTerdata">-</div>
|
||||
<div class="stat-desc">anggota dengan pendidikan</div>
|
||||
</div>
|
||||
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
||||
<div class="stat-figure text-success text-3xl"><i class="fa-solid fa-hand-holding-heart"></i></div>
|
||||
<div class="stat-title text-xs uppercase font-bold opacity-50">Bantuan Bulan Ini</div>
|
||||
<div class="stat-value text-success" id="statBantuanBulanIni">-</div>
|
||||
<div class="stat-desc" id="statBantuanTotal">dari total bantuan</div>
|
||||
</div>
|
||||
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
||||
<div class="stat-figure text-error text-3xl"><i class="fa-solid fa-right-left"></i></div>
|
||||
<div class="stat-title text-xs uppercase font-bold opacity-50">Transfer Pending</div>
|
||||
<div class="stat-value text-error" id="statTransferPending">-</div>
|
||||
<div class="stat-desc" id="statTransferDesc">permohonan aktif</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-base-200 p-6">
|
||||
@@ -143,6 +102,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fitur Dosen Charts -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-base-200 p-6">
|
||||
<h2 class="text-sm font-bold uppercase opacity-50 mb-4">Pendidikan Anggota</h2>
|
||||
<canvas id="chartPendidikan" height="220"></canvas>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-base-200 p-6">
|
||||
<h2 class="text-sm font-bold uppercase opacity-50 mb-4">Bantuan per Jenis</h2>
|
||||
<canvas id="chartBantuanJenis" height="220"></canvas>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-base-200 p-6">
|
||||
<h2 class="text-sm font-bold uppercase opacity-50 mb-4">Status Transfer</h2>
|
||||
<canvas id="chartTransfer" height="220"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gap Analysis Section -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-base-200 p-6 mb-8">
|
||||
<h2 class="text-lg font-bold flex items-center gap-2 mb-6">
|
||||
@@ -210,6 +185,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Toast Container -->
|
||||
@@ -231,19 +207,22 @@
|
||||
return turf.distance(p1, p2, { units: 'meters' }) <= radius;
|
||||
}
|
||||
|
||||
let ibadahData = [], miskinData = [];
|
||||
let ibadahData = [], miskinData = [], dosenSummary = null;
|
||||
let chartJenis = null, chartKondisi = null, chartGap = null;
|
||||
let chartPendidikan = null, chartBantuanJenis = null, chartTransfer = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadDashboard);
|
||||
|
||||
async function loadDashboard() {
|
||||
try {
|
||||
const [ri, rm] = await Promise.all([
|
||||
const [ri, rm, ds] = await Promise.all([
|
||||
fetch('{{ url('/api/ibadah') }}').then(r => r.json()),
|
||||
fetch('{{ url('/api/miskin') }}').then(r => r.json()),
|
||||
fetch('{{ url('/api/dashboard/dosen-summary') }}').then(r => r.json()),
|
||||
]);
|
||||
ibadahData = ri;
|
||||
miskinData = rm;
|
||||
dosenSummary = ds;
|
||||
renderDashboard();
|
||||
} catch (e) {
|
||||
showToast('Gagal memuat data', 'error');
|
||||
@@ -256,6 +235,7 @@
|
||||
document.getElementById('statTotalIbadah').textContent = ibadahData.length;
|
||||
document.getElementById('statTotalMiskin').textContent = miskinData.length;
|
||||
document.getElementById('statTotalJiwa').textContent = totalJiwa;
|
||||
renderDosenSummary();
|
||||
|
||||
// ── 2. GAP ANALYSIS ──────────────────────────────────────
|
||||
const coveredSet = new Set();
|
||||
@@ -388,7 +368,7 @@
|
||||
<td class="capitalize text-xs">${esc(m.kondisi_rumah || '-')}</td>
|
||||
<td class="text-center">${m.jumlah_orang || 0}</td>
|
||||
</tr>`).join('')
|
||||
: '<tr><td colspan="5" class="text-center py-6 text-success font-semibold">Semua keluarga miskin terjangkau! 🎉</td></tr>';
|
||||
: '<tr><td colspan="5" class="text-center py-6 text-success font-semibold">Semua keluarga miskin terjangkau.</td></tr>';
|
||||
}
|
||||
|
||||
function renderOrUpdateChart(canvasId, existingChart, config, setter) {
|
||||
@@ -397,15 +377,125 @@
|
||||
setter(new Chart(ctx, config));
|
||||
}
|
||||
|
||||
function renderDosenSummary() {
|
||||
if (!dosenSummary) return;
|
||||
|
||||
const anggota = dosenSummary.anggota || {};
|
||||
const bantuan = dosenSummary.bantuan || {};
|
||||
const transfer = dosenSummary.transfer || {};
|
||||
const statusHidup = anggota.status_hidup || {};
|
||||
const pendidikan = anggota.pendidikan || {};
|
||||
|
||||
const pendidikanTerdata = Object.entries(pendidikan)
|
||||
.filter(([key]) => key !== 'tidak_diketahui')
|
||||
.reduce((total, [, value]) => total + Number(value || 0), 0);
|
||||
|
||||
document.getElementById('statTotalAnggota').textContent = anggota.total || 0;
|
||||
document.getElementById('statAnggotaDesc').textContent = `${statusHidup.hidup || 0} hidup / ${statusHidup.meninggal || 0} meninggal`;
|
||||
document.getElementById('statPendidikanTerdata').textContent = pendidikanTerdata;
|
||||
document.getElementById('statBantuanBulanIni').textContent = bantuan.bulan_ini || 0;
|
||||
document.getElementById('statBantuanTotal').textContent = `${bantuan.total || 0} total bantuan`;
|
||||
document.getElementById('statTransferPending').textContent = transfer.pending || 0;
|
||||
document.getElementById('statTransferDesc').textContent = `${transfer.disetujui || 0} disetujui / ${transfer.ditolak || 0} ditolak`;
|
||||
|
||||
const pendidikanLabels = {
|
||||
tidak_sekolah: 'Tidak Sekolah',
|
||||
sd: 'SD',
|
||||
smp: 'SMP',
|
||||
sma: 'SMA',
|
||||
d3: 'D3',
|
||||
s1_plus: 'S1+',
|
||||
tidak_diketahui: 'Tidak Diketahui',
|
||||
};
|
||||
const pendidikanEntries = chartEntries(pendidikan, pendidikanLabels);
|
||||
renderOrUpdateChart('chartPendidikan', chartPendidikan, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: pendidikanEntries.labels,
|
||||
datasets: [{
|
||||
label: 'Anggota',
|
||||
data: pendidikanEntries.values,
|
||||
backgroundColor: '#14b8a6',
|
||||
borderRadius: 6,
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
plugins: { legend: { display: false } },
|
||||
scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } },
|
||||
},
|
||||
}, c => chartPendidikan = c);
|
||||
|
||||
const bantuanEntries = chartEntries(bantuan.per_jenis || {}, {});
|
||||
renderOrUpdateChart('chartBantuanJenis', chartBantuanJenis, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: bantuanEntries.labels,
|
||||
datasets: [{
|
||||
data: bantuanEntries.values,
|
||||
backgroundColor: ['#22c55e', '#3b82f6', '#f97316', '#8b5cf6', '#ef4444', '#64748b'],
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff',
|
||||
}],
|
||||
},
|
||||
options: { plugins: { legend: { position: 'bottom' } }, cutout: '55%' },
|
||||
}, c => chartBantuanJenis = c);
|
||||
|
||||
const transferEntries = chartEntries(transfer, {
|
||||
pending: 'Pending',
|
||||
disetujui: 'Disetujui',
|
||||
ditolak: 'Ditolak',
|
||||
});
|
||||
renderOrUpdateChart('chartTransfer', chartTransfer, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: transferEntries.labels,
|
||||
datasets: [{
|
||||
data: transferEntries.values,
|
||||
backgroundColor: ['#f59e0b', '#22c55e', '#ef4444'],
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff',
|
||||
}],
|
||||
},
|
||||
options: { plugins: { legend: { position: 'bottom' } }, cutout: '55%' },
|
||||
}, c => chartTransfer = c);
|
||||
}
|
||||
|
||||
function chartEntries(source, labels) {
|
||||
const entries = Object.entries(source || {}).filter(([, value]) => Number(value || 0) > 0);
|
||||
if (!entries.length) return { labels: ['Belum ada'], values: [0] };
|
||||
|
||||
return {
|
||||
labels: entries.map(([key]) => labels[key] || key.replace(/_/g, ' ')),
|
||||
values: entries.map(([, value]) => Number(value || 0)),
|
||||
};
|
||||
}
|
||||
|
||||
// ── 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 } }
|
||||
@@ -413,19 +503,27 @@
|
||||
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');
|
||||
if (typeof loadDashboard !== 'undefined') loadDashboard();
|
||||
}
|
||||
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);
|
||||
});
|
||||
})();
|
||||
|
||||
+126
-91
@@ -21,76 +21,15 @@
|
||||
body { font-weight: 500; background-color: #f8fafc; }
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<body class="min-h-screen app-body">
|
||||
|
||||
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||
<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-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
||||
<li><a href="{{ url('/data') }}" class="font-semibold bg-primary/10 text-primary rounded-lg"><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')
|
||||
|
||||
<!-- ═══ MAIN CONTENT ════════════════════════════════════════════════════ -->
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<main class="app-main">
|
||||
<div class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||
@@ -145,9 +84,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div role="tablist" class="tabs tabs-lifted tabs-lg mb-6">
|
||||
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🕌 Rumah Ibadah" checked />
|
||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
|
||||
<div class="genesis-tabs">
|
||||
<div class="genesis-tab-list" role="tablist" aria-label="Jenis data">
|
||||
<button type="button" class="genesis-tab-button is-active" id="tab_ibadah" role="tab" aria-selected="true" aria-controls="panel_ibadah" onclick="setDataTab('ibadah')">
|
||||
Rumah Ibadah
|
||||
</button>
|
||||
<button type="button" class="genesis-tab-button" id="tab_miskin" role="tab" aria-selected="false" aria-controls="panel_miskin" onclick="setDataTab('miskin')">
|
||||
Rumah Miskin
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="panel_ibadah" role="tabpanel" aria-labelledby="tab_ibadah" class="genesis-tab-panel">
|
||||
@if(auth()->user()->hasRole('administrator'))
|
||||
<div id="bulkBar_ibadah" class="hidden alert alert-warning py-2 mb-2 flex items-center justify-between">
|
||||
<span class="text-sm font-semibold"><span id="bulkCount_ibadah">0</span> item terpilih</span>
|
||||
@@ -175,8 +122,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🏠 Rumah Miskin" />
|
||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
|
||||
<div id="panel_miskin" role="tabpanel" aria-labelledby="tab_miskin" class="genesis-tab-panel hidden">
|
||||
@if(auth()->user()->hasRole('administrator'))
|
||||
<div id="bulkBar_miskin" class="hidden alert alert-warning py-2 mb-2 flex items-center justify-between">
|
||||
<span class="text-sm font-semibold"><span id="bulkCount_miskin">0</span> item terpilih</span>
|
||||
@@ -204,6 +150,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Modal: Edit Rumah Ibadah -->
|
||||
@@ -374,8 +321,9 @@
|
||||
@if(auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan'))
|
||||
<div class="collapse collapse-arrow border border-base-300 rounded-lg">
|
||||
<input type="checkbox" id="toggleFormAnggota">
|
||||
<div class="collapse-title text-sm font-bold"><i class="fa-solid fa-plus mr-2 text-info"></i>Tambah Anggota</div>
|
||||
<div class="collapse-title text-sm font-bold"><i class="fa-solid fa-user-pen mr-2 text-info"></i><span id="formAnggotaTitle">Tambah Anggota</span></div>
|
||||
<div class="collapse-content">
|
||||
<input type="hidden" id="anggota_edit_id">
|
||||
<div class="grid grid-cols-2 gap-3 mt-2">
|
||||
<div class="form-control col-span-2">
|
||||
<label class="label"><span class="label-text font-bold">Nama</span></label>
|
||||
@@ -423,7 +371,10 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="simpanAnggota()" class="btn btn-info btn-sm mt-3 w-full"><i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Anggota</button>
|
||||
<div class="flex gap-2 mt-3">
|
||||
<button onclick="resetAnggotaForm()" class="btn btn-ghost btn-sm flex-1">Reset</button>
|
||||
<button onclick="simpanAnggota()" class="btn btn-info btn-sm flex-1"><i class="fa-solid fa-floppy-disk mr-1"></i><span id="btnSimpanAnggotaText">Simpan Anggota</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@@ -495,9 +446,21 @@
|
||||
let selectedMiskin = new Set();
|
||||
|
||||
let ibadahData = [], miskinData = [];
|
||||
let activeDataTab = 'ibadah';
|
||||
document.addEventListener('DOMContentLoaded', loadAllData);
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
|
||||
function setDataTab(type) {
|
||||
activeDataTab = type;
|
||||
const isIbadah = type === 'ibadah';
|
||||
document.getElementById('tab_ibadah').classList.toggle('is-active', isIbadah);
|
||||
document.getElementById('tab_miskin').classList.toggle('is-active', !isIbadah);
|
||||
document.getElementById('tab_ibadah').setAttribute('aria-selected', isIbadah ? 'true' : 'false');
|
||||
document.getElementById('tab_miskin').setAttribute('aria-selected', isIbadah ? 'false' : 'true');
|
||||
document.getElementById('panel_ibadah').classList.toggle('hidden', !isIbadah);
|
||||
document.getElementById('panel_miskin').classList.toggle('hidden', isIbadah);
|
||||
}
|
||||
|
||||
async function loadAllData() {
|
||||
try {
|
||||
const [ri, rm] = await Promise.all([
|
||||
@@ -592,10 +555,9 @@
|
||||
}
|
||||
|
||||
function exportCSV() {
|
||||
const activeTab = document.querySelector('input[name="data_tabs"]:checked').getAttribute('aria-label');
|
||||
let csvContent = "data:text/csv;charset=utf-8,";
|
||||
|
||||
if (activeTab.includes("Ibadah")) {
|
||||
if (activeDataTab === 'ibadah') {
|
||||
csvContent += "ID,Nama,Jenis,Alamat,Radius,Jemaah,Pengurus,Telepon,Lat,Lon\n";
|
||||
ibadahData.forEach(i => {
|
||||
csvContent += `${i.id},"${i.nama}","${i.jenis}","${i.alamat}",${i.radius},${i.jumlah_jemaah},"${i.pengurus}","${i.telepon}",${i.latitude},${i.longitude}\n`;
|
||||
@@ -837,20 +799,22 @@
|
||||
|
||||
// ── ANGGOTA KELUARGA ───────────────────────────────────────────────────
|
||||
let currentAnggotaRumah = null;
|
||||
let anggotaData = [];
|
||||
const canEditAnggota = {{ auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan') ? 'true' : 'false' }};
|
||||
|
||||
async function openAnggotaModal(idRumah) {
|
||||
currentAnggotaRumah = idRumah;
|
||||
const rm = miskinData.find(m => m.id_rumah === idRumah);
|
||||
document.getElementById('anggota_rumah_label').textContent = `ID: ${idRumah} — ${rm?.nama_kk || '-'}`;
|
||||
resetAnggotaForm();
|
||||
document.getElementById('modal_anggota').showModal();
|
||||
await loadAnggota(idRumah);
|
||||
}
|
||||
|
||||
async function loadAnggota(idRumah) {
|
||||
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(idRumah)}/anggota`, { headers: { 'X-CSRF-TOKEN': csrfToken } });
|
||||
const list = await res.json();
|
||||
document.getElementById('bodyAnggota').innerHTML = list.length ? list.map(a => `
|
||||
anggotaData = await res.json();
|
||||
document.getElementById('bodyAnggota').innerHTML = anggotaData.length ? anggotaData.map(a => `
|
||||
<tr>
|
||||
<td class="font-semibold">${esc(a.nama)}</td>
|
||||
<td class="capitalize text-xs">${esc(a.hubungan.replace('_', ' '))}</td>
|
||||
@@ -858,12 +822,45 @@
|
||||
<td class="text-xs">${a.umur !== null ? esc(String(a.umur)) + ' th' : '-'}</td>
|
||||
<td class="capitalize text-xs">${a.pendidikan ? esc(a.pendidikan.replace(/_/g, ' ')) : '-'}</td>
|
||||
<td><span class="badge badge-xs ${a.status_hidup === 'hidup' ? 'badge-success' : 'badge-error'}">${esc(a.status_hidup)}</span></td>
|
||||
<td>${canEditAnggota ? `<button onclick="deleteAnggota(${a.id})" class="btn btn-xs btn-ghost text-error"><i class="fa-solid fa-trash"></i></button>` : ''}</td>
|
||||
<td>${canEditAnggota ? `<div class="flex justify-end gap-1">
|
||||
<button onclick="editAnggota(${a.id})" class="btn btn-xs btn-ghost text-warning" title="Edit"><i class="fa-solid fa-pen"></i></button>
|
||||
<button onclick="deleteAnggota(${a.id})" class="btn btn-xs btn-ghost text-error" title="Hapus"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>` : ''}</td>
|
||||
</tr>`).join('')
|
||||
: '<tr><td colspan="7" class="text-center py-4 opacity-30 italic">Belum ada anggota</td></tr>';
|
||||
}
|
||||
|
||||
function resetAnggotaForm() {
|
||||
const editId = document.getElementById('anggota_edit_id');
|
||||
if (!editId) return;
|
||||
editId.value = '';
|
||||
document.getElementById('formAnggotaTitle').textContent = 'Tambah Anggota';
|
||||
document.getElementById('btnSimpanAnggotaText').textContent = 'Simpan Anggota';
|
||||
document.getElementById('anggota_nama').value = '';
|
||||
document.getElementById('anggota_hubungan').value = 'lainnya';
|
||||
document.getElementById('anggota_kelamin').value = '';
|
||||
document.getElementById('anggota_tgl_lahir').value = '';
|
||||
document.getElementById('anggota_pendidikan').value = '';
|
||||
document.getElementById('anggota_status').value = 'hidup';
|
||||
}
|
||||
|
||||
function editAnggota(id) {
|
||||
const item = anggotaData.find(a => Number(a.id) === Number(id));
|
||||
if (!item) return;
|
||||
document.getElementById('anggota_edit_id').value = item.id;
|
||||
document.getElementById('formAnggotaTitle').textContent = 'Edit Anggota';
|
||||
document.getElementById('btnSimpanAnggotaText').textContent = 'Simpan Perubahan';
|
||||
document.getElementById('anggota_nama').value = item.nama || '';
|
||||
document.getElementById('anggota_hubungan').value = item.hubungan || 'lainnya';
|
||||
document.getElementById('anggota_kelamin').value = item.jenis_kelamin || '';
|
||||
document.getElementById('anggota_tgl_lahir').value = item.tanggal_lahir || '';
|
||||
document.getElementById('anggota_pendidikan').value = item.pendidikan || '';
|
||||
document.getElementById('anggota_status').value = item.status_hidup || 'hidup';
|
||||
document.getElementById('toggleFormAnggota').checked = true;
|
||||
}
|
||||
|
||||
async function simpanAnggota() {
|
||||
const editId = document.getElementById('anggota_edit_id')?.value || '';
|
||||
const body = {
|
||||
nama: document.getElementById('anggota_nama').value.trim(),
|
||||
hubungan: document.getElementById('anggota_hubungan').value,
|
||||
@@ -873,14 +870,22 @@
|
||||
status_hidup: document.getElementById('anggota_status').value,
|
||||
};
|
||||
if (!body.nama) { showToast('Nama wajib diisi', 'error'); return; }
|
||||
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota`, {
|
||||
method: 'POST',
|
||||
const url = editId
|
||||
? `{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota/${editId}`
|
||||
: `{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota`;
|
||||
const res = await fetch(url, {
|
||||
method: editId ? 'PUT' : 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) { showToast('Anggota ditambahkan', 'success'); loadAnggota(currentAnggotaRumah); }
|
||||
else showToast('Gagal menambah anggota', 'error');
|
||||
if (json.success) {
|
||||
showToast(editId ? 'Anggota diperbarui' : 'Anggota ditambahkan', 'success');
|
||||
resetAnggotaForm();
|
||||
loadAnggota(currentAnggotaRumah);
|
||||
} else {
|
||||
showToast(editId ? 'Gagal memperbarui anggota' : 'Gagal menambah anggota', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAnggota(id) {
|
||||
@@ -896,6 +901,8 @@
|
||||
// ── RIWAYAT BANTUAN ────────────────────────────────────────────────────
|
||||
let currentBantuanRumah = null;
|
||||
const canDeleteBantuan = {{ auth()->user()->hasRole('administrator') ? 'true' : 'false' }};
|
||||
const currentUserId = Number({{ auth()->id() ?? 0 }});
|
||||
const isPengurusIbadah = {{ auth()->user()->hasRole('pengurus_ibadah') ? 'true' : 'false' }};
|
||||
|
||||
async function openBantuanModal(idRumah) {
|
||||
currentBantuanRumah = idRumah;
|
||||
@@ -904,8 +911,11 @@
|
||||
|
||||
const sel = document.getElementById('bantuan_ibadah');
|
||||
if (sel) {
|
||||
const availableIbadah = isPengurusIbadah
|
||||
? ibadahData.filter(ib => Number(ib.user_id) === currentUserId)
|
||||
: ibadahData;
|
||||
sel.innerHTML = '<option value="">-- Pilih (opsional) --</option>' +
|
||||
ibadahData.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
|
||||
availableIbadah.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
|
||||
}
|
||||
|
||||
const tgl = document.getElementById('bantuan_tanggal');
|
||||
@@ -958,14 +968,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 } }
|
||||
@@ -973,22 +1000,30 @@
|
||||
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
|
||||
: 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);
|
||||
});
|
||||
})();
|
||||
|
||||
+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);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$roleName = $user?->getRoleNames()->first() ?? 'user';
|
||||
$pendingTransferCount = $pendingTransferCount ?? 0;
|
||||
|
||||
$navItems = [
|
||||
[
|
||||
'label' => 'Peta Interaktif',
|
||||
'short' => 'Peta',
|
||||
'href' => url('/map'),
|
||||
'icon' => 'fa-map',
|
||||
'active' => request()->is('map'),
|
||||
],
|
||||
[
|
||||
'label' => 'Data Tabular',
|
||||
'short' => 'Data',
|
||||
'href' => url('/data'),
|
||||
'icon' => 'fa-table-list',
|
||||
'active' => request()->is('data'),
|
||||
],
|
||||
[
|
||||
'label' => 'Dashboard',
|
||||
'short' => 'Dashboard',
|
||||
'href' => url('/dashboard'),
|
||||
'icon' => 'fa-chart-bar',
|
||||
'active' => request()->is('dashboard'),
|
||||
],
|
||||
[
|
||||
'label' => 'Transfer',
|
||||
'short' => 'Transfer',
|
||||
'href' => url('/permohonan'),
|
||||
'icon' => 'fa-right-left',
|
||||
'active' => request()->is('permohonan'),
|
||||
'badge' => $pendingTransferCount,
|
||||
],
|
||||
];
|
||||
|
||||
$adminItems = [
|
||||
[
|
||||
'label' => 'Admin',
|
||||
'href' => url('/admin/users'),
|
||||
'icon' => 'fa-users-gear',
|
||||
'active' => request()->is('admin/users'),
|
||||
],
|
||||
[
|
||||
'label' => 'Log',
|
||||
'href' => url('/admin/logs'),
|
||||
'icon' => 'fa-clock-rotate-left',
|
||||
'active' => request()->is('admin/logs'),
|
||||
],
|
||||
[
|
||||
'label' => 'Pengaturan',
|
||||
'href' => url('/admin/settings'),
|
||||
'icon' => 'fa-sliders',
|
||||
'active' => request()->is('admin/settings'),
|
||||
],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<input id="app-sidebar-toggle" type="checkbox" class="app-sidebar-toggle" autocomplete="off">
|
||||
|
||||
<header class="app-mobile-bar">
|
||||
<label for="app-sidebar-toggle" class="btn btn-ghost btn-square app-sidebar-open" aria-label="Buka navigasi">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</label>
|
||||
<a href="{{ url('/') }}" class="app-mobile-brand">
|
||||
<i class="fa-solid fa-house-chimney-crack"></i>
|
||||
<span>WebGIS</span>
|
||||
</a>
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-primary text-primary-content rounded-full w-8">
|
||||
<span class="text-xs font-bold">{{ substr($user->name, 0, 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<label for="app-sidebar-toggle" class="app-sidebar-backdrop" aria-label="Tutup navigasi"></label>
|
||||
|
||||
<aside class="app-sidebar" aria-label="Navigasi utama">
|
||||
<div class="app-sidebar-header">
|
||||
<a href="{{ url('/') }}" class="app-sidebar-brand">
|
||||
<span class="app-brand-icon"><i class="fa-solid fa-house-chimney-crack"></i></span>
|
||||
<span class="app-brand-copy">
|
||||
<span class="app-brand-title">WebGIS</span>
|
||||
<span class="app-brand-subtitle">Pemetaan Kemiskinan</span>
|
||||
</span>
|
||||
</a>
|
||||
<label for="app-sidebar-toggle" class="btn btn-ghost btn-square app-sidebar-close" aria-label="Tutup navigasi">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<nav class="app-sidebar-nav">
|
||||
<div class="app-nav-section">
|
||||
<div class="app-nav-section-title">Menu</div>
|
||||
@foreach($navItems as $item)
|
||||
<a href="{{ $item['href'] }}" class="app-nav-link {{ $item['active'] ? 'is-active' : '' }}">
|
||||
<i class="fa-solid {{ $item['icon'] }}"></i>
|
||||
<span>{{ $item['label'] }}</span>
|
||||
@if(($item['badge'] ?? 0) > 0)
|
||||
<span class="badge badge-error badge-xs transfer-pending-badge app-nav-badge">{{ $item['badge'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if($user->hasRole('administrator'))
|
||||
<div class="app-nav-section">
|
||||
<div class="app-nav-section-title">Administrasi</div>
|
||||
@foreach($adminItems as $item)
|
||||
<a href="{{ $item['href'] }}" class="app-nav-link {{ $item['active'] ? 'is-active' : '' }}">
|
||||
<i class="fa-solid {{ $item['icon'] }}"></i>
|
||||
<span>{{ $item['label'] }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</nav>
|
||||
|
||||
<div class="app-sidebar-footer">
|
||||
<div class="app-user-card">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-primary text-primary-content rounded-full w-9">
|
||||
<span class="text-xs font-bold">{{ substr($user->name, 0, 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-user-copy">
|
||||
<div class="app-user-name">{{ $user->name }}</div>
|
||||
<div class="app-user-role">{{ str_replace('_', ' ', $roleName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="app-logout-button">
|
||||
<i class="fa-solid fa-right-from-bracket"></i>
|
||||
<span>Keluar</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -14,40 +14,13 @@
|
||||
body { font-weight: 500; background-color: #f8fafc; }
|
||||
h1, h2, h3, .btn { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||
<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-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
||||
<li><a href="{{ url('/data') }}" class="font-medium"><i class="fa-solid fa-table-list"></i> Data</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-semibold bg-primary/10 text-primary rounded-lg"><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>
|
||||
<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>
|
||||
</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>
|
||||
<body class="min-h-screen app-body">
|
||||
@include('partials.sidebar')
|
||||
|
||||
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<main class="app-main">
|
||||
<div class="max-w-screen-xl mx-auto px-4 py-8">
|
||||
<div class="mb-8 flex items-start justify-between flex-wrap gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold flex items-center gap-3"><i class="fa-solid fa-right-left text-primary"></i> Permohonan Transfer</h1>
|
||||
@@ -81,6 +54,7 @@
|
||||
<tbody id="bodyPermohonan"><tr><td colspan="8" class="text-center py-10 opacity-30 italic">Memuat...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Modal Ajukan Permohonan -->
|
||||
@@ -137,6 +111,8 @@
|
||||
|
||||
const statusBadge = { pending: 'badge-warning', disetujui: 'badge-success', ditolak: 'badge-error' };
|
||||
const canRespond = {{ auth()->user()->hasRole('administrator') || auth()->user()->hasRole('pengurus_ibadah') ? 'true' : 'false' }};
|
||||
const currentUserId = Number({{ auth()->id() ?? 0 }});
|
||||
const isPengurusIbadah = {{ auth()->user()->hasRole('pengurus_ibadah') ? 'true' : 'false' }};
|
||||
|
||||
let allPermohonan = [];
|
||||
let ibadahList = [];
|
||||
@@ -153,9 +129,13 @@
|
||||
ibadahList = await ibadahRes.json();
|
||||
miskinList = await miskinRes.json();
|
||||
|
||||
const optIbadah = ibadahList.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
|
||||
document.getElementById('perm_dari').innerHTML = optIbadah;
|
||||
document.getElementById('perm_ke').innerHTML = optIbadah;
|
||||
const fromIbadah = isPengurusIbadah
|
||||
? ibadahList.filter(ib => Number(ib.user_id) === currentUserId)
|
||||
: ibadahList;
|
||||
const optFromIbadah = fromIbadah.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
|
||||
const optToIbadah = ibadahList.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
|
||||
document.getElementById('perm_dari').innerHTML = optFromIbadah;
|
||||
document.getElementById('perm_ke').innerHTML = optToIbadah;
|
||||
document.getElementById('perm_miskin').innerHTML = miskinList
|
||||
.map(m => `<option value="${esc(m.id_rumah)}">${esc(m.id_rumah)} — ${esc(m.nama_kk || '-')}</option>`).join('');
|
||||
|
||||
|
||||
@@ -20,16 +20,6 @@
|
||||
<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;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value {
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
#bgMap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -37,73 +27,141 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
opacity: 0.5;
|
||||
filter: saturate(1.2) brightness(0.7);
|
||||
opacity: 0.78;
|
||||
filter: saturate(0.88) brightness(0.96) contrast(0.92);
|
||||
}
|
||||
.hero {
|
||||
.landing-shell {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background: transparent !important;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
align-items: end;
|
||||
padding: 96px 24px 32px;
|
||||
}
|
||||
/* Glass effect for content */
|
||||
.hero-content {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
padding: 3rem;
|
||||
border-radius: 2rem;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
.landing-shell::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(250, 250, 250, 0.94) 0%, rgba(250, 250, 250, 0.82) 42%, rgba(250, 250, 250, 0.22) 100%),
|
||||
linear-gradient(180deg, rgba(250, 250, 250, 0.12) 0%, rgba(250, 250, 250, 0.84) 100%);
|
||||
}
|
||||
[data-theme="dark"] .hero-content {
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
.landing-content {
|
||||
max-width: 760px;
|
||||
}
|
||||
.brand-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #6b6b6b;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.brand-mark {
|
||||
display: inline-grid;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
place-items: center;
|
||||
border: 1px solid #e8e8ec;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #0a0a0a;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.landing-title {
|
||||
max-width: 720px;
|
||||
margin-top: 24px;
|
||||
color: #0a0a0a;
|
||||
font-size: clamp(44px, 7vw, 72px);
|
||||
line-height: 0.94;
|
||||
}
|
||||
.landing-copy {
|
||||
max-width: 520px;
|
||||
margin-top: 20px;
|
||||
color: #6b6b6b;
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.landing-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.landing-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
margin-top: 28px;
|
||||
color: #6b6b6b;
|
||||
font-size: 13px;
|
||||
}
|
||||
.redirect-strip {
|
||||
width: min(420px, 100%);
|
||||
margin-top: 40px;
|
||||
}
|
||||
.progress {
|
||||
height: 4px;
|
||||
background: #e8e8ec;
|
||||
}
|
||||
.progress::-webkit-progress-value {
|
||||
background: #6366f1;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.landing-shell {
|
||||
padding: 80px 20px 28px;
|
||||
}
|
||||
.landing-title {
|
||||
font-size: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen bg-base-200 flex flex-col items-center justify-center overflow-hidden">
|
||||
<body class="min-h-screen overflow-hidden">
|
||||
|
||||
<!-- Background Map -->
|
||||
<div id="bgMap"></div>
|
||||
|
||||
<div class="hero min-h-screen">
|
||||
<div class="hero-content text-center flex-col gap-2">
|
||||
<!-- Logo -->
|
||||
<div class="text-7xl mb-2">🏘️</div>
|
||||
<h1 class="text-4xl font-bold tracking-tight text-primary">WebGIS Pemetaan Kemiskinan</h1>
|
||||
<p class="max-w-md text-base-content/60 mt-2 mb-6 leading-relaxed">
|
||||
Sistem Informasi Geografis untuk pemetaan rumah ibadah dan
|
||||
analisis sebaran keluarga kurang mampu.
|
||||
<div class="landing-shell">
|
||||
<div class="landing-content">
|
||||
<div class="brand-kicker">
|
||||
<span class="brand-mark">GIS</span>
|
||||
Sistem informasi geografis desa
|
||||
</div>
|
||||
<h1 class="landing-title">Pemetaan kemiskinan berbasis radius layanan.</h1>
|
||||
<p class="landing-copy">
|
||||
Pantau sebaran keluarga kurang mampu, rumah ibadah, dan celah jangkauan bantuan dalam satu ruang kerja spasial yang ringkas.
|
||||
</p>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<div class="landing-actions">
|
||||
<a href="{{ url('/map') }}" class="btn btn-primary btn-lg gap-2">
|
||||
<i class="fa-solid fa-map-location-dot"></i>
|
||||
Buka Peta & Input
|
||||
Buka Peta
|
||||
</a>
|
||||
<a href="{{ url('/data') }}" class="btn btn-outline btn-lg gap-2">
|
||||
<i class="fa-solid fa-table-list"></i>
|
||||
Lihat Data Tabular
|
||||
Lihat Data
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- User info -->
|
||||
<div class="flex items-center gap-2 text-sm text-base-content/50 mt-2">
|
||||
<i class="fa-solid fa-user text-xs"></i>
|
||||
<div class="landing-meta">
|
||||
<span>{{ auth()->user()->name }}</span>
|
||||
<span class="opacity-30">•</span>
|
||||
<span>/</span>
|
||||
<span class="capitalize">{{ str_replace('_', ' ', auth()->user()->getRoleNames()->first() ?? '') }}</span>
|
||||
<span class="opacity-30">•</span>
|
||||
<span>/</span>
|
||||
<form method="POST" action="{{ route('logout') }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="hover:text-error transition-colors underline underline-offset-2">Keluar</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Redirect progress -->
|
||||
<div class="mt-8 w-72">
|
||||
<p class="text-xs text-base-content/40 mb-2">Mengarahkan ke halaman Peta dalam 5 detik...</p>
|
||||
<progress class="progress progress-primary w-full" id="redirectProgress" value="0" max="100"></progress>
|
||||
<div class="redirect-strip">
|
||||
<p class="text-xs text-base-content/50 mb-2">Mengarahkan ke halaman Peta dalam 5 detik</p>
|
||||
<progress class="progress w-full" id="redirectProgress" value="0" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user