feat: implement full authentication system and CRUD APIs for users, rumah ibadah, and penduduk miskin with database updates.

This commit is contained in:
Syariffullah
2026-06-11 08:10:42 +07:00
parent 5585c34c5c
commit b77d4f6869
48 changed files with 1439 additions and 1442 deletions
+113 -220
View File
@@ -83,203 +83,30 @@
rightPanelActions.innerHTML = '';
switch (type) {
case 'spbu': renderSpbuPanel(); break;
case 'jalan': renderJalanPanel(); break;
case 'parsil': renderParsilPanel(); break;
case 'ibadah': renderIbadahPanel(); break;
case 'miskin': renderMiskinPanel(); break;
}
}
// ==========================
// ===== SPBU Panel =========
// ==========================
function renderSpbuPanel() {
rightPanelTitle.textContent = '⛽ SPBU';
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelAddSpbu">
<i class="fas fa-plus"></i> Tambah SPBU
</button>`;
document.getElementById('btnPanelAddSpbu').addEventListener('click', () => {
window.activateAddMode('spbu');
});
const items = [];
spbuLayer.eachLayer(l => { if (l.spbuData) items.push(l.spbuData); });
if (!items.length) {
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data SPBU</div>';
return;
}
rightPanelBody.innerHTML = '';
items.forEach(d => {
const bdg = d.is_24_jam
? '<span class="data-card-badge badge-green">24 Jam</span>'
: '<span class="data-card-badge badge-red">Tidak 24 Jam</span>';
const card = document.createElement('div');
card.className = 'data-card';
card.innerHTML = `
<div class="data-card-main">
<div class="data-card-icon card-icon-spbu">⛽</div>
<div class="data-card-info">
<div class="data-card-name">${escHtml(d.nama)}</div>
<div class="data-card-sub">📞 ${d.no_wa || '-'}</div>
${bdg}
</div>
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
</div>`;
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditSpbuModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteSpbu(d.id); });
card.addEventListener('click', () => flyToSpbu(d.id));
rightPanelBody.appendChild(card);
});
}
function flyToSpbu(id) {
spbuLayer.eachLayer(l => {
if (l.spbuData && l.spbuData.id == id) { map.setView(l.getLatLng(), 17); l.openPopup(); }
});
}
// ==========================
// ===== Jalan Panel ========
// ==========================
function renderJalanPanel() {
rightPanelTitle.textContent = '🛣️ Jalan';
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelAddJalan">
<i class="fas fa-plus"></i> Tambah Jalan
</button>`;
document.getElementById('btnPanelAddJalan').addEventListener('click', () => {
window.activateDraw('polyline');
});
const items = [];
jalanLayer.eachLayer(l => { if (l.jalanData) items.push(l.jalanData); });
if (!items.length) {
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data jalan</div>';
return;
}
const statusBadge = {
'Nasional' : '<span class="data-card-badge badge-red">Nasional</span>',
'Provinsi' : '<span class="data-card-badge badge-blue">Provinsi</span>',
'Kabupaten': '<span class="data-card-badge badge-green">Kabupaten</span>',
};
rightPanelBody.innerHTML = '';
items.forEach(d => {
const km = (d.panjang / 1000).toFixed(2);
const card = document.createElement('div');
card.className = 'data-card';
card.innerHTML = `
<div class="data-card-main">
<div class="data-card-icon card-icon-jalan">🛣️</div>
<div class="data-card-info">
<div class="data-card-name">${escHtml(d.nama)}</div>
<div class="data-card-sub">📏 ${km} km</div>
${statusBadge[d.status] || ''}
</div>
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
</div>`;
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditJalanModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteJalan(d.id); });
card.addEventListener('click', () => flyToJalan(d.id));
rightPanelBody.appendChild(card);
});
}
function flyToJalan(id) {
jalanLayer.eachLayer(l => {
if (l.jalanData && l.jalanData.id == id && l.getBounds) {
map.fitBounds(l.getBounds(), { padding: [40, 40] }); l.openPopup();
}
});
}
// ==========================
// ===== Parsil Panel =======
// ==========================
function renderParsilPanel() {
rightPanelTitle.textContent = '🗺️ Parsil Tanah';
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelAddParsil">
<i class="fas fa-plus"></i> Tambah Parsil
</button>`;
document.getElementById('btnPanelAddParsil').addEventListener('click', () => {
window.activateDraw('polygon');
});
const items = [];
parsilLayer.eachLayer(l => { if (l.parsilData) items.push(l.parsilData); });
if (!items.length) {
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data parsil</div>';
return;
}
const statusBadge = {
'SHM': '<span class="data-card-badge badge-green">SHM</span>',
'HGB': '<span class="data-card-badge badge-blue">HGB</span>',
'HGU': '<span class="data-card-badge badge-yellow">HGU</span>',
'HP' : '<span class="data-card-badge badge-orange">HP</span>',
};
rightPanelBody.innerHTML = '';
items.forEach(d => {
const luas = d.luas >= 10000
? (d.luas / 10000).toFixed(2) + ' ha'
: d.luas.toFixed(0) + ' m²';
const card = document.createElement('div');
card.className = 'data-card';
card.innerHTML = `
<div class="data-card-main">
<div class="data-card-icon card-icon-parsil">🗺️</div>
<div class="data-card-info">
<div class="data-card-name">${escHtml(d.nama || 'Tanpa Nama')}</div>
<div class="data-card-sub">📐 ${luas}</div>
${statusBadge[d.status] || ''}
</div>
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
</div>`;
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditParsilModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteParsil(d.id); });
card.addEventListener('click', () => flyToParsil(d.id));
rightPanelBody.appendChild(card);
});
}
function flyToParsil(id) {
parsilLayer.eachLayer(l => {
if (l.parsilData && l.parsilData.id == id && l.getBounds) {
map.fitBounds(l.getBounds(), { padding: [40, 40] }); l.openPopup();
}
});
}
// ==========================
// ===== Ibadah Panel =======
// ==========================
function renderIbadahPanel() {
rightPanelTitle.textContent = '🕌 Rumah Ibadah';
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelAddIbadah">
<i class="fas fa-plus"></i> Tambah Ibadah
</button>`;
document.getElementById('btnPanelAddIbadah').addEventListener('click', () => {
window.activateAddMode('rumah_ibadah');
});
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
if (isAdmin) {
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelAddIbadah">
<i class="fas fa-plus"></i> Tambah Ibadah
</button>`;
document.getElementById('btnPanelAddIbadah').addEventListener('click', () => {
window.activateAddMode('rumah_ibadah');
});
} else {
rightPanelActions.innerHTML = '';
}
const items = [];
rumahIbadahLayer.eachLayer(l => {
@@ -296,6 +123,15 @@
const emoji = IBADAH_EMOJI[d.jenis] || '🕌';
const card = document.createElement('div');
card.className = 'data-card';
let actionButtons = '';
if (isAdmin) {
actionButtons = `
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
`;
}
card.innerHTML = `
<div class="data-card-main">
<div class="data-card-icon card-icon-ibadah">${emoji}</div>
@@ -303,13 +139,12 @@
<div class="data-card-name">${escHtml(d.nama)}</div>
<div class="data-card-sub">${escHtml(d.jenis || 'Masjid')} • 📡 ${d.radius} m</div>
</div>
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
${actionButtons}
</div>`;
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditIbadahModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteIbadah(d.id); });
if (isAdmin) {
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditIbadahModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteIbadah(d.id); });
}
card.addEventListener('click', () => flyToIbadah(d.id));
rightPanelBody.appendChild(card);
});
@@ -328,31 +163,34 @@
// ==========================
function renderMiskinPanel() {
rightPanelTitle.textContent = '🏠 Penduduk Miskin';
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelTambahMiskin">
<i class="fas fa-plus"></i> Tambah
</button>
<button class="btn-panel-import" id="btnPanelImportMiskin">
<i class="fas fa-file-import"></i> Impor
</button>`;
const canManage = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola'));
if (canManage) {
rightPanelActions.innerHTML = `
<button class="btn-panel-add" id="btnPanelTambahMiskin">
<i class="fas fa-plus"></i> Tambah
</button>
<button class="btn-panel-import" id="btnPanelImportMiskin">
<i class="fas fa-file-import"></i> Impor
</button>`;
// Tombol Tambah: buka form via right-click popup di peta
document.getElementById('btnPanelTambahMiskin').addEventListener('click', () => {
// Aktifkan mode: klik kiri di peta = tambah miskin
window.currentAddMode = 'miskin_click';
document.getElementById('map').style.cursor = 'crosshair';
if (window.cursorTooltip) {
window.cursorTooltip.textContent = 'Klik di peta untuk tentukan lokasi penduduk miskin';
}
});
document.getElementById('btnPanelTambahMiskin').addEventListener('click', () => {
window.currentAddMode = 'miskin_click';
document.getElementById('map').style.cursor = 'crosshair';
if (window.cursorTooltip) {
window.cursorTooltip.textContent = 'Klik di peta untuk tentukan lokasi penduduk miskin';
}
});
document.getElementById('btnPanelImportMiskin').addEventListener('click', () => {
if (typeof window.openImportMiskinModal === 'function') {
window.openImportMiskinModal();
} else {
alert('Fitur impor belum siap.');
}
});
document.getElementById('btnPanelImportMiskin').addEventListener('click', () => {
if (typeof window.openImportMiskinModal === 'function') {
window.openImportMiskinModal();
} else {
alert('Fitur impor belum siap.');
}
});
} else {
rightPanelActions.innerHTML = '';
}
const all = [];
pendudukMiskinLayer.eachLayer(l => { if (l.miskinData) all.push(l.miskinData); });
@@ -394,6 +232,7 @@
}
function buildMiskinCard(d, terurus) {
const canManage = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola'));
const iconCls = terurus ? 'card-icon-miskin-in' : 'card-icon-miskin-out';
const badge = d.kategori_bantuan === 'Makan'
? '<span class="data-card-badge badge-orange">Bantuan Makan</span>'
@@ -402,6 +241,41 @@
const card = document.createElement('div');
card.className = 'data-card';
card.dataset.miskinId = d.id;
let actionButtons = '';
if (canManage) {
actionButtons = `
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
`;
}
let fotoHtml = '';
if (d.foto_rumah || d.foto_kk) {
fotoHtml += `<div class="expand-row-photos" style="display:flex; gap:10px; margin-top:8px; justify-content: space-around;">`;
if (d.foto_rumah) {
fotoHtml += `
<div style="text-align:center;">
<span style="font-size:10px; display:block; font-weight:500; color:#555;">Foto Rumah</span>
<a href="uploads/${d.foto_rumah}" target="_blank">
<img src="uploads/${d.foto_rumah}" style="width:80px; height:60px; object-fit:cover; border-radius:4px; border:1px solid #ddd; margin-top:3px;" />
</a>
</div>`;
}
if (d.foto_kk) {
fotoHtml += `
<div style="text-align:center;">
<span style="font-size:10px; display:block; font-weight:500; color:#555;">Foto KK</span>
<a href="uploads/${d.foto_kk}" target="_blank">
<img src="uploads/${d.foto_kk}" style="width:80px; height:60px; object-fit:cover; border-radius:4px; border:1px solid #ddd; margin-top:3px;" />
</a>
</div>`;
}
fotoHtml += `</div>`;
}
card.innerHTML = `
<div class="data-card-main">
<div class="data-card-icon ${iconCls}">🏠</div>
@@ -410,22 +284,22 @@
<div class="data-card-sub">👨‍👩‍👧 ${d.jumlah_jiwa || '-'} jiwa</div>
${badge}
</div>
<div class="data-card-actions">
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
</div>
${actionButtons}
</div>
<div class="data-card-expand">
<div class="expand-row"><span>Kategori Bantuan</span><span>${escHtml(d.kategori_bantuan)}</span></div>
<div class="expand-row"><span>Jumlah Jiwa</span><span>${d.jumlah_jiwa || '-'}</span></div>
<div class="expand-row"><span>Status</span><span>${terurus ? 'Terurus ✅' : 'Tidak Terurus ⚠️'}</span></div>
${fotoHtml}
<button class="btn-log-bantuan" data-id="${d.id}" data-nama="${escHtml(d.nama)}">
<i class="fas fa-history"></i> Lihat Log Bantuan
</button>
</div>`;
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditMiskinModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteMiskin(d.id); });
if (canManage) {
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditMiskinModal(d.id); });
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteMiskin(d.id); });
}
card.querySelector('.btn-log-bantuan').addEventListener('click', (e) => { e.stopPropagation(); openLogBantuan(d.id, d.nama); });
card.querySelector('.data-card-main').addEventListener('click', () => {
@@ -473,6 +347,18 @@
logTitle.textContent = `Log Bantuan — ${nama}`;
logModal.classList.add('show');
const isAuth = !!window.currentUser;
const formContainer = document.querySelector('#logBantuanModal .modal-body > div:last-child');
const modalFooter = document.querySelector('#logBantuanModal .modal-footer');
if (isAuth) {
if (formContainer) formContainer.style.display = 'block';
if (modalFooter) modalFooter.style.display = 'flex';
} else {
if (formContainer) formContainer.style.display = 'none';
if (modalFooter) modalFooter.style.display = 'none';
}
// Isi dropdown ibadah
logIbadahSel.innerHTML = '<option value="">-- Pilih Rumah Ibadah --</option>';
if (typeof ibadahDataList !== 'undefined') {
@@ -484,6 +370,13 @@
});
}
if (window.currentUser && window.currentUser.role === 'pengelola') {
logIbadahSel.value = window.currentUser.ibadah_id;
logIbadahSel.disabled = true;
} else {
logIbadahSel.disabled = false;
}
// Set tanggal default hari ini
document.getElementById('logTanggal').valueAsDate = new Date();
fetchLogBantuan(miskinId);