var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); // Layer Groups yang Spesifik var layerMasjid = L.layerGroup().addTo(map); var layerMasjidRadius = L.layerGroup().addTo(map); var layerPelaporan = L.layerGroup().addTo(map); var layerWarga = L.layerGroup().addTo(map); var layerSpbu24 = L.layerGroup().addTo(map); var layerSpbuNon24 = L.layerGroup().addTo(map); var layerLines = L.layerGroup().addTo(map); var layerPolygons = L.layerGroup().addTo(map); // Layers Control var overlays = {}; if (!window.dashboardType || window.dashboardType === 'sosial' || window.dashboardType === 'admin') { overlays["🕌 Tempat Ibadah"] = layerMasjid; overlays["📡 Radius Ibadah/Bantuan"] = layerMasjidRadius; overlays["🚨 Pelaporan Darurat"] = layerPelaporan; overlays["📍 Warga Rentan"] = layerWarga; } if (!window.dashboardType || window.dashboardType === 'infra' || window.dashboardType === 'admin') { overlays["⛽ SPBU 24 Jam (Hijau)"] = layerSpbu24; overlays["⛽ SPBU Terbatas (Merah)"] = layerSpbuNon24; overlays["🛣️ Jalan (Polyline)"] = layerLines; overlays["🟩 Kavling (Polygon)"] = layerPolygons; } L.control.layers(null, overlays, { collapsed: false, position: 'topright' }).addTo(map); var drawnItems = new L.FeatureGroup().addTo(map); var currentRadius = 500; var cachedData = null; var drawControl = new L.Control.Draw({ draw: { marker: false, circle: false, circlemarker: false, polyline: { shapeOptions: { color: 'blue', weight: 4 } }, polygon: { shapeOptions: { color: 'red' } }, rectangle: false }, edit: { featureGroup: drawnItems } }); map.addControl(drawControl); // Locate Control var locateBtn = L.control({ position: 'topleft' }); locateBtn.onAdd = function (map) { var div = L.DomUtil.create('div', 'leaflet-bar leaflet-control'); div.style.backgroundColor = 'white'; div.style.width = '34px'; div.style.height = '34px'; div.style.cursor = 'pointer'; div.style.display = 'flex'; div.style.alignItems = 'center'; div.style.justifyContent = 'center'; div.innerHTML = ''; div.title = "Lokasi Anda Saat Ini"; div.onclick = function () { map.locate({ setView: true, maxZoom: 16 }); } return div; }; locateBtn.addTo(map); map.on('locationfound', function (e) { L.circleMarker(e.latlng, { radius: 8, fillColor: '#3b82f6', color: '#fff', weight: 3, fillOpacity: 1 }).addTo(map).bindPopup("Lokasi Anda").openPopup(); }); // Event Draw map.on(L.Draw.Event.CREATED, function (e) { var layer = e.layer; var type = e.layerType; drawnItems.addLayer(layer); var panel = document.getElementById('panelInput'); if (panel) panel.classList.remove('collapsed'); if (type === 'polyline') { document.getElementById('jenis').value = 'jalan'; toggleForm(); var coords = layer.getLatLngs(); document.getElementById('jalan_coords').value = JSON.stringify(coords); // Calculate Length automatically var length = 0; for (var i = 0; i < coords.length - 1; i++) { length += coords[i].distanceTo(coords[i + 1]); } document.getElementById('panjang_jalan').value = length.toFixed(2); } else if (type === 'polygon') { document.getElementById('jenis').value = 'parsil'; toggleForm(); var coords = layer.getLatLngs()[0]; document.getElementById('parsil_coords').value = JSON.stringify(coords); } }); map.on(L.Draw.Event.EDITED, function (e) { var layers = e.layers; layers.eachLayer(function (layer) { if (layer.db_id && layer.db_tipe) { var coords = (layer.db_tipe === 'parsil') ? layer.getLatLngs()[0] : layer.getLatLngs(); var fd = new FormData(); fd.append('action', 'update_koordinat'); fd.append('id', layer.db_id); fd.append('tipe', layer.db_tipe); fd.append('koordinat', JSON.stringify(coords)); if (layer.db_tipe === 'jalan') { var length = 0; for (var i = 0; i < coords.length - 1; i++) { length += coords[i].distanceTo(coords[i + 1]); } fd.append('panjang_m', length.toFixed(2)); } fetch('../api/api.php', { method: 'POST', body: fd }); } }); Swal.fire({ icon: 'success', title: 'Tersimpan', text: 'Perubahan bentuk berhasil disimpan!', timer: 1500, showConfirmButton: false }); }); map.on(L.Draw.Event.DELETED, function (e) { var layers = e.layers; layers.eachLayer(function (layer) { if (layer.db_id && layer.db_tipe) { var fd = new FormData(); fd.append('action', 'hapus_data'); fd.append('id', layer.db_id); fd.append('tipe', layer.db_tipe); fetch('../api/api.php', { method: 'POST', body: fd }); } }); Swal.fire({ icon: 'success', title: 'Terhapus', text: 'Data berhasil dihapus dari peta!', timer: 1500, showConfirmButton: false }); }); function toggleSidebar() { document.getElementById('sidebar').classList.toggle('active'); } function toggleRightPanel() { var panel = document.getElementById('panelInput'); if (panel) panel.classList.toggle('collapsed'); } function filterData() { var input = document.getElementById('searchInput').value.toLowerCase(); var cards = document.getElementsByClassName('item-data'); if (input.length > 0) document.getElementById('sidebar').classList.add('active'); for (var i = 0; i < cards.length; i++) { cards[i].style.display = cards[i].getAttribute('data-nama').includes(input) ? "" : "none"; } } function changeRadius(v) { currentRadius = parseInt(v); document.getElementById('radiusVal').innerText = v; if (cachedData) renderMap(cachedData); } var prevReportCount = -1; function loadData(isPolling = false) { fetch('../api/api.php?data=all') .then(r => r.json()) .then(data => { cachedData = data; // Logika Notifikasi Bubble if (prevReportCount !== -1 && data.pelaporan.length > prevReportCount) { var newCount = data.pelaporan.length - prevReportCount; Swal.fire({ title: 'Laporan Baru!', text: `Ada ${newCount} laporan warga baru yang masuk.`, icon: 'info', toast: true, position: 'top-end', showConfirmButton: false, timer: 5000 }); } prevReportCount = data.pelaporan.length; var badge = document.getElementById('notifBadge'); if (badge) { if (data.pelaporan.length > 0) { badge.style.display = 'block'; badge.innerText = data.pelaporan.length; } else { badge.style.display = 'none'; } } if (!isPolling) { renderSidebar(data); renderMap(data); } else { // If polling, maybe just update sidebar if it's open, or wait for manual refresh // But for real-time map, we can re-render map as well (might cause flicker if doing heavy edits) // We'll skip re-rendering map to avoid interrupting user edits, just update the bubble renderSidebar(data); renderMap(data); } }); } // Auto-poll every 10 seconds setInterval(() => { // Only poll if not currently editing (edit_id is 0) if (document.getElementById('edit_id') && document.getElementById('edit_id').value === "0") { loadData(true); } }, 10000); function renderSidebar(data) { var html = `
`; if (!window.dashboardType || window.dashboardType === 'sosial' || window.dashboardType === 'admin') { html += `
${data.pelaporan.length}
Laporan
${data.warga.length}
Warga Rentan
`; } if (!window.dashboardType || window.dashboardType === 'infra' || window.dashboardType === 'admin') { html += `
${data.spbu.length}
SPBU Terdata
${data.jalan.length}
Jalur Jalan
`; } html += `
`; if (!window.dashboardType || window.dashboardType === 'sosial' || window.dashboardType === 'admin') { // Pelaporan html += `
🚨 Laporan Darurat
`; if (data.pelaporan.length === 0) html += `Belum ada laporan`; data.pelaporan.forEach(p => { html += `
${p.nama_pelapor} ${p.waktu}
${p.isi_laporan.substring(0, 40)}...

