566 lines
39 KiB
JavaScript
566 lines
39 KiB
JavaScript
// =========================================================================
|
|
// FILE: assets/js/webgis.js
|
|
// Otak Front-End untuk Peta Bansos (Melayani Admin Lokal & Super Admin)
|
|
// =========================================================================
|
|
|
|
// Variabel Global Leaflet
|
|
let tempLat, tempLng;
|
|
const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
|
L.control.zoom({ position: 'topright' }).addTo(map);
|
|
const baseOsm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap' }).addTo(map);
|
|
|
|
// Inisialisasi Ikon
|
|
const redIcon = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34] });
|
|
const greenIcon = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34] });
|
|
const violetIcon = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-violet.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34] });
|
|
const daruratIcon = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-orange.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], className: 'marker-darurat' });
|
|
|
|
const layerIbadah = L.layerGroup().addTo(map);
|
|
const layerPenduduk = L.layerGroup().addTo(map);
|
|
let searchableLayers = [];
|
|
|
|
// --- UTILITAS UMUM ---
|
|
window.konfirmasiLogout = function(urlLogout) {
|
|
if(confirm("Apakah Anda yakin ingin keluar dari sistem?")) { window.location.href = urlLogout; }
|
|
}
|
|
window.tutupModal = function(modalId) { document.getElementById(modalId).style.display = "none"; }
|
|
window.onclick = function(event) { if (event.target.classList.contains('modal')) { event.target.style.display = "none"; } }
|
|
window.toggleOpsiLanjutan = function(id) {
|
|
let el = document.getElementById('opsi-' + id);
|
|
el.style.display = (el.style.display === 'none') ? 'block' : 'none';
|
|
}
|
|
window.cariLokasiSaya = function() { map.locate({setView: true, maxZoom: 16}); }
|
|
map.on('locationfound', function(e) { L.marker(e.latlng).addTo(map).bindPopup("📍 Anda di sini.").openPopup(); });
|
|
|
|
async function getAlamatOtomatis(lat, lng) {
|
|
try {
|
|
const r = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`);
|
|
const d = await r.json();
|
|
if(d && d.address) return [d.address.road, d.address.village||d.address.suburb, d.address.city_district].filter(Boolean).join(", ");
|
|
} catch (e) {} return "";
|
|
}
|
|
|
|
window.cariData = function() {
|
|
const kw = document.getElementById('searchInput').value.toLowerCase().trim(); if (!kw) return;
|
|
let f = false;
|
|
for (let i of searchableLayers) {
|
|
if (i.nama && i.nama.toLowerCase().includes(kw)) { map.flyTo(i.layer.getLatLng(), 18); i.layer.openPopup(); f = true; break; }
|
|
}
|
|
if (!f) alert("Data tidak ditemukan!");
|
|
}
|
|
window.handleEnter = function(e) { if (e.key === 'Enter') cariData(); }
|
|
|
|
|
|
// --- FUNGSI RENDER PETA (DIPANGGIL DARI PHP HTML) ---
|
|
function initWebGIS() {
|
|
// Kalkulasi Jarak & Radius Warga ke Institusi terdekat
|
|
DATA_PENDUDUK.forEach(p => {
|
|
let latlngPenduduk = L.latLng(p.latitude, p.longitude);
|
|
let minDist = Infinity; let closestIbadah = null;
|
|
|
|
DATA_IBADAH.forEach(ib => {
|
|
let dist = latlngPenduduk.distanceTo(L.latLng(ib.latitude, ib.longitude));
|
|
if (dist < minDist) { minDist = dist; closestIbadah = ib; }
|
|
});
|
|
|
|
p.closestIbadah = closestIbadah;
|
|
p.minDist = minDist;
|
|
p.isInside = (closestIbadah && minDist <= closestIbadah.radius);
|
|
|
|
if (p.isInside && closestIbadah) {
|
|
let ibadahIndex = DATA_IBADAH.findIndex(ib => ib.id === closestIbadah.id);
|
|
if(ibadahIndex !== -1) DATA_IBADAH[ibadahIndex].warga_count++;
|
|
}
|
|
});
|
|
|
|
// Render Tempat Ibadah
|
|
DATA_IBADAH.forEach(ib => {
|
|
const marker = L.marker([ib.latitude, ib.longitude], { icon: violetIcon });
|
|
|
|
let ibadahControls = IS_SUPER_ADMIN ? `
|
|
<div class="button-group">
|
|
<button class="btn-edit" onclick="editIbadah(${ib.id}, '${ib.nama}', '${ib.jenis}', ${ib.radius}, '${ib.alamat}', ${ib.latitude}, ${ib.longitude})">Edit Profil</button>
|
|
<button class="btn-delete" onclick="hapusIbadah(${ib.id})">Hapus</button>
|
|
</div>
|
|
` : '';
|
|
|
|
let htmlPopup = `
|
|
<div class="popup-content">
|
|
<div class="popup-title">🕌 ${ib.nama}</div>
|
|
<div style="font-size: 0.9rem;">Tipe: ${ib.jenis || 'Institusi'} <br> Radius: <b>${ib.radius} Meter</b></div>
|
|
<div class="stat-box">
|
|
👥 Warga dalam jangkauan: <span>${ib.warga_count} KK</span><br>
|
|
📦 Total Penyaluran: <span>${ib.total_log || 0} Kali</span>
|
|
</div>
|
|
${ibadahControls}
|
|
</div>
|
|
`;
|
|
marker.bindPopup(htmlPopup);
|
|
marker.addTo(layerIbadah);
|
|
L.circle([ib.latitude, ib.longitude], { radius: ib.radius, color: 'violet', fillOpacity: 0.1 }).addTo(layerIbadah);
|
|
searchableLayers.push({ nama: ib.nama, layer: marker, tipe: 'titik' });
|
|
});
|
|
|
|
// Render Warga Miskin
|
|
DATA_PENDUDUK.forEach(p => {
|
|
let isDarurat = p.is_darurat > 0;
|
|
let idMusibahAktif = p.id_musibah_aktif ? p.id_musibah_aktif : 'null';
|
|
let iconPenduduk = isDarurat ? daruratIcon : (p.isInside ? greenIcon : redIcon);
|
|
|
|
let statusBadge = ""; let statusLabel = "";
|
|
let statusKemiskinan = p.status_kemiskinan || "Data Belum Lengkap";
|
|
|
|
if (statusKemiskinan === "Data Belum Lengkap" || (!p.skor_aset && !p.pengeluaran)) {
|
|
statusBadge = "bg-gray"; statusLabel = "Data Belum Lengkap";
|
|
} else if (statusKemiskinan === "Sangat Miskin") {
|
|
statusBadge = "bg-red"; statusLabel = "SANGAT MISKIN (P1)";
|
|
} else if (statusKemiskinan === "Miskin") {
|
|
statusBadge = "bg-orange"; statusLabel = "MISKIN (P2)";
|
|
} else {
|
|
statusBadge = "bg-yellow"; statusLabel = "RENTAN MISKIN";
|
|
}
|
|
|
|
let radiusText = '';
|
|
let actionButtons = '';
|
|
let isOwnedByAdmin = IS_SUPER_ADMIN ? true : (p.closestIbadah && p.closestIbadah.id == ADMIN_ID_IBADAH && p.isInside);
|
|
|
|
let bannerDarurat = isDarurat ? `
|
|
<div style="background-color: #c0392b; color: white; padding: 6px; text-align: center; border-radius: 4px; font-weight: bold; margin-bottom: 8px; animation: blink-animation 1s infinite;">
|
|
🚨 STATUS DARURAT MUSIBAH
|
|
</div>
|
|
` : '';
|
|
|
|
if (p.isInside || IS_SUPER_ADMIN) {
|
|
radiusText = p.isInside ? `🟢 Masuk area <b>${p.closestIbadah.nama}</b>` : `🔴 Di luar jangkauan (Bypass Super Admin)`;
|
|
|
|
if (isOwnedByAdmin) {
|
|
let btnBantuanHtml = (!IS_SUPER_ADMIN) ? `<button class="btn-action" onclick="bukaFormBantuan(${p.id}, '${statusLabel}', ${p.latitude}, ${p.longitude}, ${idMusibahAktif})">📦 Catat Penyaluran Bantuan</button>` : '';
|
|
|
|
actionButtons = `
|
|
${btnBantuanHtml}
|
|
<button class="btn-danger" onclick="bukaFormMusibah(${p.id}, ${p.latitude}, ${p.longitude})">🚨 Lapor Musibah Darurat</button>
|
|
<button class="btn-info" onclick="bukaModalKeluarga(${p.id}, '${p.nama_kk}')">👨👩👧👦 Data Keluarga & Pelatihan</button>
|
|
|
|
<button class="btn-secondary" onclick="toggleOpsiLanjutan(${p.id})">⚙️ Opsi Lanjutan ▼</button>
|
|
|
|
<div id="opsi-${p.id}" style="display:none; margin-top:10px; border-top:1px dashed #ccc; padding-top:5px;">
|
|
<button class="btn-history" onclick="lihatRiwayat(${p.id}, '${p.nama_kk}', ${p.latitude}, ${p.longitude})">📜 Lihat Riwayat Bantuan</button>
|
|
<button class="btn-training" onclick="lihatLogPelatihan(${p.id}, '${p.nama_kk}', ${p.latitude}, ${p.longitude})">🎓 Lihat Log Pelatihan</button>
|
|
<div class="button-group">
|
|
<button class="btn-edit" onclick="editPenduduk(${p.id}, '${p.nama_kk}', '${p.alamat}', '${p.agama}', ${p.tanggungan}, ${p.pengeluaran}, ${p.lantai||0}, ${p.dinding||0}, ${p.sanitasi||0}, ${p.listrik||0}, ${p.air||0}, ${p.latitude}, ${p.longitude})">Edit Data Induk</button>
|
|
<button class="btn-delete" onclick="hapusPenduduk(${p.id})">Hapus Rumah</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
actionButtons = `<div style="margin-top:10px; padding:8px; background:#eee; color:#7f8c8d; font-size:0.85rem; text-align:center; border-radius:4px;">Wewenang: ${p.closestIbadah.nama}</div>`;
|
|
}
|
|
} else {
|
|
radiusText = `🔴 Di Luar Jangkauan Bantuan`;
|
|
actionButtons = `<div style="margin-top:10px; padding:8px; background:#f9ebea; color:#c0392b; font-size:0.85rem; text-align:center; border-radius:4px;">Tidak masuk radius wewenang tempat ibadah manapun.</div>`;
|
|
}
|
|
|
|
let marker = L.marker([p.latitude, p.longitude], { icon: iconPenduduk }).bindPopup(`
|
|
<div class="popup-content">
|
|
${bannerDarurat}
|
|
<div class="popup-title">🏠 Keluarga ${p.nama_kk}</div>
|
|
${p.alamat || 'Alamat tak diketahui'}<br>
|
|
Tanggungan: ${p.tanggungan || '?'} Jiwa<br>
|
|
<div style="margin-top: 5px;"><span class="badge ${statusBadge}">${statusLabel}</span></div>
|
|
<hr style="margin: 8px 0; border: 0.5px solid #ccc;">
|
|
<small>${radiusText}</small>
|
|
<div style="margin-top: 8px;">${actionButtons}</div>
|
|
</div>
|
|
`);
|
|
marker.addTo(layerPenduduk);
|
|
searchableLayers.push({ nama: p.nama_kk, layer: marker, tipe: 'titik' });
|
|
});
|
|
|
|
// Menampilkan Layer
|
|
loadBasemap(layerPenduduk, layerIbadah);
|
|
}
|
|
|
|
// --- FUNGSI LOAD KECAMATAN JSON ---
|
|
async function loadBasemap(layerPenduduk, layerIbadah) {
|
|
const adminGrp = L.layerGroup().addTo(map);
|
|
try {
|
|
// PERHATIAN: Sesuaikan URL json ini jika Anda memindahkannya ke folder assets
|
|
const dataAdmin = await (await fetch('assets/data/Admin_Kecamatan.json')).json();
|
|
L.geoJSON(dataAdmin, {
|
|
style: function (f) {
|
|
const n = (f.properties.Ket || "").toUpperCase();
|
|
let c = "#ccc";
|
|
if(n.includes("BARAT")) c="#e41a1c"; else if(n.includes("KOTA")) c="#377eb8"; else if(n.includes("SELATAN")) c="#4daf4a";
|
|
else if(n.includes("TENGGARA")) c="#984ea3"; else if(n.includes("TIMUR")) c="#ff7f00"; else if(n.includes("UTARA")) c="#f1c40f";
|
|
return { color: "black", weight: 2, fillOpacity: 0.2, fillColor: c };
|
|
},
|
|
onEachFeature: function (f, layer) {
|
|
const p = new Intl.NumberFormat('id-ID').format(f.properties.penduduk || 0);
|
|
layer.bindPopup(`<div style="text-align: center;"><b>Kecamatan ${f.properties.Ket || '?'}</b><hr>Penduduk: <b>${p} Jiwa</b></div>`);
|
|
}
|
|
}).addTo(adminGrp);
|
|
} catch (e) { console.error("Gagal load geojson kecamatan"); }
|
|
|
|
const lc = L.control.layers(null, { "Batas Kecamatan": adminGrp, "Lokasi Warga": layerPenduduk, "Tempat Ibadah (Ungu)": layerIbadah }, { collapsed: false }).addTo(map);
|
|
document.getElementById('layerControlWrapper').appendChild(lc.getContainer());
|
|
}
|
|
|
|
// --- DRAW CONTROL ---
|
|
const drawControl = new L.Control.Draw({ draw: { polyline: false, polygon: false, circle: false, rectangle: false, circlemarker: false, marker: { icon: redIcon } } }).addTo(map);
|
|
document.getElementById('drawControlWrapper').appendChild(drawControl.getContainer());
|
|
|
|
map.on(L.Draw.Event.CREATED, async function (e) {
|
|
const layer = e.layer;
|
|
tempLat = layer.getLatLng().lat; tempLng = layer.getLatLng().lng;
|
|
let latlngNew = L.latLng(tempLat, tempLng);
|
|
|
|
if (!IS_SUPER_ADMIN) {
|
|
let ibadahAdmin = DATA_IBADAH.find(ib => ib.id == ADMIN_ID_IBADAH);
|
|
if(ibadahAdmin && latlngNew.distanceTo(L.latLng(ibadahAdmin.latitude, ibadahAdmin.longitude)) > ibadahAdmin.radius) {
|
|
alert("Titik ini berada di LUAR radius tempat ibadah Anda. Anda tidak berhak menambahkan warga di area ini.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
layer.bindPopup("<i>Menyiapkan form...</i>").addTo(map).openPopup();
|
|
const alamatAuto = await getAlamatOtomatis(tempLat, tempLng);
|
|
|
|
if (IS_SUPER_ADMIN) {
|
|
let modePilih = document.getElementById('modeTambah').value;
|
|
if(modePilih === 'warga') tampilkanFormPenduduk(layer, 0, '', alamatAuto, 'Islam', 0, '', 0, 0, 0, 0, 0, 'tambah');
|
|
else tampilkanFormIbadah(layer, 0, '', 'Masjid', 500, alamatAuto, 'tambah');
|
|
} else {
|
|
tampilkanFormPenduduk(layer, 0, '', alamatAuto, 'Islam', 0, '', 0, 0, 0, 0, 0, 'tambah');
|
|
}
|
|
});
|
|
|
|
// --- FUNGSI CRUD WARGA ---
|
|
function tampilkanFormPenduduk(layerOrId, id, nama, alamat, agama, tanggungan, pengeluaran, lantai, dinding, sanitasi, listrik, air, mode) {
|
|
tanggungan = tanggungan || 0; pengeluaran = pengeluaran || '';
|
|
const formContent = `
|
|
<div class="popup-content">
|
|
<div class="popup-title">${mode === 'tambah' ? 'Tambah Data Induk' : 'Edit Data Induk'} Warga</div>
|
|
<label>Nama Kepala Keluarga (KK)</label><input type="text" id="p_nama" value="${nama}">
|
|
<label>Alamat Lengkap</label><input type="text" id="p_alamat" value="${alamat}">
|
|
<label>Agama</label>
|
|
<select id="p_agama">
|
|
<option value="Islam" ${agama=='Islam'?'selected':''}>Islam</option><option value="Kristen Katolik" ${agama=='Kristen Katolik'?'selected':''}>Kristen Katolik</option><option value="Kristen Protestan" ${agama=='Kristen Protestan'?'selected':''}>Kristen Protestan</option>
|
|
<option value="Hindu" ${agama=='Hindu'?'selected':''}>Hindu</option><option value="Buddha" ${agama=='Buddha'?'selected':''}>Buddha</option><option value="Konghucu" ${agama=='Konghucu'?'selected':''}>Konghucu</option>
|
|
</select>
|
|
<div style="display:flex; gap: 5px;">
|
|
<div style="flex:1;"><label>Tanggungan</label><input type="number" id="p_tanggungan" value="${tanggungan}" readonly title="Otomatis dihitung"></div>
|
|
<div style="flex:2;"><label>Pengeluaran (Rp)</label><input type="number" id="p_pengeluaran" value="${pengeluaran}"></div>
|
|
</div>
|
|
<hr style="margin: 5px 0;"><label style="color:#d35400;">Observasi Fisik Rumah (PMT)</label>
|
|
<label>Lantai</label><select id="p_lantai"><option value="1" ${lantai==1?'selected':''}>Rendah</option><option value="0" ${lantai==0?'selected':''}>Bagus</option></select>
|
|
<label>Dinding</label><select id="p_dinding"><option value="1" ${dinding==1?'selected':''}>Bambu/Rumbia</option><option value="0" ${dinding==0?'selected':''}>Tembok Bata</option></select>
|
|
<label>Sanitasi</label><select id="p_sanitasi"><option value="1" ${sanitasi==1?'selected':''}>MCK Umum</option><option value="0" ${sanitasi==0?'selected':''}>Milik Sendiri</option></select>
|
|
<label>Listrik</label><select id="p_listrik"><option value="1" ${listrik==1?'selected':''}>Numpang</option><option value="0" ${listrik==0?'selected':''}>Meteran Sendiri</option></select>
|
|
<label>Air</label><select id="p_air"><option value="1" ${air==1?'selected':''}>Tak Terlindung</option><option value="0" ${air==0?'selected':''}>PDAM</option></select>
|
|
<button class="btn-save" onclick="simpanPenduduk(${id}, '${mode}')">Simpan Profil Rumah</button>
|
|
</div>`;
|
|
if (mode === 'tambah') layerOrId.setPopupContent(formContent); else L.popup().setLatLng([tempLat, tempLng]).setContent(formContent).openOn(map);
|
|
}
|
|
|
|
window.editPenduduk = function(id, nama, alamat, agama, tanggungan, pengeluaran, lantai, dinding, sanitasi, listrik, air, lat, lng) {
|
|
tempLat = lat; tempLng = lng; map.closePopup(); tampilkanFormPenduduk(null, id, nama, alamat, agama, tanggungan, pengeluaran, lantai, dinding, sanitasi, listrik, air, 'edit');
|
|
}
|
|
|
|
window.simpanPenduduk = async function(id, mode) {
|
|
let valLantai = parseInt(document.getElementById('p_lantai').value); let valDinding = parseInt(document.getElementById('p_dinding').value);
|
|
let valSanitasi = parseInt(document.getElementById('p_sanitasi').value); let valListrik = parseInt(document.getElementById('p_listrik').value);
|
|
let valAir = parseInt(document.getElementById('p_air').value);
|
|
let skorAset = valLantai + valDinding + valSanitasi + valListrik + valAir;
|
|
let valPengeluaran = parseFloat(document.getElementById('p_pengeluaran').value) || 0; let valTanggungan = parseInt(document.getElementById('p_tanggungan').value) || 1;
|
|
|
|
let pengeluaranKapita = valPengeluaran / (valTanggungan > 0 ? valTanggungan : 1);
|
|
let miskinMoneter = pengeluaranKapita < GARIS_KEMISKINAN_KALBAR;
|
|
let statusKemiskinan = (valPengeluaran > 0) ? (miskinMoneter && skorAset >= 3 ? "Sangat Miskin" : (miskinMoneter ? "Miskin" : "Rentan Miskin")) : "Data Belum Lengkap";
|
|
|
|
const fd = new FormData(); if (mode === 'edit') fd.append('id', id);
|
|
fd.append('latitude', tempLat); fd.append('longitude', tempLng); fd.append('nama_kk', document.getElementById('p_nama').value); fd.append('alamat', document.getElementById('p_alamat').value);
|
|
fd.append('agama', document.getElementById('p_agama').value); fd.append('tanggungan', valTanggungan); fd.append('pengeluaran', valPengeluaran); fd.append('skor_aset', skorAset);
|
|
fd.append('lantai', valLantai); fd.append('dinding', valDinding); fd.append('sanitasi', valSanitasi); fd.append('listrik', valListrik); fd.append('air', valAir); fd.append('status_kemiskinan', statusKemiskinan);
|
|
|
|
const endpoint = mode === 'tambah' ? 'api/penduduk/tambah_penduduk.php' : 'api/penduduk/update_penduduk.php';
|
|
const response = await fetch(endpoint, { method: 'POST', body: fd });
|
|
if ((await response.json()).status === 'success') location.reload(); else alert("Gagal menyimpan data!");
|
|
}
|
|
|
|
window.hapusPenduduk = async function(id) { if (confirm("Hapus rumah dan seluruh keluarga ini?")) { await fetch(`api/penduduk/hapus_penduduk.php?id=${id}`); location.reload(); } }
|
|
|
|
// --- FUNGSI CRUD KELUARGA & PELATIHAN ---
|
|
window.bukaModalKeluarga = async function(id_penduduk, nama_kk) {
|
|
map.closePopup();
|
|
document.getElementById('modalTitle').innerText = "👨👩👧👦 Manajemen Anggota & Pelatihan: Keluarga " + nama_kk;
|
|
document.getElementById('keluargaModal').style.display = "block";
|
|
|
|
let opsiPelatihanHtml = '';
|
|
try {
|
|
const resPelatihan = await fetch('api/transaksi/get_master_pelatihan.php');
|
|
const dataPelatihan = await resPelatihan.json();
|
|
dataPelatihan.forEach(pel => { opsiPelatihanHtml += `<option value="${pel.id}">${pel.nama_pelatihan} (Oleh: ${pel.penyelenggara})</option>`; });
|
|
} catch(e) {}
|
|
|
|
document.getElementById('modalBody').innerHTML = `<p style="text-align:center;">Memuat data anggota...</p>`;
|
|
|
|
const response = await fetch(`api/anggota/get_anggota_keluarga.php?id_penduduk=${id_penduduk}`);
|
|
const dataAnggota = await response.json();
|
|
|
|
let tableRows = '';
|
|
if(dataAnggota.length > 0) {
|
|
dataAnggota.forEach(ang => {
|
|
let aksiHtml = `
|
|
<div class="button-group" style="margin-top:0;">
|
|
<button class="btn-edit" style="width:48%; padding:4px; font-size:0.8rem; margin:0;" onclick="siapkanEditAnggota(${ang.id}, '${ang.nik}', '${ang.nama_lengkap}', '${ang.tanggal_lahir}', '${ang.pendidikan_terakhir}', '${ang.pekerjaan}')">Edit</button>
|
|
<button class="btn-delete" style="width:48%; padding:4px; font-size:0.8rem; margin:0;" onclick="hapusAnggota(${ang.id}, ${id_penduduk}, '${nama_kk}')">Hapus</button>
|
|
</div>`;
|
|
|
|
if(ang.rekomendasi_pelatihan) {
|
|
aksiHtml += `
|
|
<div style="margin-top:5px; background:#e8f8f5; padding:5px; border-radius:4px; border: 1px solid #27ae60;">
|
|
<span style="font-size:0.8rem; color:#27ae60; font-weight:bold;">✨ Rekomendasi Pelatihan</span><br>
|
|
<select id="sel_pelatihan_${ang.id}" style="font-size:0.8rem; width:100%; margin-bottom:5px;">
|
|
${opsiPelatihanHtml ? opsiPelatihanHtml : '<option value="">Tidak ada pelatihan aktif</option>'}
|
|
</select>
|
|
<button class="btn-training" style="padding:4px; font-size:0.8rem; margin:0;" onclick="daftarkanPelatihan(${ang.id}, ${id_penduduk}, '${nama_kk}')">Daftarkan</button>
|
|
</div>`;
|
|
} else { aksiHtml += `<div style="margin-top:5px; font-size:0.75rem; color:#7f8c8d;">❌ Tidak Memenuhi Syarat Pelatihan</div>`; }
|
|
|
|
tableRows += `<tr><td>${ang.nik}</td><td><b>${ang.nama_lengkap}</b><br><small>${ang.umur} Tahun</small></td><td>${ang.pendidikan_terakhir}</td><td>${ang.pekerjaan}</td><td style="width: 200px;">${aksiHtml}</td></tr>`;
|
|
});
|
|
} else { tableRows = `<tr><td colspan="5" style="text-align:center; padding: 15px;">Belum ada anggota keluarga ditambahkan.</td></tr>`; }
|
|
|
|
document.getElementById('modalBody').innerHTML = `
|
|
<div style="max-height: 40vh; overflow-y: auto; margin-bottom: 20px;">
|
|
<table class="history-table" style="width: 100%;">
|
|
<thead><tr><th>NIK</th><th>Nama & Umur</th><th>Pendidikan</th><th>Pekerjaan</th><th style="width: 200px;">Aksi</th></tr></thead>
|
|
<tbody>${tableRows}</tbody>
|
|
</table>
|
|
</div>
|
|
<div id="formContainerAnggota" style="background-color: #f8f9fa; border: 1px solid #dcdde1; padding: 15px; border-radius: 6px;">
|
|
<h4 id="formAnggotaTitle" style="margin-top:0; color:#34495e;">+ Tambah Anggota Keluarga Baru</h4>
|
|
<input type="hidden" id="ak_id_edit" value="">
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
<div style="flex: 1 1 30%;"><label>NIK</label><input type="text" id="ak_nik" maxlength="16"></div>
|
|
<div style="flex: 1 1 30%;"><label>Nama Lengkap</label><input type="text" id="ak_nama"></div>
|
|
<div style="flex: 1 1 30%;"><label>Tanggal Lahir</label><input type="date" id="ak_tgl"></div>
|
|
<div style="flex: 1 1 30%;"><label>Pendidikan Terakhir</label><select id="ak_pendidikan"><option value="Tidak Sekolah">Tidak Sekolah</option><option value="SD Sederajat">SD Sederajat</option><option value="SMP Sederajat">SMP Sederajat</option><option value="SMA Sederajat">SMA Sederajat</option><option value="Diploma">Diploma</option><option value="Sarjana">Sarjana</option></select></div>
|
|
<div style="flex: 1 1 30%;"><label>Pekerjaan Saat Ini</label><input type="text" id="ak_pekerjaan" placeholder="Cth: Tidak Bekerja / Menganggur"></div>
|
|
<div style="flex: 1 1 100%; display:flex; align-items:flex-end; gap: 5px;">
|
|
<button id="btnSimpanAnggota" class="btn-info" style="margin:0; width:auto; padding: 10px 20px;" onclick="simpanAnggota(${id_penduduk}, '${nama_kk}')">Simpan Anggota</button>
|
|
<button id="btnBatalEdit" class="btn-secondary" style="margin:0; width:auto; padding: 10px 20px; display:none;" onclick="bukaModalKeluarga(${id_penduduk}, '${nama_kk}')">Batal Edit</button>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
window.siapkanEditAnggota = function(id, nik, nama, tgl, pendidikan, pekerjaan) {
|
|
document.getElementById('ak_id_edit').value = id; document.getElementById('ak_nik').value = nik; document.getElementById('ak_nama').value = nama; document.getElementById('ak_tgl').value = tgl; document.getElementById('ak_pendidikan').value = pendidikan; document.getElementById('ak_pekerjaan').value = pekerjaan;
|
|
document.getElementById('formAnggotaTitle').innerText = "✏️ Edit Data Anggota Keluarga"; document.getElementById('formContainerAnggota').style.backgroundColor = "#fef9e7"; document.getElementById('formContainerAnggota').style.borderColor = "#f1c40f";
|
|
let btnSimpan = document.getElementById('btnSimpanAnggota'); btnSimpan.innerText = "Update Data Anggota"; btnSimpan.style.backgroundColor = "#f39c12"; document.getElementById('btnBatalEdit').style.display = "block";
|
|
}
|
|
|
|
window.simpanAnggota = async function(id_penduduk, nama_kk) {
|
|
let id_edit = document.getElementById('ak_id_edit').value;
|
|
const fd = new FormData(); if(id_edit) fd.append('id_anggota', id_edit);
|
|
fd.append('id_penduduk', id_penduduk); fd.append('nik', document.getElementById('ak_nik').value); fd.append('nama_lengkap', document.getElementById('ak_nama').value); fd.append('tanggal_lahir', document.getElementById('ak_tgl').value); fd.append('pendidikan_terakhir', document.getElementById('ak_pendidikan').value); fd.append('pekerjaan', document.getElementById('ak_pekerjaan').value);
|
|
|
|
if(!fd.get('nik') || !fd.get('nama_lengkap') || !fd.get('tanggal_lahir') || !fd.get('pekerjaan')) { alert("Lengkapi semua data!"); return; }
|
|
const r = await fetch(id_edit ? 'api/anggota/update_anggota_keluarga.php' : 'api/anggota/tambah_anggota_keluarga.php', { method: 'POST', body: fd });
|
|
if((await r.json()).status === 'success') { alert(id_edit ? "Berhasil diperbarui!" : "Berhasil ditambah!"); bukaModalKeluarga(id_penduduk, nama_kk); } else { alert("Gagal menyimpan."); }
|
|
}
|
|
|
|
window.hapusAnggota = async function(id_anggota, id_penduduk, nama_kk) {
|
|
if(confirm("Yakin ingin menghapus anggota ini?")) {
|
|
const r = await fetch(`api/anggota/hapus_anggota_keluarga.php?id=${id_anggota}`);
|
|
if((await r.json()).status === 'success') bukaModalKeluarga(id_penduduk, nama_kk);
|
|
}
|
|
}
|
|
|
|
window.daftarkanPelatihan = async function(id_anggota, id_penduduk, nama_kk) {
|
|
let id_pel = document.getElementById('sel_pelatihan_' + id_anggota).value; if(!id_pel) return;
|
|
if(confirm("Daftarkan anggota ini ke pelatihan?")) {
|
|
const fd = new FormData(); fd.append('id_anggota', id_anggota); fd.append('id_pelatihan', id_pel);
|
|
const r = await fetch('api/transaksi/tambah_log_pelatihan.php', { method: 'POST', body: fd });
|
|
if((await r.json()).status === 'success') alert("Berhasil didaftarkan!"); else alert("Gagal didaftarkan");
|
|
}
|
|
}
|
|
|
|
// --- FUNGSI BANTUAN & MUSIBAH ---
|
|
window.bukaFormMusibah = function(id_penduduk, lat, lng) {
|
|
map.closePopup();
|
|
L.popup().setLatLng([lat, lng]).setContent(`
|
|
<div class="popup-content"><div class="popup-title" style="color:#c0392b;">🚨 Lapor Musibah Darurat</div>
|
|
<label>Jenis Musibah</label><select id="m_jenis"><option value="Kebakaran">Kebakaran</option><option value="Sakit Keras">Sakit Keras</option><option value="Kematian">Kematian</option><option value="Kecelakaan">Kecelakaan</option><option value="Bencana Alam">Bencana Alam</option><option value="Lainnya">Lainnya</option></select>
|
|
<label>Deskripsi Kejadian</label><textarea id="m_deskripsi" rows="3" style="width:100%; border:1px solid #ccc; border-radius:4px;"></textarea>
|
|
<button class="btn-danger" style="margin-top:10px;" onclick="simpanLaporanMusibah(${id_penduduk})">Simpan Laporan Darurat</button>
|
|
</div>`).openOn(map);
|
|
}
|
|
|
|
window.simpanLaporanMusibah = async function(id_penduduk) {
|
|
let deskripsi = document.getElementById('m_deskripsi').value; if(!deskripsi.trim()) { alert("Isi deskripsi!"); return; }
|
|
const fd = new FormData(); fd.append('id_penduduk', id_penduduk); fd.append('id_ibadah', ADMIN_ID_IBADAH); fd.append('jenis_musibah', document.getElementById('m_jenis').value); fd.append('deskripsi', deskripsi);
|
|
const res = await (await fetch('api/transaksi/tambah_musibah.php', { method: 'POST', body: fd })).json();
|
|
if(res.status === 'success') { alert("Berhasil dicatat!"); location.reload(); } else { alert("Gagal."); }
|
|
}
|
|
|
|
window.bukaFormBantuan = function(id_penduduk, status, lat, lng, id_musibah = null) {
|
|
map.closePopup();
|
|
let beras = 0, minyak = 0, gula = 0, telur = 0, susu = 0, uang = 0;
|
|
if (status.includes("SANGAT MISKIN")) { beras = 10; minyak = 2; gula = 2; telur = 2; susu = 3; uang = 300000; }
|
|
else if (status.includes("MISKIN (P2)")) { beras = 5; minyak = 2; gula = 1; telur = 1; susu = 2; uang = 150000; }
|
|
else if (status.includes("RENTAN MISKIN")) { beras = 5; minyak = 1; gula = 1; telur = 0; susu = 1; uang = 0; }
|
|
|
|
L.popup().setLatLng([lat, lng]).setContent(`
|
|
<div class="popup-content" style="min-width: 280px;">
|
|
<div class="popup-title">Catat Penyaluran Bantuan</div>
|
|
<div style="background-color: #e8f8f5; border-left: 4px solid #27ae60; padding: 8px; margin-bottom: 12px; font-size: 0.85rem; border-radius: 2px;">Rekomendasi Kategori: <b>${status}</b></div>
|
|
<div style="display:flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;">
|
|
<div style="flex: 1 1 45%;"><label>Beras (kg)</label><input type="number" id="b_beras" value="${beras}"></div>
|
|
<div style="flex: 1 1 45%;"><label>Minyak (L)</label><input type="number" id="b_minyak" value="${minyak}"></div>
|
|
<div style="flex: 1 1 45%;"><label>Gula (kg)</label><input type="number" id="b_gula" value="${gula}"></div>
|
|
<div style="flex: 1 1 45%;"><label>Telur (kg)</label><input type="number" id="b_telur" value="${telur}"></div>
|
|
<div style="flex: 1 1 45%;"><label>Susu (Kaleng)</label><input type="number" id="b_susu" value="${susu}"></div>
|
|
<div style="flex: 1 1 45%;"><label>Tunai (Rp)</label><input type="number" id="b_uang" value="${uang}"></div>
|
|
</div>
|
|
<label>Catatan Opsional</label><input type="text" id="b_catatan" placeholder="Cth: Disesuaikan...">
|
|
<button class="btn-save" onclick="simpanLogBantuan(${id_penduduk}, ${id_musibah})">Simpan Log</button>
|
|
</div>`).openOn(map);
|
|
}
|
|
|
|
window.simpanLogBantuan = async function(id_penduduk, id_musibah) {
|
|
const fd = new FormData();
|
|
fd.append('id_penduduk', id_penduduk); fd.append('id_ibadah', ADMIN_ID_IBADAH);
|
|
if (id_musibah !== null && id_musibah !== undefined) { fd.append('id_musibah', id_musibah); }
|
|
fd.append('beras_kg', document.getElementById('b_beras').value); fd.append('minyak_l', document.getElementById('b_minyak').value);
|
|
fd.append('gula_kg', document.getElementById('b_gula').value); fd.append('telur_kg', document.getElementById('b_telur').value);
|
|
fd.append('susu_kaleng', document.getElementById('b_susu').value); fd.append('uang_tunai', document.getElementById('b_uang').value);
|
|
fd.append('catatan', document.getElementById('b_catatan').value);
|
|
|
|
const res = await (await fetch('api/transaksi/tambah_log_bantuan.php', { method: 'POST', body: fd })).json();
|
|
if (res.status === 'success') { alert("Berhasil dicatat!"); location.reload(); } else { alert("Gagal menyimpan."); }
|
|
}
|
|
|
|
// --- FUNGSI MODAL LEBAR (Riwayat Bantuan & Pelatihan) ---
|
|
window.lihatRiwayat = async function(id_penduduk, nama_kk, lat, lng) {
|
|
map.closePopup();
|
|
document.getElementById('rbModalTitle').innerText = "📜 Riwayat Penyaluran Bantuan: Keluarga " + nama_kk;
|
|
document.getElementById('riwayatBantuanModal').style.display = "block";
|
|
document.getElementById('rbModalBody').innerHTML = `<p style="text-align:center;">Memuat data riwayat...</p>`;
|
|
|
|
try {
|
|
const data = await (await fetch(`api/transaksi/get_log_bantuan.php?id_penduduk=${id_penduduk}`)).json();
|
|
let tableRows = '';
|
|
if(data.length > 0) {
|
|
data.forEach(log => {
|
|
let catatanTeks = (log.catatan !== undefined && log.catatan !== null && log.catatan !== '') ? log.catatan : '-';
|
|
tableRows += `
|
|
<tr>
|
|
<td style="width: 15%; font-weight: bold;">${log.tanggal_penyaluran}</td>
|
|
<td style="width: 25%; font-style: italic; color: #555;">${catatanTeks}</td>
|
|
<td style="line-height: 1.8;">
|
|
<b>Beras:</b> ${log.beras_kg} Kg; <b>Minyak:</b> ${log.minyak_l} L; <b>Gula:</b> ${log.gula_kg} Kg <br>
|
|
<b>Telur:</b> ${log.telur_kg} Kg; <b>Susu:</b> ${log.susu_kaleng} Kaleng; <b>Uang:</b> Rp${log.uang_tunai}
|
|
</td>
|
|
</tr>`;
|
|
});
|
|
} else { tableRows = `<tr><td colspan="3" style="text-align:center; padding: 15px;">Belum ada riwayat.</td></tr>`; }
|
|
|
|
document.getElementById('rbModalBody').innerHTML = `
|
|
<div style="max-height: 50vh; overflow-y: auto;">
|
|
<table class="history-table" style="width: 100%;">
|
|
<thead><tr><th>Tanggal</th><th>Catatan / Topik</th><th>Rincian Item Penyaluran</th></tr></thead>
|
|
<tbody>${tableRows}</tbody>
|
|
</table>
|
|
</div>`;
|
|
} catch (e) { document.getElementById('rbModalBody').innerHTML = `<p style="text-align:center; color:red;">Gagal memuat data.</p>`; }
|
|
}
|
|
|
|
window.lihatLogPelatihan = async function(id_penduduk, nama_kk, lat, lng) {
|
|
map.closePopup();
|
|
document.getElementById('rpModalTitle').innerText = "🎓 Riwayat Pelatihan: Keluarga " + nama_kk;
|
|
document.getElementById('riwayatPelatihanModal').style.display = "block";
|
|
document.getElementById('rpModalBody').innerHTML = `<p style="text-align:center;">Memuat data riwayat pelatihan...</p>`;
|
|
|
|
try {
|
|
const data = await (await fetch(`api/transaksi/get_log_pelatihan.php?id_penduduk=${id_penduduk}`)).json();
|
|
let tableRows = '';
|
|
if(data.length > 0) {
|
|
data.forEach(log => {
|
|
let statusColor = log.status_kelulusan === 'Lulus' ? 'color: #27ae60;' : (log.status_kelulusan === 'Sedang Berjalan' ? 'color: #f39c12;' : 'color: #c0392b;');
|
|
tableRows += `
|
|
<tr>
|
|
<td style="width: 35%;"><b>${log.nama_lengkap}</b><br><small style="color:#7f8c8d;">Daftar: ${log.tanggal_daftar}</small></td>
|
|
<td style="width: 45%;">${log.nama_pelatihan}<br><small>Oleh: ${log.penyelenggara}</small></td>
|
|
<td style="width: 20%; font-weight:bold; ${statusColor}">${log.status_kelulusan}</td>
|
|
</tr>`;
|
|
});
|
|
} else { tableRows = `<tr><td colspan="3" style="text-align:center; padding: 15px;">Belum ada riwayat.</td></tr>`; }
|
|
|
|
document.getElementById('rpModalBody').innerHTML = `
|
|
<div style="max-height: 50vh; overflow-y: auto;">
|
|
<table class="history-table" style="width: 100%;">
|
|
<thead><tr><th>Nama Anggota</th><th>Program Pelatihan</th><th>Status</th></tr></thead>
|
|
<tbody>${tableRows}</tbody>
|
|
</table>
|
|
</div>`;
|
|
} catch (error) { document.getElementById('rpModalBody').innerHTML = `<p style="text-align:center; color:red;">Gagal memuat log.</p>`; }
|
|
}
|
|
|
|
// --- FUNGSI SUPER ADMIN KHUSUS ---
|
|
window.updateGarisKemiskinan = async function() {
|
|
let newVal = document.getElementById('input_gk').value;
|
|
if(!newVal || newVal < 100000) { alert("Masukkan nilai yang valid!"); return; }
|
|
const fd = new FormData(); fd.append('garis_kemiskinan', newVal);
|
|
await fetch('api/sistem/update_pengaturan.php', { method: 'POST', body: fd });
|
|
alert("Garis Kemiskinan berhasil diperbarui!"); location.reload();
|
|
}
|
|
|
|
window.bukaModalMasterPelatihan = async function() {
|
|
document.getElementById('masterPelatihanModal').style.display = "block";
|
|
memuatDaftarMasterPelatihan();
|
|
}
|
|
|
|
window.memuatDaftarMasterPelatihan = async function() {
|
|
try {
|
|
const res = await fetch('api/transaksi/get_master_pelatihan.php');
|
|
const data = await res.json();
|
|
let html = `<table class="history-table"><thead><tr><th>Nama Pelatihan</th><th>Penyelenggara</th><th>Periode</th></tr></thead><tbody>`;
|
|
if(data.length > 0) {
|
|
data.forEach(p => {
|
|
html += `<tr><td><b>${p.nama_pelatihan}</b></td><td>${p.penyelenggara}</td><td>${p.tanggal_mulai} s/d ${p.tanggal_selesai}</td></tr>`;
|
|
});
|
|
} else { html += `<tr><td colspan="3" style="text-align:center;">Belum ada program aktif.</td></tr>`; }
|
|
html += `</tbody></table>`;
|
|
document.getElementById('listMasterPelatihan').innerHTML = html;
|
|
} catch(e) { document.getElementById('listMasterPelatihan').innerHTML = "Gagal memuat data."; }
|
|
}
|
|
|
|
window.simpanMasterPelatihan = async function() {
|
|
const fd = new FormData();
|
|
fd.append('nama_pelatihan', document.getElementById('mp_nama').value);
|
|
fd.append('penyelenggara', document.getElementById('mp_instansi').value);
|
|
fd.append('tanggal_mulai', document.getElementById('mp_mulai').value);
|
|
fd.append('tanggal_selesai', document.getElementById('mp_selesai').value);
|
|
fd.append('deskripsi', document.getElementById('mp_deskripsi').value);
|
|
|
|
if(!fd.get('nama_pelatihan') || !fd.get('tanggal_mulai')) { alert("Data tidak lengkap!"); return; }
|
|
const res = await (await fetch('api/transaksi/tambah_master_pelatihan.php', { method: 'POST', body: fd })).json();
|
|
if(res.status === 'success') { alert("Berhasil ditambahkan!"); memuatDaftarMasterPelatihan(); } else { alert("Gagal: " + res.pesan); }
|
|
}
|
|
|
|
function tampilkanFormIbadah(layerOrId, id, nama, jenis, radius, alamat, mode) {
|
|
const formHtml = `<div class="popup-content"><div class="popup-title">${mode === 'tambah' ? 'Tambah' : 'Edit'} Tempat Ibadah</div><label>Nama Institusi</label><input type="text" id="i_nama" value="${nama}"><label>Jenis</label><select id="i_jenis"><option value="Masjid" ${jenis==='Masjid'?'selected':''}>Masjid</option><option value="Gereja Katolik" ${jenis==='Gereja Katolik'?'selected':''}>Gereja Katolik</option><option value="Gereja Protestan" ${jenis==='Gereja Protestan'?'selected':''}>Gereja Protestan</option><option value="Vihara" ${jenis==='Vihara'?'selected':''}>Vihara</option><option value="Pura" ${jenis==='Pura'?'selected':''}>Pura</option><option value="Klenteng" ${jenis==='Klenteng'?'selected':''}>Klenteng</option></select><label>Radius Pelayanan (Meter)</label><input type="number" id="i_radius" value="${radius}"><label>Alamat</label><input type="text" id="i_alamat" value="${alamat}"><button class="btn-save" onclick="simpanIbadah(${id}, '${mode}')">Simpan Institusi</button></div>`;
|
|
if (mode === 'tambah') layerOrId.setPopupContent(formHtml); else L.popup().setLatLng([tempLat, tempLng]).setContent(formHtml).openOn(map);
|
|
}
|
|
window.editIbadah = function(id, nama, jenis, radius, alamat, lat, lng) { tempLat = lat; tempLng = lng; map.closePopup(); tampilkanFormIbadah(null, id, nama, jenis, radius, alamat, 'edit'); }
|
|
window.simpanIbadah = async function(id, mode) {
|
|
const fd = new FormData(); if (mode === 'edit') fd.append('id', id);
|
|
fd.append('latitude', tempLat); fd.append('longitude', tempLng); fd.append('nama', document.getElementById('i_nama').value); fd.append('jenis', document.getElementById('i_jenis').value); fd.append('radius', document.getElementById('i_radius').value); fd.append('alamat', document.getElementById('i_alamat').value);
|
|
await fetch(mode === 'tambah' ? 'api/ibadah/tambah_ibadah.php' : 'api/ibadah/update_ibadah.php', { method: 'POST', body: fd }); location.reload();
|
|
}
|
|
window.hapusIbadah = async function(id) { if (confirm("Hapus Institusi ini?")) { await fetch(`api/ibadah/hapus_ibadah.php?id=${id}`); location.reload(); } }
|
|
|
|
// Memulai Aplikasi setelah semua kode siap
|
|
initWebGIS(); |