tambah fitur add tempat ibadah

This commit is contained in:
tistopandita
2026-06-11 13:04:28 +07:00
parent 6535ae815f
commit 7ef7620d2a
3 changed files with 426 additions and 8 deletions
+91 -1
View File
@@ -170,6 +170,19 @@
z-index: 2000;
}
.toast.show { opacity: 1; transform: translateY(0); }
.ibadah-legend { display: none; }
.ibadah-legend.active { display: block; }
.ibadah-map-marker {
display: grid;
place-items: center;
width: 30px;
height: 30px;
border-radius: 999px;
color: white;
border: 3px solid white;
box-shadow: 0 10px 24px rgba(15, 23, 42, .28);
font-size: 15px;
}
@media (max-width: 880px) {
body { overflow: hidden; }
.app { grid-template-columns: 1fr; }
@@ -222,6 +235,7 @@
<input id="searchInput" placeholder="Nama, alamat, atau wilayah" />
</label>
<label class="switch"><span>Heatmap persebaran</span><input id="heatToggle" type="checkbox" checked /></label>
<label class="switch"><span>⛪ Tempat Ibadah</span><input id="ibadahToggle" type="checkbox" /></label>
</section>
<section class="panel">
@@ -234,6 +248,17 @@
</div>
</section>
<section class="panel ibadah-legend" id="ibadahLegendPanel">
<h2>Legenda Tempat Ibadah</h2>
<div class="legend">
<span><i class="dot" style="background:#7c3aed"></i>Gereja Katolik</span>
<span><i class="dot" style="background:#2563eb"></i>GPIB</span>
<span><i class="dot" style="background:#0891b2"></i>GKI</span>
<span><i class="dot" style="background:#16a34a"></i>HKBP</span>
<span><i class="dot" style="background:#ea580c"></i>Pentakosta</span>
</div>
</section>
<section class="panel">
<h2>Statistik Wilayah</h2>
<div id="areaStats"></div>
@@ -262,11 +287,12 @@
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const API = "/api/poverty";
const state = { rows: [], dashboard: null, markers: new Map() };
const state = { rows: [], dashboard: null, markers: new Map(), ibadah: [], ibadahLoaded: false };
const map = L.map("map", { zoomControl: true }).setView([-0.0263, 109.3425], 12);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "&copy; OpenStreetMap" }).addTo(map);
const markerLayer = L.layerGroup().addTo(map);
const heatLayer = L.layerGroup().addTo(map);
const ibadahLayer = L.layerGroup();
const el = (id) => document.getElementById(id);
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[char]));
@@ -274,6 +300,13 @@
const score = (value) => Number(value || 0).toFixed(3);
const badgeClass = (kategori) => kategori === "Sangat Membutuhkan" ? "sangat" : kategori === "Membutuhkan" ? "butuh" : kategori === "Rentan" ? "rentan" : "mampu";
const color = (kategori) => kategori === "Sangat Membutuhkan" ? "#dc2626" : kategori === "Membutuhkan" ? "#f97316" : kategori === "Rentan" ? "#facc15" : "#22c55e";
const ibadahColors = {
"Gereja Katolik": "#7c3aed",
GPIB: "#2563eb",
GKI: "#0891b2",
HKBP: "#16a34a",
Pentakosta: "#ea580c",
};
function toast(message) {
el("toast").textContent = message;
@@ -350,6 +383,47 @@
`;
}
function ibadahIcon(jenis) {
const markerColor = ibadahColors[jenis] || "#64748b";
return L.divIcon({
className: "",
html: `<div class="ibadah-map-marker" style="background:${markerColor}">⛪</div>`,
iconSize: [30, 30],
iconAnchor: [15, 30],
popupAnchor: [0, -28],
});
}
function ibadahPopupHtml(item) {
const markerColor = ibadahColors[item.jenis] || "#64748b";
return `
<div class="popup">
<h3>${esc(item.nama)}</h3>
<span class="badge" style="background:${markerColor}22;color:${markerColor};border:1px solid ${markerColor}44">${esc(item.jenis)}</span>
<p style="margin-top:10px">${esc(item.alamat)}<br>${esc(item.kelurahan)}</p>
</div>
`;
}
function renderIbadahMarkers() {
ibadahLayer.clearLayers();
state.ibadah.forEach((item) => {
const latlng = [Number(item.lat), Number(item.lng)];
if (!Number.isFinite(latlng[0]) || !Number.isFinite(latlng[1])) return;
L.marker(latlng, { icon: ibadahIcon(item.jenis) })
.bindPopup(ibadahPopupHtml(item))
.addTo(ibadahLayer);
});
}
async function loadIbadahLayer(force = false) {
if (state.ibadahLoaded && !force) return;
const payload = await fetchJson("/ibadah");
state.ibadah = payload.data || [];
state.ibadahLoaded = true;
renderIbadahMarkers();
}
function renderMarkers() {
markerLayer.clearLayers();
heatLayer.clearLayers();
@@ -408,6 +482,22 @@
let debounce;
["categoryFilter", "heatToggle"].forEach((id) => el(id).addEventListener("change", () => loadData().catch((error) => toast(error.message))));
el("ibadahToggle").addEventListener("change", async () => {
try {
if (el("ibadahToggle").checked) {
await loadIbadahLayer(true);
ibadahLayer.addTo(map);
el("ibadahLegendPanel").classList.add("active");
} else {
map.removeLayer(ibadahLayer);
el("ibadahLegendPanel").classList.remove("active");
}
} catch (error) {
el("ibadahToggle").checked = false;
el("ibadahLegendPanel").classList.remove("active");
toast(error.message);
}
});
el("searchInput").addEventListener("input", () => {
clearTimeout(debounce);
debounce = setTimeout(() => loadData().catch((error) => toast(error.message)), 260);