`; }); // Warga html += `
📍 Penduduk Miskin / Rentan
`; data.warga.forEach(w => { html += `
${w.nama} ${w.riwayat_penyakit ? ' Medis' : ''}

`; }); } if (!window.dashboardType || window.dashboardType === 'infra' || window.dashboardType === 'admin') { // SPBU html += `
⛽ Stasiun Pengisian (SPBU)
`; data.spbu.forEach(s => { var is24 = s.kategori === '24jam'; html += `
${s.nama} ${s.no_hp}
${is24 ? 'Buka 24 Jam' : 'Tidak 24 Jam'}

`; }); } if (!window.dashboardType || window.dashboardType === 'sosial' || window.dashboardType === 'admin') { // Tempat Ibadah html += `
🕌 Tempat Ibadah & Bantuan
`; data.masjid.forEach(m => { html += `
${m.nama}

`; }); } if (!window.dashboardType || window.dashboardType === 'infra' || window.dashboardType === 'admin') { // Jalan html += `
🛣️ Manajemen Jalan
`; data.jalan.forEach(j => { var color = '#ef4444'; // default red if (j.status_jalan === 'Nasional') color = '#ef4444'; else if (j.status_jalan === 'Provinsi') color = '#eab308'; else if (j.status_jalan === 'Kabupaten') color = '#22c55e'; html += `
${j.nama_jalan} ${j.status_jalan}
${j.panjang_m} meter

