update fitur peta pada tambah spbu

This commit is contained in:
tistopandita
2026-06-11 23:54:47 +07:00
parent 9d22253e40
commit 6535ae815f
2 changed files with 247 additions and 28 deletions
+244 -16
View File
@@ -473,11 +473,85 @@
font-weight: 900;
}
/* ── Map SPBU (pin klik) ── */
.spbu-map-wrap {
grid-column: 1 / -1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
align-items: start;
}
.spbu-map-box {
border: 2px solid var(--border);
border-radius: 12px;
overflow: hidden;
height: 300px;
position: relative;
cursor: crosshair;
}
.spbu-map-box.has-pin {
border-color: var(--accent);
}
.spbu-coords-hint {
padding: 9px 12px;
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 10px;
font-size: 0.82rem;
color: #1e40af;
margin-bottom: 10px;
line-height: 1.5;
}
.spbu-coords-hint strong {
display: block;
margin-bottom: 3px;
}
.coords-pill {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 12px;
background: #f8fafc;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 0.82rem;
color: var(--muted);
font-family: monospace;
margin-top: 8px;
}
.coords-pill.filled {
color: var(--text);
border-color: var(--accent);
background: #eff6ff;
}
.coords-pill span {
flex: 1;
}
.coords-clear {
width: 22px;
height: 22px;
border: 0;
border-radius: 6px;
background: #e2e8f0;
cursor: pointer;
font-size: 0.85rem;
line-height: 1;
display: none;
}
.coords-pill.filled .coords-clear {
display: inline-flex;
align-items: center;
justify-content: center;
}
/* ── Responsive ── */
@media (max-width: 860px) {
.map-input-wrap {
grid-template-columns: 1fr;
}
.spbu-map-wrap {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.form-grid {
@@ -554,14 +628,75 @@
placeholder="Jl. ..., Kelurahan, Kecamatan"
/>
</div>
<div class="form-group">
<label for="fLat">Latitude *</label>
<input id="fLat" type="text" placeholder="Contoh: -0.0350" />
</div>
<div class="form-group">
<label for="fLng">Longitude *</label>
<input id="fLng" type="text" placeholder="Contoh: 109.3400" />
<!-- Peta pin SPBU -->
<div class="spbu-map-wrap">
<div>
<div class="spbu-coords-hint">
<strong>📍 Tentukan lokasi SPBU di peta</strong>
Klik langsung di peta untuk menempatkan pin lokasi SPBU.
Klik di tempat lain untuk memindahkan pin.
</div>
<div id="mapSpbu" class="spbu-map-box"></div>
<div class="coords-pill" id="coordsPillSpbu">
<span id="coordsTextSpbu"
>Belum ada pin. Klik di peta untuk menentukan
lokasi.</span
>
<button
class="coords-clear"
id="clearPinSpbu"
title="Hapus pin"
type="button"
>
×
</button>
</div>
</div>
<!-- Input lat/lng tetap ada tapi read-only dan tersembunyi untuk keperluan submit -->
<div style="display: flex; flex-direction: column; gap: 10px">
<div class="form-group">
<label>Latitude (otomatis dari peta)</label>
<input
id="fLat"
type="text"
placeholder="Klik peta untuk mengisi"
readonly
style="
background: #f1f5f9;
cursor: not-allowed;
color: var(--muted);
"
/>
</div>
<div class="form-group">
<label>Longitude (otomatis dari peta)</label>
<input
id="fLng"
type="text"
placeholder="Klik peta untuk mengisi"
readonly
style="
background: #f1f5f9;
cursor: not-allowed;
color: var(--muted);
"
/>
</div>
<p
style="
margin: 4px 0 0;
font-size: 0.78rem;
color: var(--muted);
line-height: 1.5;
"
>
Koordinat diisi otomatis saat kamu klik peta. Tidak perlu
mengetik secara manual.
</p>
</div>
</div>
<div class="form-group">
<label for="fJamBuka">Jam Buka *</label>
<input id="fJamBuka" type="time" value="06:00" />
@@ -939,7 +1074,7 @@
});
// ─── Tab switching ────────────────────────────────────────────────────────────
let mapsInitialized = { jalan: false, bangunan: false };
let mapsInitialized = { spbu: false, jalan: false, bangunan: false };
document.querySelectorAll(".tab-btn").forEach((btn) => {
btn.addEventListener("click", () => {
@@ -954,6 +1089,12 @@
document.getElementById(tabId).classList.add("active");
// Init peta saat tab dibuka pertama kali (Leaflet butuh container visible)
if (tabId === "tab-spbu" && !mapsInitialized.spbu) {
setTimeout(() => {
initSpbuMap();
mapsInitialized.spbu = true;
}, 80);
}
if (tabId === "tab-jalan" && !mapsInitialized.jalan) {
setTimeout(() => {
initJalanMap();
@@ -969,6 +1110,79 @@
});
});
// ══════════════════════════════════════════════════════════════════════════════
// ─── PETA SPBU (pin klik) ────────────────────────────────────────────────────
// ══════════════════════════════════════════════════════════════════════════════
let spbuMap, spbuPinMarker;
function initSpbuMap() {
spbuMap = L.map("mapSpbu").setView(PONTIANAK_CENTER, 13);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(spbuMap);
spbuMap.on("click", (e) => {
const { lat, lng } = e.latlng;
setSpbuPin(lat, lng);
});
}
function setSpbuPin(lat, lng) {
const latR = parseFloat(lat.toFixed(6));
const lngR = parseFloat(lng.toFixed(6));
// Hapus marker lama, pasang yang baru
if (spbuPinMarker) spbuMap.removeLayer(spbuPinMarker);
spbuPinMarker = L.marker([latR, lngR], {
icon: L.divIcon({
className: "",
html: `<div style="
width:36px;height:36px;display:grid;place-items:center;
background:#dc2626;border:3px solid white;border-radius:50% 50% 50% 10%;
transform:rotate(-45deg);box-shadow:0 6px 18px rgba(0,0,0,.3);">
<span style="transform:rotate(45deg);font-size:18px">⛽</span>
</div>`,
iconSize: [36, 36],
iconAnchor: [18, 34],
}),
draggable: true,
}).addTo(spbuMap);
// Drag marker juga update koordinat
spbuPinMarker.on("dragend", (ev) => {
const pos = ev.target.getLatLng();
setSpbuPin(pos.lat, pos.lng);
});
// Update input fields & pill
spbuEl.fLat.value = latR;
spbuEl.fLng.value = lngR;
document.getElementById("coordsTextSpbu").textContent =
`Lat: ${latR} | Lng: ${lngR}`;
const pill = document.getElementById("coordsPillSpbu");
pill.classList.add("filled");
document.getElementById("mapSpbu").classList.add("has-pin");
}
function clearSpbuPin() {
if (spbuPinMarker) {
spbuMap.removeLayer(spbuPinMarker);
spbuPinMarker = null;
}
spbuEl.fLat.value = "";
spbuEl.fLng.value = "";
document.getElementById("coordsTextSpbu").textContent =
"Belum ada pin. Klik di peta untuk menentukan lokasi.";
document.getElementById("coordsPillSpbu").classList.remove("filled");
document.getElementById("mapSpbu").classList.remove("has-pin");
}
document
.getElementById("clearPinSpbu")
.addEventListener("click", clearSpbuPin);
// ══════════════════════════════════════════════════════════════════════════════
// ─── TAB SPBU ────────────────────────────────────────────────────────────────
// ══════════════════════════════════════════════════════════════════════════════
@@ -1080,16 +1294,12 @@
}
function resetSpbuForm() {
spbuEl.editId.value = "";
spbuEl.fKode.value =
spbuEl.fNama.value =
spbuEl.fAlamat.value =
spbuEl.fLat.value =
spbuEl.fLng.value =
"";
spbuEl.fKode.value = spbuEl.fNama.value = spbuEl.fAlamat.value = "";
spbuEl.fJamBuka.value = "06:00";
spbuEl.fJamTutup.value = "22:00";
spbuEl.fOverride.value = "auto";
renderFuelChecks([]);
clearSpbuPin();
spbuEl.formTitle.textContent = " Tambah SPBU Baru";
spbuEl.submitBtn.textContent = "💾 Simpan SPBU";
spbuEl.resetBtn.style.display = "none";
@@ -1101,8 +1311,6 @@
spbuEl.fKode.value = spbu.kode;
spbuEl.fNama.value = spbu.nama;
spbuEl.fAlamat.value = spbu.alamat;
spbuEl.fLat.value = spbu.lat;
spbuEl.fLng.value = spbu.lng;
spbuEl.fJamBuka.value = spbu.jam_buka;
spbuEl.fJamTutup.value = spbu.jam_tutup;
spbuEl.fOverride.value = spbu.manual_override || "auto";
@@ -1111,6 +1319,21 @@
spbuEl.submitBtn.textContent = "💾 Update SPBU";
spbuEl.resetBtn.style.display = "";
window.scrollTo({ top: 0, behavior: "smooth" });
// Set pin di peta — inisialisasi peta dulu kalau belum
const doPin = () => {
setSpbuPin(spbu.lat, spbu.lng);
spbuMap.setView([spbu.lat, spbu.lng], 15);
};
if (!mapsInitialized.spbu) {
setTimeout(() => {
initSpbuMap();
mapsInitialized.spbu = true;
setTimeout(doPin, 200);
}, 80);
} else {
doPin();
}
}
async function submitSpbu() {
const id = spbuEl.editId.value;
@@ -1171,6 +1394,11 @@
spbuEl.resetBtn.addEventListener("click", resetSpbuForm);
renderFuelChecks([]);
loadSpbu();
// Tab SPBU aktif saat load — init petanya langsung
setTimeout(() => {
initSpbuMap();
mapsInitialized.spbu = true;
}, 120);
// ══════════════════════════════════════════════════════════════════════════════
// ─── TAB JALAN ───────────────────────────────────────────────────────────────
+3 -12
View File
@@ -194,7 +194,7 @@
<div class="brand">
<small>Gereja Peduli</small>
<h1>WebGIS Poverty Mapping</h1>
<p>Peta prioritas keluarga penerima bantuan berbasis SAW, terpusat, dan mendukung mode privasi.</p>
<p>Peta prioritas keluarga penerima bantuan berbasis SAW, terpusat, dan mudah dipantau.</p>
</div>
<section class="panel">
@@ -209,12 +209,6 @@
<section class="panel">
<h2>Filter Peta</h2>
<label class="field">Role Akses
<select id="roleSelect">
<option value="admin">Admin - semua data</option>
<option value="pengurus">Pengurus - data sensitif disembunyikan</option>
</select>
</label>
<label class="field">Kategori
<select id="categoryFilter">
<option value="">Semua Kategori</option>
@@ -227,7 +221,6 @@
<label class="field">Pencarian Keluarga
<input id="searchInput" placeholder="Nama, alamat, atau wilayah" />
</label>
<label class="switch"><span>Mode Privasi</span><input id="privacyToggle" type="checkbox" /></label>
<label class="switch"><span>Heatmap persebaran</span><input id="heatToggle" type="checkbox" checked /></label>
</section>
@@ -296,11 +289,9 @@
}
function params() {
const role = el("roleSelect").value;
const privacy = el("privacyToggle").checked ? "1" : "0";
const kategori = encodeURIComponent(el("categoryFilter").value);
const search = encodeURIComponent(el("searchInput").value.trim());
return `role=${role}&privacy=${privacy}&kategori=${kategori}&search=${search}`;
return `kategori=${kategori}&search=${search}`;
}
async function loadData() {
@@ -416,7 +407,7 @@
}
let debounce;
["roleSelect", "categoryFilter", "privacyToggle", "heatToggle"].forEach((id) => el(id).addEventListener("change", () => loadData().catch((error) => toast(error.message))));
["categoryFilter", "heatToggle"].forEach((id) => el(id).addEventListener("change", () => loadData().catch((error) => toast(error.message))));
el("searchInput").addEventListener("input", () => {
clearTimeout(debounce);
debounce = setTimeout(() => loadData().catch((error) => toast(error.message)), 260);