912 lines
37 KiB
JavaScript
912 lines
37 KiB
JavaScript
// ========================================
|
||
// FILE: js/main.js
|
||
// Main JavaScript untuk Pemetaan Kemiskinan
|
||
// ========================================
|
||
|
||
// ========== KONFIGURASI ==========
|
||
const MAP_CENTER = [-0.0612, 109.3449]; // Koordinat pusat peta (Pontianak)
|
||
const MAP_ZOOM = 12; // Tingkat perbesaran bawaan peta
|
||
const API_PATH = 'php/'; // Jalur relatif menuju berkas API PHP
|
||
|
||
let map;
|
||
const markers = {
|
||
masyarakat: [],
|
||
ibadah: [],
|
||
spbu: []
|
||
};
|
||
|
||
const layers = {
|
||
jalan: L.layerGroup(),
|
||
polygon: L.layerGroup()
|
||
};
|
||
|
||
let current_tab = 'masyarakat';
|
||
const all_data = {
|
||
masyarakat: [],
|
||
ibadah: [],
|
||
jalan: [],
|
||
polygon: [],
|
||
spbu: []
|
||
};
|
||
|
||
let drawnItems;
|
||
|
||
// ========== ICON MARKER ==========
|
||
const iconMasyarakat = L.icon({
|
||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||
iconSize: [25, 41],
|
||
iconAnchor: [12, 41],
|
||
popupAnchor: [1, -34],
|
||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||
shadowSize: [41, 41]
|
||
});
|
||
|
||
const iconIbadah = L.icon({
|
||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||
iconSize: [25, 41],
|
||
iconAnchor: [12, 41],
|
||
popupAnchor: [1, -34],
|
||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||
shadowSize: [41, 41]
|
||
});
|
||
|
||
// ========== UTILITAS KEAMANAN & FORMAT ==========
|
||
|
||
/**
|
||
* Mengamankan string teks dari potensi serangan injeksi XSS.
|
||
* @param {string} str - String mentah yang akan dibersihkan.
|
||
* @returns {string} String yang aman untuk direndering ke DOM.
|
||
*/
|
||
function escapeHTML(str) {
|
||
if (str === null || str === undefined) return '-';
|
||
return String(str)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
/**
|
||
* Melakukan validasi dasar terhadap respons HTTP Fetch sebelum diproses menjadi JSON.
|
||
* @param {Response} response - Objek respons dari Fetch API.
|
||
* @returns {Promise} Mengembalikan Promise berupa objek JSON jika sukses.
|
||
*/
|
||
function handleFetchResponse(response) {
|
||
if (!response.ok) {
|
||
throw new Error(`Gagal memuat data dari server (Status: ${response.status})`);
|
||
}
|
||
return response.json();
|
||
}
|
||
|
||
// ========== INISIALISASI ==========
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initMap();
|
||
loadData();
|
||
attachEventListeners();
|
||
|
||
// Check initial active tab from landing page
|
||
const activeTab = localStorage.getItem('activeTab');
|
||
const accs = document.querySelectorAll('.accordion');
|
||
|
||
if (activeTab === 'fasilitas') {
|
||
if (accs.length > 0) {
|
||
accs[0].style.display = 'flex'; // Show Fasilitas
|
||
if (accs.length > 1) accs[1].style.display = 'none'; // Hide Sosial
|
||
if (accs.length > 2) accs[2].style.display = 'none'; // Hide Publik
|
||
|
||
accs[0].classList.add('active');
|
||
accs[0].nextElementSibling.style.display = 'block';
|
||
const firstTab = accs[0].nextElementSibling.querySelector('.tab-button');
|
||
if (firstTab) firstTab.click();
|
||
}
|
||
} else if (activeTab === 'sosial') {
|
||
if (accs.length > 1) {
|
||
if (accs.length > 0) accs[0].style.display = 'none'; // Hide Fasilitas
|
||
accs[1].style.display = 'flex'; // Show Sosial
|
||
if (accs.length > 2) accs[2].style.display = 'none'; // Hide Publik
|
||
|
||
accs[1].classList.add('active');
|
||
accs[1].nextElementSibling.style.display = 'block';
|
||
const firstTab = accs[1].nextElementSibling.querySelector('.tab-button');
|
||
if (firstTab) firstTab.click();
|
||
}
|
||
} else if (activeTab === 'publik') {
|
||
// Mode Publik: Sembunyikan semua tombol Add dan Form Input
|
||
document.querySelectorAll('.btn-primary').forEach(btn => {
|
||
if(btn.textContent.includes('+')) btn.style.display = 'none';
|
||
});
|
||
document.querySelector('.leaflet-draw').style.display = 'none'; // Sembunyikan toolbar Leaflet Draw
|
||
|
||
// Buka Layanan Publik (Accordion 3)
|
||
if (accs.length > 0) accs[0].style.display = 'none';
|
||
if (accs.length > 1) accs[1].style.display = 'none';
|
||
if (accs.length > 2) {
|
||
accs[2].style.display = 'flex';
|
||
accs[2].classList.add('active');
|
||
accs[2].nextElementSibling.style.display = 'block';
|
||
const firstTab = accs[2].nextElementSibling.querySelector('.tab-button');
|
||
if (firstTab) firstTab.click();
|
||
}
|
||
|
||
// Tambahkan badge Portal Publik di sidebar jika belum ada
|
||
if (!document.getElementById('badge-publik')) {
|
||
const sidebar = document.getElementById('sidebar');
|
||
const badge = document.createElement('div');
|
||
badge.id = 'badge-publik';
|
||
badge.innerHTML = '<div style="background:#2563eb;color:white;padding:10px;text-align:center;border-radius:5px;margin-bottom:15px;font-weight:bold;">Mode Akses Publik</div>';
|
||
sidebar.insertBefore(badge, sidebar.children[2]);
|
||
}
|
||
}
|
||
});
|
||
|
||
/**
|
||
* Menginisialisasi objek peta Leaflet dan menambahkan event listener klik.
|
||
*/
|
||
function initMap() {
|
||
map = L.map('map').setView(MAP_CENTER, MAP_ZOOM);
|
||
|
||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
maxZoom: 19,
|
||
attribution: '© OpenStreetMap contributors'
|
||
}).addTo(map);
|
||
|
||
// LEAFLET DRAW
|
||
drawnItems = new L.FeatureGroup();
|
||
map.addLayer(drawnItems);
|
||
|
||
const drawControl = new L.Control.Draw({
|
||
edit: { featureGroup: drawnItems },
|
||
draw: {
|
||
polyline: true,
|
||
polygon: true,
|
||
circle: false,
|
||
marker: false,
|
||
circlemarker: false,
|
||
rectangle: false
|
||
}
|
||
});
|
||
map.addControl(drawControl);
|
||
|
||
map.on(L.Draw.Event.CREATED, function (e) {
|
||
const type = e.layerType;
|
||
const layer = e.layer;
|
||
const geojson = JSON.stringify(layer.toGeoJSON());
|
||
|
||
if (type === 'polyline') {
|
||
openModal('modal-form-jalan');
|
||
setTimeout(() => { document.getElementById('geojson-jalan').value = geojson; }, 100);
|
||
} else if (type === 'polygon') {
|
||
openModal('modal-form-polygon');
|
||
setTimeout(() => { document.getElementById('geojson-polygon').value = geojson; }, 100);
|
||
}
|
||
});
|
||
|
||
// Klik peta untuk memunculkan pilihan tambah data
|
||
map.on('click', function(e) {
|
||
const lat = e.latlng.lat.toFixed(8);
|
||
const lng = e.latlng.lng.toFixed(8);
|
||
|
||
document.querySelectorAll('input[name="latitude"]').forEach(el => el.value = lat);
|
||
document.querySelectorAll('input[name="longitude"]').forEach(el => el.value = lng);
|
||
|
||
let buttonsHTML = '';
|
||
if (current_tab === 'masyarakat') {
|
||
buttonsHTML = `<button onclick="map.closePopup(); openModal('modal-form-masyarakat');" style="background:#4CAF50; color:white; border:none; padding:8px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:bold; transition:0.2s;">+ Tambah Warga Miskin</button>`;
|
||
} else if (current_tab === 'ibadah') {
|
||
buttonsHTML = `<button onclick="map.closePopup(); openModal('modal-form-ibadah');" style="background:#2196F3; color:white; border:none; padding:8px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:bold; transition:0.2s;">+ Tambah Rumah Ibadah</button>`;
|
||
} else if (current_tab === 'spbu') {
|
||
buttonsHTML = `<button onclick="map.closePopup(); openModal('modal-form-spbu');" style="background:#ff9800; color:white; border:none; padding:8px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:bold; transition:0.2s;">+ Tambah SPBU</button>`;
|
||
} else {
|
||
buttonsHTML = `<small style="color:#f44336;">Silakan pilih tab yang sesuai (Warga, Ibadah, atau SPBU) di menu kiri terlebih dahulu.</small>`;
|
||
}
|
||
|
||
// Buat konten popup pilihan
|
||
const popupContent = `
|
||
<div style="text-align: center; min-width: 160px; padding: 5px;">
|
||
<b style="font-size: 14px; color: #1e3c72; margin-bottom:5px; display:block;">Pilih Data untuk Ditambah</b>
|
||
<small style="color: #666; display:block; margin-bottom:10px;">${lat}, ${lng}</small>
|
||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||
${buttonsHTML}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
L.popup()
|
||
.setLatLng(e.latlng)
|
||
.setContent(popupContent)
|
||
.openOn(map);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Memicu proses memuat seluruh data dari API secara paralel.
|
||
*/
|
||
function loadData() {
|
||
loadMasyarakat();
|
||
loadRumahIbadah();
|
||
loadJalan();
|
||
loadPolygon();
|
||
loadSPBU();
|
||
}
|
||
|
||
function loadJalan() {
|
||
fetch(API_PATH + 'ambil_fasilitas_jalan.php').then(handleFetchResponse).then(result => {
|
||
if (result.status === 'success') {
|
||
all_data.jalan = result.data || [];
|
||
tampilkanDataJalan(all_data.jalan);
|
||
}
|
||
}).catch(e => console.error(e));
|
||
}
|
||
|
||
function loadPolygon() {
|
||
fetch(API_PATH + 'ambil_fasilitas_polygon.php').then(handleFetchResponse).then(result => {
|
||
if (result.status === 'success') {
|
||
all_data.polygon = result.data || [];
|
||
tampilkanDataPolygon(all_data.polygon);
|
||
}
|
||
}).catch(e => console.error(e));
|
||
}
|
||
|
||
function loadSPBU() {
|
||
fetch(API_PATH + 'ambil_fasilitas_spbu.php').then(handleFetchResponse).then(result => {
|
||
if (result.status === 'success') {
|
||
all_data.spbu = result.data || [];
|
||
tampilkanDataSPBU(all_data.spbu);
|
||
}
|
||
}).catch(e => console.error(e));
|
||
}
|
||
|
||
/**
|
||
* Memuat data masyarakat miskin dari server.
|
||
*/
|
||
function loadMasyarakat() {
|
||
fetch(API_PATH + 'ambil_data_masyarakat.php')
|
||
.then(handleFetchResponse)
|
||
.then(result => {
|
||
if (result.status === 'success') {
|
||
all_data.masyarakat = result.data || [];
|
||
tampilkanDataMasyarakat(all_data.masyarakat);
|
||
} else {
|
||
console.error('Gagal memuat data masyarakat:', result.message);
|
||
}
|
||
})
|
||
.catch(error => console.error('Error saat fetch data masyarakat:', error));
|
||
}
|
||
|
||
/**
|
||
* Memuat data fasilitas rumah ibadah dari server.
|
||
*/
|
||
function loadRumahIbadah() {
|
||
fetch(API_PATH + 'ambil_rumah_ibadah.php')
|
||
.then(handleFetchResponse)
|
||
.then(result => {
|
||
if (result.status === 'success') {
|
||
all_data.ibadah = result.data || [];
|
||
tampilkanDataIbadah(all_data.ibadah);
|
||
} else {
|
||
console.error('Gagal memuat data rumah ibadah:', result.message);
|
||
}
|
||
})
|
||
.catch(error => console.error('Error saat fetch data rumah ibadah:', error));
|
||
}
|
||
|
||
/**
|
||
* Merender penanda peta dan elemen daftar sidebar untuk kategori masyarakat.
|
||
* @param {Array} data - Larik berisi data objek masyarakat.
|
||
*/
|
||
function tampilkanDataMasyarakat(data) {
|
||
markers.masyarakat.forEach(m => map.removeLayer(m));
|
||
markers.masyarakat = [];
|
||
|
||
const list = document.getElementById('list-masyarakat');
|
||
if (!list) return;
|
||
list.innerHTML = '';
|
||
|
||
data.forEach(item => {
|
||
const marker = L.marker(
|
||
[parseFloat(item.latitude), parseFloat(item.longitude)],
|
||
{ icon: iconMasyarakat }
|
||
).addTo(map);
|
||
|
||
const bpjs_class = item.status_bpjs === 'Ya' ? 'status-ya' : (item.status_bpjs === 'Proses' ? 'status-proses' : 'status-tidak');
|
||
|
||
// Penerapan escapeHTML untuk mencegah kerentanan XSS pada popup
|
||
marker.bindPopup(`
|
||
<div style="font-size:13px; min-width:250px;">
|
||
<b>${escapeHTML(item.nama)}</b><br>
|
||
<small style="opacity:0.7;">${escapeHTML(item.alamat)}</small><br>
|
||
Status BPJS: <span class="status-label ${bpjs_class}">${escapeHTML(item.status_bpjs)}</span><br>
|
||
<button onclick="showDetail('${escapeHTML(item.id)}')" style="width:100%; padding:8px; margin-top:10px; background:#1e3c72; color:white; border:none; border-radius:5px; cursor:pointer;">Lihat Detail</button>
|
||
</div>
|
||
`);
|
||
|
||
markers.masyarakat.push(marker);
|
||
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `
|
||
<b>${escapeHTML(item.nama)}</b>
|
||
<small>${escapeHTML(item.kecamatan)} | BPJS: ${escapeHTML(item.status_bpjs)}</small>
|
||
`;
|
||
|
||
li.onclick = () => {
|
||
map.setView([item.latitude, item.longitude], 16);
|
||
marker.openPopup();
|
||
};
|
||
|
||
list.appendChild(li);
|
||
});
|
||
|
||
updateMapVisibility();
|
||
}
|
||
|
||
/**
|
||
* Merender penanda peta dan elemen daftar sidebar untuk kategori rumah ibadah.
|
||
* @param {Array} data - Larik berisi data objek rumah ibadah.
|
||
*/
|
||
function tampilkanDataIbadah(data) {
|
||
markers.ibadah.forEach(m => map.removeLayer(m));
|
||
markers.ibadah = [];
|
||
|
||
const list = document.getElementById('list-ibadah');
|
||
if (!list) return;
|
||
list.innerHTML = '';
|
||
|
||
data.forEach(item => {
|
||
const marker = L.marker(
|
||
[parseFloat(item.latitude), parseFloat(item.longitude)],
|
||
{ icon: iconIbadah }
|
||
).addTo(map);
|
||
|
||
const info_pemimpin = item.nama_pemimpin ? 'Pemimpin: ' + escapeHTML(item.nama_pemimpin) + '<br>' : '';
|
||
const info_alamat = item.alamat ? '<small>' + escapeHTML(item.alamat) + '</small>' : '';
|
||
|
||
marker.bindPopup(`
|
||
<div style="font-size:13px; min-width:250px;">
|
||
<b>${escapeHTML(item.nama)}</b><br>
|
||
<small>${escapeHTML(item.jenis)}</small><br>
|
||
${info_pemimpin}
|
||
${info_alamat}
|
||
</div>
|
||
`);
|
||
|
||
markers.ibadah.push(marker);
|
||
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `
|
||
<b>${escapeHTML(item.nama)}</b>
|
||
<small>${escapeHTML(item.jenis)}</small>
|
||
`;
|
||
|
||
li.onclick = () => {
|
||
map.setView([item.latitude, item.longitude], 16);
|
||
marker.openPopup();
|
||
};
|
||
|
||
list.appendChild(li);
|
||
});
|
||
|
||
updateMapVisibility();
|
||
}
|
||
|
||
function tampilkanDataJalan(data) {
|
||
if (layers.jalan) map.removeLayer(layers.jalan);
|
||
layers.jalan = L.layerGroup().addTo(map);
|
||
const list = document.getElementById('list-jalan');
|
||
if (!list) return;
|
||
list.innerHTML = '';
|
||
|
||
data.forEach(item => {
|
||
try {
|
||
const geojson = JSON.parse(item.geojson);
|
||
const layer = L.geoJSON(geojson, { style: { color: 'blue', weight: 4 } }).addTo(layers.jalan);
|
||
layer.bindPopup(`<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small><br><button onclick="hapusData('jalan', ${item.id})" style="margin-top:5px;cursor:pointer;background:#f44336;color:white;border:none;padding:5px;border-radius:3px;">Hapus</button>`);
|
||
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small>`;
|
||
li.onclick = () => { map.fitBounds(layer.getBounds()); layer.openPopup(); };
|
||
list.appendChild(li);
|
||
} catch(e) { console.error('Invalid GeoJSON', e); }
|
||
});
|
||
|
||
updateMapVisibility();
|
||
}
|
||
|
||
function tampilkanDataPolygon(data) {
|
||
if (layers.polygon) map.removeLayer(layers.polygon);
|
||
layers.polygon = L.layerGroup().addTo(map);
|
||
const list = document.getElementById('list-polygon');
|
||
if (!list) return;
|
||
list.innerHTML = '';
|
||
|
||
data.forEach(item => {
|
||
try {
|
||
const geojson = JSON.parse(item.geojson);
|
||
const layer = L.geoJSON(geojson, { style: { color: 'purple', fillColor: 'purple', fillOpacity: 0.4 } }).addTo(layers.polygon);
|
||
layer.bindPopup(`<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small><br><button onclick="hapusData('polygon', ${item.id})" style="margin-top:5px;cursor:pointer;background:#f44336;color:white;border:none;padding:5px;border-radius:3px;">Hapus</button>`);
|
||
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.tipe)}</small>`;
|
||
li.onclick = () => { map.fitBounds(layer.getBounds()); layer.openPopup(); };
|
||
list.appendChild(li);
|
||
} catch(e) { console.error('Invalid GeoJSON', e); }
|
||
});
|
||
|
||
updateMapVisibility();
|
||
}
|
||
|
||
function tampilkanDataSPBU(data) {
|
||
markers.spbu.forEach(m => map.removeLayer(m));
|
||
markers.spbu = [];
|
||
const list = document.getElementById('list-spbu');
|
||
if (!list) return;
|
||
list.innerHTML = '';
|
||
|
||
data.forEach(item => {
|
||
const is24 = item.is_24_jam === 'Ya';
|
||
// Reuse iconIbadah for green (24h) and iconMasyarakat for red (not 24h)
|
||
const iconSPBU = is24 ? iconIbadah : iconMasyarakat;
|
||
const statusText = is24 ? '<span style="color:green;font-weight:bold;">Buka 24 Jam</span>' : '<span style="color:red;font-weight:bold;">Tidak 24 Jam</span>';
|
||
|
||
const marker = L.marker([parseFloat(item.latitude), parseFloat(item.longitude)], {
|
||
icon: iconSPBU,
|
||
draggable: true
|
||
}).addTo(map);
|
||
|
||
marker.bindPopup(`<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.alamat)}</small><br>${statusText}<br><button onclick="hapusData('spbu', ${item.id})" style="margin-top:5px;cursor:pointer;background:#f44336;color:white;border:none;padding:5px;border-radius:3px;">Hapus</button>`);
|
||
|
||
marker.on('dragend', function(e) {
|
||
const newPos = marker.getLatLng();
|
||
if (confirm('Apakah Anda yakin ingin memperbarui lokasi SPBU ini?')) {
|
||
const fd = new FormData();
|
||
fd.append('id', item.id);
|
||
fd.append('latitude', newPos.lat.toFixed(8));
|
||
fd.append('longitude', newPos.lng.toFixed(8));
|
||
fetch(API_PATH + 'update_lokasi_spbu.php', { method: 'POST', body: fd })
|
||
.then(handleFetchResponse)
|
||
.then(res => {
|
||
if (res.status === 'success') {
|
||
alert('Lokasi berhasil diperbarui');
|
||
item.latitude = newPos.lat;
|
||
item.longitude = newPos.lng;
|
||
} else {
|
||
alert('Gagal: ' + res.message);
|
||
marker.setLatLng([parseFloat(item.latitude), parseFloat(item.longitude)]);
|
||
}
|
||
}).catch(err => {
|
||
alert('Error: ' + err.message);
|
||
marker.setLatLng([parseFloat(item.latitude), parseFloat(item.longitude)]);
|
||
});
|
||
} else {
|
||
marker.setLatLng([parseFloat(item.latitude), parseFloat(item.longitude)]);
|
||
}
|
||
});
|
||
|
||
markers.spbu.push(marker);
|
||
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `<b>${escapeHTML(item.nama)}</b><br><small>${escapeHTML(item.alamat)}</small><br><small>${statusText}</small>`;
|
||
li.onclick = () => { map.setView([item.latitude, item.longitude], 16); marker.openPopup(); };
|
||
list.appendChild(li);
|
||
});
|
||
updateMapVisibility();
|
||
}
|
||
|
||
/**
|
||
* Memuat detail data spesifik masyarakat miskin termasuk riwayat bantuan dan status BPJS.
|
||
* @param {string|number} id - ID unik entitas masyarakat miskin.
|
||
*/
|
||
function showDetail(id) {
|
||
fetch(API_PATH + 'ambil_detail.php?id=' + encodeURIComponent(id))
|
||
.then(handleFetchResponse)
|
||
.then(result => {
|
||
if (result.status === 'success') {
|
||
const data = result.data;
|
||
const masyarakat = data.masyarakat;
|
||
const bantuan = data.bantuan || [];
|
||
const bpjs = data.bpjs;
|
||
|
||
const bpjs_class = masyarakat.status_bpjs === 'Ya' ? 'status-ya' : (masyarakat.status_bpjs === 'Proses' ? 'status-proses' : 'status-tidak');
|
||
|
||
let html = `
|
||
<div class="info-section">
|
||
<h3>👤 Data Pribadi</h3>
|
||
<div class="info-row"><span class="info-label">Nama</span><span class="info-value">${escapeHTML(masyarakat.nama)}</span></div>
|
||
<div class="info-row"><span class="info-label">Tanggal Lahir</span><span class="info-value">${escapeHTML(masyarakat.tanggal_lahir)}</span></div>
|
||
<div class="info-row"><span class="info-label">Jenis Kelamin</span><span class="info-value">${escapeHTML(masyarakat.jenis_kelamin)}</span></div>
|
||
<div class="info-row"><span class="info-label">NIK</span><span class="info-value">${escapeHTML(masyarakat.nik)}</span></div>
|
||
<div class="info-row"><span class="info-label">No. HP</span><span class="info-value">${escapeHTML(masyarakat.no_hp)}</span></div>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h3>🏠 Data Alamat</h3>
|
||
<div class="info-row"><span class="info-label">Alamat</span><span class="info-value">${escapeHTML(masyarakat.alamat)}</span></div>
|
||
<div class="info-row"><span class="info-label">Kelurahan</span><span class="info-value">${escapeHTML(masyarakat.kelurahan)}</span></div>
|
||
<div class="info-row"><span class="info-label">Kecamatan</span><span class="info-value">${escapeHTML(masyarakat.kecamatan)}</span></div>
|
||
<div class="info-row"><span class="info-label">Status Rumah</span><span class="info-value">${escapeHTML(masyarakat.status_rumah)}</span></div>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h3>💼 Data Ekonomi</h3>
|
||
<div class="info-row"><span class="info-label">Pekerjaan</span><span class="info-value">${escapeHTML(masyarakat.pekerjaan)}</span></div>
|
||
<div class="info-row"><span class="info-label">Pendapatan Bulanan</span><span class="info-value">Rp. ${new Intl.NumberFormat('id-ID').format(masyarakat.pendapatan_bulanan || 0)}</span></div>
|
||
<div class="info-row"><span class="info-label">Jumlah Tanggungan</span><span class="info-value">${escapeHTML(masyarakat.jumlah_tanggungan)} orang</span></div>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h3>🏥 Status BPJS</h3>
|
||
<div class="info-row"><span class="info-label">Status BPJS</span><span class="info-value"><span class="status-label ${bpjs_class}">${escapeHTML(masyarakat.status_bpjs)}</span></span></div>
|
||
`;
|
||
|
||
if (bpjs) {
|
||
html += `
|
||
<div class="info-row"><span class="info-label">No. Peserta BPJS</span><span class="info-value">${escapeHTML(bpjs.no_peserta)}</span></div>
|
||
<div class="info-row"><span class="info-label">Jenis BPJS</span><span class="info-value">${escapeHTML(bpjs.jenis_bpjs)}</span></div>
|
||
<div class="info-row"><span class="info-label">Tingkat</span><span class="info-value">${escapeHTML(bpjs.tingkat)}</span></div>
|
||
`;
|
||
}
|
||
|
||
html += `</div>`; // Memperbaiki string penutup tag div yang sebelumnya memicu syntax error
|
||
|
||
if (bantuan.length > 0) {
|
||
html += `<div class="info-section"><h3>💰 Riwayat Bantuan</h3>`;
|
||
bantuan.forEach(b => {
|
||
html += `
|
||
<div style="background:white; padding:8px; margin-bottom:8px; border-left:3px solid #ff9800; border-radius:3px; font-size:12px;">
|
||
<b>${escapeHTML(b.jenis_bantuan)}</b> (${escapeHTML(b.tanggal_bantuan)})<br>
|
||
Nilai: <strong>Rp. ${new Intl.NumberFormat('id-ID').format(b.nilai)}</strong> ${escapeHTML(b.satuan)}
|
||
${b.keterangan ? '<br><small>' + escapeHTML(b.keterangan) + '</small>' : ''}
|
||
</div>
|
||
`;
|
||
});
|
||
html += `</div>`;
|
||
}
|
||
|
||
html += `
|
||
<div class="button-group">
|
||
<button class="btn-primary" onclick="editBantuan(${parseInt(id)})">➕ Tambah Bantuan</button>
|
||
<button class="btn-secondary" onclick="hapusData('masyarakat', ${parseInt(id)})">🗑️ Hapus</button>
|
||
</div>
|
||
`;
|
||
|
||
const modal_body = document.getElementById('modal-body');
|
||
if (modal_body) {
|
||
modal_body.innerHTML = html;
|
||
openModal('modal-detail');
|
||
}
|
||
}
|
||
})
|
||
.catch(error => console.error('Error saat fetch detail data:', error));
|
||
}
|
||
|
||
/**
|
||
* Menghubungkan fungsi penangan kejadian (event handler) ke elemen-elemen HTML DOM.
|
||
*/
|
||
function attachEventListeners() {
|
||
// Accordion Logic
|
||
document.querySelectorAll('.accordion').forEach(acc => {
|
||
acc.addEventListener('click', function() {
|
||
// Close other accordions
|
||
document.querySelectorAll('.accordion').forEach(otherAcc => {
|
||
if (otherAcc !== this) {
|
||
otherAcc.classList.remove('active');
|
||
if (otherAcc.nextElementSibling) {
|
||
otherAcc.nextElementSibling.style.display = "none";
|
||
}
|
||
}
|
||
});
|
||
|
||
// Toggle this one
|
||
this.classList.toggle('active');
|
||
const panel = this.nextElementSibling;
|
||
if (panel.style.display === "block") {
|
||
panel.style.display = "none";
|
||
} else {
|
||
panel.style.display = "block";
|
||
// Optionally auto-click the first tab in this accordion
|
||
const firstTab = panel.querySelector('.tab-button');
|
||
if (firstTab && !firstTab.classList.contains('active')) {
|
||
firstTab.click();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||
btn.addEventListener('click', function(e) {
|
||
const tab = this.dataset.tab;
|
||
switchTab(e, tab);
|
||
});
|
||
});
|
||
|
||
const search_input = document.getElementById('search');
|
||
const f_kecamatan = document.getElementById('filter-kecamatan');
|
||
const f_bpjs = document.getElementById('filter-bpjs');
|
||
const f_jenis = document.getElementById('filter-jenis');
|
||
|
||
if (search_input) search_input.addEventListener('input', filterMasyarakat);
|
||
if (f_kecamatan) f_kecamatan.addEventListener('change', filterMasyarakat);
|
||
if (f_bpjs) f_bpjs.addEventListener('change', filterMasyarakat);
|
||
if (f_jenis) f_jenis.addEventListener('change', filterIbadah);
|
||
|
||
const form_masyarakat = document.getElementById('form-masyarakat');
|
||
const form_ibadah = document.getElementById('form-ibadah');
|
||
const form_jalan = document.getElementById('form-jalan');
|
||
const form_polygon = document.getElementById('form-polygon');
|
||
const form_spbu = document.getElementById('form-spbu');
|
||
|
||
if (form_masyarakat) form_masyarakat.addEventListener('submit', function(e) { e.preventDefault(); submitFormMasyarakat(); });
|
||
if (form_ibadah) form_ibadah.addEventListener('submit', function(e) { e.preventDefault(); submitFormIbadah(); });
|
||
if (form_jalan) form_jalan.addEventListener('submit', function(e) { e.preventDefault(); submitFormJalan(); });
|
||
if (form_polygon) form_polygon.addEventListener('submit', function(e) { e.preventDefault(); submitFormPolygon(); });
|
||
if (form_spbu) form_spbu.addEventListener('submit', function(e) { e.preventDefault(); submitFormSPBU(); });
|
||
|
||
// Shortcut keyboard untuk membuka formulir penambahan data baru
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'n' || e.key === 'N') {
|
||
const active_modal = document.querySelector('.modal.active');
|
||
if (active_modal) return; // Cegah konflik jika modal lain sudah terbuka
|
||
|
||
if (current_tab === 'masyarakat') openModal('modal-form-masyarakat');
|
||
else if (current_tab === 'ibadah') openModal('modal-form-ibadah');
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Mengubah status visibilitas tab kategori aktif (Masyarakat / Rumah Ibadah).
|
||
* @param {Event} e - Objek event klik dari DOM.
|
||
* @param {string} tab - Nama tab penanda tujuan ('masyarakat' / 'ibadah').
|
||
*/
|
||
function switchTab(e, tab) {
|
||
current_tab = tab;
|
||
|
||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
if (e && e.target) {
|
||
e.target.classList.add('active');
|
||
}
|
||
|
||
document.querySelectorAll('.list-container').forEach(container => {
|
||
container.classList.remove('active');
|
||
});
|
||
|
||
const target_tab = document.getElementById('tab-' + tab);
|
||
if (target_tab) target_tab.classList.add('active');
|
||
|
||
updateMapVisibility();
|
||
}
|
||
|
||
function updateMapVisibility() {
|
||
markers.masyarakat.forEach(m => map.removeLayer(m));
|
||
markers.ibadah.forEach(m => map.removeLayer(m));
|
||
markers.spbu.forEach(m => map.removeLayer(m));
|
||
if (layers.jalan) map.removeLayer(layers.jalan);
|
||
if (layers.polygon) map.removeLayer(layers.polygon);
|
||
|
||
if (current_tab === 'masyarakat' || current_tab === 'info-fasilitas') {
|
||
markers.masyarakat.forEach(m => map.addLayer(m));
|
||
}
|
||
if (current_tab === 'ibadah' || current_tab === 'info-fasilitas') {
|
||
markers.ibadah.forEach(m => map.addLayer(m));
|
||
}
|
||
if (current_tab === 'spbu' || current_tab === 'info-fasilitas') {
|
||
markers.spbu.forEach(m => map.addLayer(m));
|
||
}
|
||
if (current_tab === 'jalan' || current_tab === 'info-fasilitas') {
|
||
if (layers.jalan) map.addLayer(layers.jalan);
|
||
}
|
||
if (current_tab === 'polygon' || current_tab === 'info-fasilitas') {
|
||
if (layers.polygon) map.addLayer(layers.polygon);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Memfilter kumpulan data masyarakat berdasarkan kata kunci pencarian, kecamatan, dan status BPJS.
|
||
*/
|
||
function filterMasyarakat() {
|
||
const search_el = document.getElementById('search');
|
||
const kec_el = document.getElementById('filter-kecamatan');
|
||
const bpjs_el = document.getElementById('filter-bpjs');
|
||
|
||
const keyword = search_el ? search_el.value.toLowerCase() : '';
|
||
const kecamatan = kec_el ? kec_el.value : '';
|
||
const bpjs = bpjs_el ? bpjs_el.value : '';
|
||
|
||
const filtered = all_data.masyarakat.filter(item => {
|
||
const item_nama = item.nama ? item.nama.toLowerCase() : '';
|
||
const item_alamat = item.alamat ? item.alamat.toLowerCase() : '';
|
||
|
||
const match_keyword = item_nama.includes(keyword) || item_alamat.includes(keyword);
|
||
const match_kecamatan = !kecamatan || item.kecamatan === kecamatan;
|
||
const match_bpjs = !bpjs || item.status_bpjs === bpjs;
|
||
|
||
return match_keyword && match_kecamatan && match_bpjs;
|
||
});
|
||
|
||
tampilkanDataMasyarakat(filtered);
|
||
}
|
||
|
||
/**
|
||
* Memfilter kumpulan data tempat ibadah berdasarkan jenis kategori tempat ibadah.
|
||
*/
|
||
function filterIbadah() {
|
||
const jenis_el = document.getElementById('filter-jenis');
|
||
const jenis = jenis_el ? jenis_el.value : '';
|
||
|
||
const filtered = all_data.ibadah.filter(item => {
|
||
return !jenis || item.jenis === jenis;
|
||
});
|
||
|
||
tampilkanDataIbadah(filtered);
|
||
}
|
||
|
||
/**
|
||
* Mengirimkan formulir data masyarakat baru ke berkas PHP pemroses menggunakan Fetch FormData.
|
||
*/
|
||
function submitFormMasyarakat() {
|
||
const form = document.getElementById('form-masyarakat');
|
||
if (!form) return;
|
||
const form_data = new FormData(form);
|
||
|
||
fetch(API_PATH + 'simpan_masyarakat.php', {
|
||
method: 'POST',
|
||
body: form_data
|
||
})
|
||
.then(handleFetchResponse)
|
||
.then(result => {
|
||
if (result.status === 'success') {
|
||
showAlert('alert-form', 'Data berhasil disimpan!', 'success');
|
||
form.reset();
|
||
setTimeout(() => {
|
||
closeModal('modal-form-masyarakat');
|
||
loadMasyarakat();
|
||
}, 1500);
|
||
} else {
|
||
showAlert('alert-form', result.message || 'Gagal menyimpan data.', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
showAlert('alert-form', 'Terjadi kesalahan sistem: ' + error.message, 'error');
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Mengirimkan formulir data rumah ibadah baru ke berkas PHP pemroses menggunakan Fetch FormData.
|
||
*/
|
||
function submitFormIbadah() {
|
||
const form = document.getElementById('form-ibadah');
|
||
if (!form) return;
|
||
const form_data = new FormData(form);
|
||
|
||
fetch(API_PATH + 'simpan_rumah_ibadah.php', {
|
||
method: 'POST',
|
||
body: form_data
|
||
})
|
||
.then(handleFetchResponse)
|
||
.then(result => {
|
||
if (result.status === 'success') {
|
||
showAlert('alert-form-ibadah', 'Data berhasil disimpan!', 'success');
|
||
form.reset();
|
||
setTimeout(() => {
|
||
closeModal('modal-form-ibadah');
|
||
loadRumahIbadah();
|
||
}, 1500);
|
||
} else {
|
||
showAlert('alert-form-ibadah', result.message || 'Gagal menyimpan data.', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
showAlert('alert-form-ibadah', 'Terjadi kesalahan sistem: ' + error.message, 'error');
|
||
});
|
||
}
|
||
|
||
function submitFormJalan() {
|
||
const form = document.getElementById('form-jalan');
|
||
if (!form) return;
|
||
const form_data = new FormData(form);
|
||
fetch(API_PATH + 'simpan_fasilitas_jalan.php', { method: 'POST', body: form_data })
|
||
.then(handleFetchResponse).then(result => {
|
||
if (result.status === 'success') {
|
||
showAlert('alert-form-jalan', 'Berhasil disimpan', 'success'); form.reset(); drawnItems.clearLayers();
|
||
setTimeout(() => { closeModal('modal-form-jalan'); loadJalan(); }, 1000);
|
||
} else showAlert('alert-form-jalan', result.message, 'error');
|
||
}).catch(e => showAlert('alert-form-jalan', e.message, 'error'));
|
||
}
|
||
|
||
function submitFormPolygon() {
|
||
const form = document.getElementById('form-polygon');
|
||
if (!form) return;
|
||
const form_data = new FormData(form);
|
||
fetch(API_PATH + 'simpan_fasilitas_polygon.php', { method: 'POST', body: form_data })
|
||
.then(handleFetchResponse).then(result => {
|
||
if (result.status === 'success') {
|
||
showAlert('alert-form-polygon', 'Berhasil disimpan', 'success'); form.reset(); drawnItems.clearLayers();
|
||
setTimeout(() => { closeModal('modal-form-polygon'); loadPolygon(); }, 1000);
|
||
} else showAlert('alert-form-polygon', result.message, 'error');
|
||
}).catch(e => showAlert('alert-form-polygon', e.message, 'error'));
|
||
}
|
||
|
||
function submitFormSPBU() {
|
||
const form = document.getElementById('form-spbu');
|
||
if (!form) return;
|
||
const form_data = new FormData(form);
|
||
fetch(API_PATH + 'simpan_fasilitas_spbu.php', { method: 'POST', body: form_data })
|
||
.then(handleFetchResponse).then(result => {
|
||
if (result.status === 'success') {
|
||
showAlert('alert-form-spbu', 'Berhasil disimpan', 'success'); form.reset();
|
||
setTimeout(() => { closeModal('modal-form-spbu'); loadSPBU(); }, 1000);
|
||
} else showAlert('alert-form-spbu', result.message, 'error');
|
||
}).catch(e => showAlert('alert-form-spbu', e.message, 'error'));
|
||
}
|
||
|
||
/**
|
||
* Mengirimkan instruksi penghapusan data spesifik (masyarakat/ibadah) ke sistem basis data backend.
|
||
* @param {string} tipe - Jenis kategori data yang akan dihapus ('masyarakat' / 'ibadah').
|
||
* @param {string|number} id - ID baris data target.
|
||
*/
|
||
function hapusData(tipe, id) {
|
||
if (!confirm('Apakah Anda yakin ingin menghapus data ini? Tindakan ini tidak dapat dibatalkan.')) {
|
||
return;
|
||
}
|
||
|
||
const form_data = new FormData();
|
||
form_data.append('tipe', tipe);
|
||
form_data.append('id', id);
|
||
|
||
fetch(API_PATH + 'hapus_data.php', {
|
||
method: 'POST',
|
||
body: form_data
|
||
})
|
||
.then(handleFetchResponse)
|
||
.then(result => {
|
||
if (result.status === 'success') {
|
||
alert('Data berhasil dihapus');
|
||
closeModal('modal-detail');
|
||
if (tipe === 'masyarakat') loadMasyarakat();
|
||
else if (tipe === 'ibadah') loadRumahIbadah();
|
||
else if (tipe === 'jalan') loadJalan();
|
||
else if (tipe === 'polygon') loadPolygon();
|
||
else if (tipe === 'spbu') loadSPBU();
|
||
} else {
|
||
alert('Gagal menghapus: ' + (result.message || 'Error tidak diketahui'));
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert('Terjadi kesalahan jaringan atau sistem: ' + error.message);
|
||
});
|
||
}
|
||
|
||
function editBantuan(id) {
|
||
alert('Fitur tambah bantuan dengan ID ' + id + ' akan ditambahkan segera');
|
||
}
|
||
|
||
function openModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
if (modal) modal.classList.add('active');
|
||
}
|
||
|
||
function closeModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
if (modal) modal.classList.remove('active');
|
||
}
|
||
|
||
function showAlert(elementId, message, type) {
|
||
const alert_el = document.getElementById(elementId);
|
||
if (!alert_el) return;
|
||
|
||
alert_el.textContent = message;
|
||
alert_el.className = 'alert active alert-' + type;
|
||
|
||
setTimeout(() => {
|
||
alert_el.classList.remove('active');
|
||
}, 5000);
|
||
}
|
||
|
||
// Menutup jendela modal otomatis jika pengguna mengklik area luar modal (overlay)
|
||
document.addEventListener('click', function(e) {
|
||
if (e.target && e.target.classList.contains('modal')) {
|
||
e.target.classList.remove('active');
|
||
}
|
||
}); |