`; }); // Parsil html += `
🟩 Manajemen Kavling / Parsil
`; data.parsil.forEach(p => { html += `
${p.nama_pemilik}
Status: ${p.status_hak}

`; }); } document.getElementById('sidebarContent').innerHTML = html; } function renderMap(data) { layerMasjid.clearLayers(); layerMasjidRadius.clearLayers(); layerPelaporan.clearLayers(); layerWarga.clearLayers(); layerSpbu24.clearLayers(); layerSpbuNon24.clearLayers(); layerLines.clearLayers(); layerPolygons.clearLayers(); var lokMasjid = data.masjid.map(m => L.latLng(m.latitude, m.longitude)); if (!window.dashboardType || window.dashboardType === 'sosial' || window.dashboardType === 'admin') { // Render Masjid / Tempat Ibadah data.masjid.forEach(m => { var popup = `${m.jenis || 'Tempat Ibadah'}: ${m.nama}
`; var faClass = 'fa-mosque'; if (m.jenis === 'Gereja') faClass = 'fa-church'; else if (m.jenis === 'Vihara') faClass = 'fa-vihara'; else if (m.jenis === 'Pura') faClass = 'fa-torii-gate'; // Buat custom marker bentuk map pin dengan icon sesuai jenis var masjidIcon = L.divIcon({ html: `
`, className: '', // Hilangkan background default Leaflet iconSize: [34, 34], iconAnchor: [17, 40], // Anchor ujung lancip di bawah popupAnchor: [0, -40] }); L.marker([m.latitude, m.longitude], { icon: masjidIcon, draggable: true }).addTo(layerMasjid).bindPopup(popup) .on('dragend', function (e) { updateMarkerCoord(m.id, 'masjid', e.target.getLatLng()); }); L.circle([m.latitude, m.longitude], { radius: currentRadius, color: '#2563eb', fillOpacity: 0.1, weight: 1 }).addTo(layerMasjidRadius); }); // Render Pelaporan data.pelaporan.forEach(p => { var popup = `🚨 Laporan: ${p.isi_laporan}
`; var pelaporanIcon = L.divIcon({ html: `
`, className: '', iconSize: [32, 32], iconAnchor: [16, 38], popupAnchor: [0, -38] }); L.marker([p.latitude, p.longitude], { icon: pelaporanIcon, draggable: true }).addTo(layerPelaporan).bindPopup(popup) .on('dragend', function (e) { updateMarkerCoord(p.id, 'pelaporan', e.target.getLatLng()); }); }); } if (!window.dashboardType || window.dashboardType === 'infra' || window.dashboardType === 'admin') { // Render SPBU data.spbu.forEach(s => { var is24 = s.kategori === '24jam'; var iconUrl = is24 ? 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png' : 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png'; var targetLayer = is24 ? layerSpbu24 : layerSpbuNon24; var popup = `⛽ ${s.nama}
Kategori: ${s.kategori === '24jam' ? 'Buka 24 Jam' : 'Tidak 24 Jam'}
`; L.marker([s.latitude, s.longitude], { icon: L.icon({ iconUrl: iconUrl, iconSize: [25, 41], iconAnchor: [12, 41] }), draggable: true }).addTo(targetLayer).bindPopup(popup) .on('dragend', function (e) { updateMarkerCoord(s.id, 'spbu', e.target.getLatLng()); }); }); } if (!window.dashboardType || window.dashboardType === 'sosial' || window.dashboardType === 'admin') { // Render Warga data.warga.forEach(w => { var pos = L.latLng(w.latitude, w.longitude); var isCovered = lokMasjid.some(m => pos.distanceTo(m) <= currentRadius); // Buat elemen point berbentuk map pin (teardrop) dengan ikon rumah di dalamnya var color = isCovered ? '#16a34a' : '#ef4444'; // Hijau aman, merah di luar var houseIcon = L.divIcon({ html: `
`, className: '', // Kosongkan agar background default leaflet tidak muncul iconSize: [30, 30], iconAnchor: [15, 36], // Titik jangkar di ujung bawah pin popupAnchor: [0, -36] }); var isPenerima = (w.status_bantuan === 'Sudah') ? 'Ya' : 'Belum'; var txtPendidikan = w.pendidikan ? w.pendidikan : 'Tidak diketahui'; var txtPenyakit = (w.riwayat_penyakit && w.riwayat_penyakit.trim().length > 0) ? w.riwayat_penyakit : 'Tidak ada'; var popup = `
Warga: ${w.nama}
Jangkauan: ${isCovered ? 'Aman (Dekat Fasilitas)' : 'Di Luar'}
Tanggungan: ${w.jumlah_kk} Jiwa
Pendidikan: ${txtPendidikan}
Riwayat Sakit: ${txtPenyakit}
Terima Bantuan: ${isPenerima === 'Ya' ? 'Sudah' : 'Belum'}
Histori: ${w.histori_bantuan || 'Tidak ada riwayat bantuan'}
`; L.marker(pos, { icon: houseIcon, draggable: true }) .addTo(layerWarga).bindPopup(popup) .on('dragend', function (e) { updateMarkerCoord(w.id, 'warga', e.target.getLatLng()); }); }); } if (!window.dashboardType || window.dashboardType === 'infra' || window.dashboardType === 'admin') { // Render Jalan (Polyline) data.jalan.forEach(j => { var coords = JSON.parse(j.koordinat); var color = '#ef4444'; // default red if (j.status_jalan === 'Nasional') color = '#ef4444'; // Merah else if (j.status_jalan === 'Provinsi') color = '#eab308'; // Kuning else if (j.status_jalan === 'Kabupaten') color = '#22c55e'; // Hijau var popup = `🛣️ ${j.nama_jalan}
Status: ${j.status_jalan}
Panjang: ${j.panjang_m} meter
`; var polyline = L.polyline(coords, { color: color, weight: 6 }).addTo(layerLines).bindPopup(popup); polyline.db_id = j.id; polyline.db_tipe = 'jalan'; drawnItems.addLayer(polyline); }); // Render Parsil (Polygon) data.parsil.forEach(p => { var coords = JSON.parse(p.koordinat); var popup = `🟩 Kavling
Pemilik: ${p.nama_pemilik}
Status Hak: ${p.status_hak}
`; var polygon = L.polygon(coords, { color: '#2563eb', fillOpacity: 0.4 }).addTo(layerPolygons).bindPopup(popup); polygon.db_id = p.id; polygon.db_tipe = 'parsil'; drawnItems.addLayer(polygon); }); } } function toggleForm() { var j = document.getElementById('jenis').value; ['pelaporan', 'warga', 'masjid', 'spbu', 'jalan', 'parsil'].forEach(f => { var el = document.getElementById('form_' + f); if (el) { el.classList.toggle('hidden', j !== f); } }); } map.on('click', e => { document.getElementById('lat').value = e.latlng.lat; document.getElementById('lng').value = e.latlng.lng; L.popup().setLatLng(e.latlng).setContent("Dipilih: " + e.latlng.lat.toFixed(4) + ", " + e.latlng.lng.toFixed(4)).openOn(map); }); function simpanData() { var j = document.getElementById('jenis').value; var fd = new FormData(); fd.append('action', 'simpan_' + j); fd.append('edit_id', document.getElementById('edit_id').value); var lat = document.getElementById('lat').value; var lng = document.getElementById('lng').value; if (j === 'jalan') { fd.append('nama', document.getElementById('nama_jalan').value); fd.append('status_jalan', document.getElementById('status_jalan').value); fd.append('panjang', document.getElementById('panjang_jalan').value); fd.append('koordinat', document.getElementById('jalan_coords').value); } else if (j === 'parsil') { fd.append('nama', document.getElementById('nama_pemilik').value); fd.append('status_hak', document.getElementById('status_hak').value); fd.append('koordinat', document.getElementById('parsil_coords').value); } else { if (j !== 'geometri' && (!lat || !lng) && document.getElementById('edit_id').value === "0") { Swal.fire({ icon: 'warning', title: 'Oops...', text: 'Silakan klik titik koordinat di peta terlebih dahulu!' }); return; } fd.append('lat', lat); fd.append('lng', lng); if (j === 'pelaporan') { fd.append('nama', document.getElementById('nama_pelapor').value); fd.append('laporan', document.getElementById('isi_laporan').value); } else if (j === 'warga') { fd.append('nama', document.getElementById('nama_kk').value); fd.append('jumlah_kk', document.getElementById('jumlah_kk').value); fd.append('tanggal_lahir', document.getElementById('tanggal_lahir').value); fd.append('pendidikan', document.getElementById('pendidikan').value); fd.append('riwayat_penyakit', document.getElementById('riwayat_penyakit').value); fd.append('histori_bantuan', document.getElementById('histori_bantuan').value); fd.append('alamat', document.getElementById('alamat_warga').value); } else if (j === 'masjid') { fd.append('nama', document.getElementById('nama_masjid').value); fd.append('jenis', document.getElementById('jenis_masjid').value); fd.append('pic', document.getElementById('pic_masjid').value); fd.append('alamat', document.getElementById('alamat_masjid').value); } else if (j === 'spbu') { fd.append('nama', document.getElementById('nama_spbu').value); fd.append('no_hp', document.getElementById('no_hp_spbu').value); fd.append('kategori', document.getElementById('kategori_spbu').value); } } Swal.fire({ title: 'Menyimpan...', allowOutsideClick: false, didOpen: () => { Swal.showLoading() } }); fetch('../api/api.php', { method: 'POST', body: fd }) .then(res => res.json()) .then((res) => { if (res.status === 'error') { Swal.fire('Gagal!', res.message, 'error'); } else { Swal.fire({ icon: 'success', title: 'Berhasil', text: 'Data berhasil disimpan!', timer: 1500, showConfirmButton: false }) .then(() => location.reload()); } }); } function hapusData(id, tipe) { Swal.fire({ title: 'Hapus Data?', text: "Data yang dihapus tidak dapat dikembalikan!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#ef4444', cancelButtonColor: '#94a3b8', confirmButtonText: 'Ya, Hapus!' }).then((result) => { if (result.isConfirmed) { Swal.fire({ title: 'Menghapus...', allowOutsideClick: false, didOpen: () => { Swal.showLoading() } }); var fd = new FormData(); fd.append('action', 'hapus_data'); fd.append('id', id); fd.append('tipe', tipe); fetch('../api/api.php', { method: 'POST', body: fd }) .then(res => res.json()) .then((res) => { if (res.status === 'error') { Swal.fire('Gagal!', res.message, 'error'); } else { location.reload(); } }); } }); } function fokus(lat, lng) { map.flyTo([lat, lng], 18); } function batalEdit() { document.getElementById('edit_id').value = "0"; document.getElementById('btnSimpanUtama').innerText = "Simpan Data"; document.getElementById('btnBatalEdit').style.display = "none"; document.getElementById('lat').value = ""; document.getElementById('lng').value = ""; } function editData(tipe, id) { var item = cachedData[tipe].find(x => x.id == id); if (!item) return; document.getElementById('edit_id').value = id; document.getElementById('jenis').value = tipe; toggleForm(); var panel = document.getElementById('panelInput'); if (panel) panel.classList.remove('collapsed'); if (tipe == 'warga') { document.getElementById('nama_kk').value = item.nama; document.getElementById('jumlah_kk').value = item.jumlah_kk; document.getElementById('tanggal_lahir').value = item.tanggal_lahir || ''; document.getElementById('pendidikan').value = item.pendidikan || ''; document.getElementById('riwayat_penyakit').value = item.riwayat_penyakit || ''; document.getElementById('histori_bantuan').value = item.histori_bantuan || ''; document.getElementById('alamat_warga').value = item.alamat || ''; } else if (tipe == 'pelaporan') { document.getElementById('nama_pelapor').value = item.nama_pelapor; document.getElementById('isi_laporan').value = item.isi_laporan; } else if (tipe == 'spbu') { document.getElementById('nama_spbu').value = item.nama; document.getElementById('no_hp_spbu').value = item.no_hp; document.getElementById('kategori_spbu').value = item.kategori; } else if (tipe == 'masjid') { document.getElementById('nama_masjid').value = item.nama; document.getElementById('pic_masjid').value = item.pic; document.getElementById('alamat_masjid').value = item.alamat; } else if (tipe == 'jalan') { document.getElementById('nama_jalan').value = item.nama_jalan; document.getElementById('status_jalan').value = item.status_jalan; document.getElementById('panjang_jalan').value = item.panjang_m; document.getElementById('jalan_coords').value = item.koordinat; } else if (tipe == 'parsil') { document.getElementById('nama_pemilik').value = item.nama_pemilik; document.getElementById('status_hak').value = item.status_hak; document.getElementById('parsil_coords').value = item.koordinat; } if (item.latitude && item.longitude) { document.getElementById('lat').value = item.latitude; document.getElementById('lng').value = item.longitude; fokus(item.latitude, item.longitude); } document.getElementById('btnSimpanUtama').innerText = "Update Data"; document.getElementById('btnBatalEdit').style.display = "inline-block"; } // INIT loadData(); function updateMarkerCoord(id, tipe, latlng) { var fd = new FormData(); fd.append('action', 'update_koordinat'); fd.append('id', id); fd.append('tipe', tipe); fd.append('lat', latlng.lat); fd.append('lng', latlng.lng); fetch('../api/api.php', { method: 'POST', body: fd }) .then(() => { Swal.fire({ icon: 'success', title: 'Tersimpan', text: 'Posisi titik berhasil digeser!', timer: 1500, showConfirmButton: false, toast: true, position: 'top-end' }); }); }