first commit

This commit is contained in:
2026-06-11 21:18:48 +07:00
parent 14ef9997c2
commit 2747d616c7
45 changed files with 7381 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
/* ============================================================
js/anggota.js — Profil individu anggota keluarga v2.1
============================================================ */
window.labelHubungan = {
kepala_keluarga:'Kepala Keluarga', istri:'Istri/Suami', anak:'Anak',
orang_tua:'Orang Tua', saudara:'Saudara', lainnya:'Lainnya'
};
window.labelStatusHidup = {
hidup:'Hidup', meninggal:'Meninggal', pindah:'Pindah', menikah_keluar:'Menikah/Keluar'
};
window.labelPendidikan = {
belum_sekolah:'Belum Sekolah', paud:'PAUD/TK', sd:'SD', smp:'SMP', sma:'SMA/SMK',
kuliah:'Kuliah/S1', pesantren:'Pesantren', pelatihan:'Pelatihan Vokasi',
bekerja:'Bekerja', tidak_sekolah:'Tidak Sekolah'
};
window.getSaranPendidikan = function (umur) {
if (umur === null || umur < 0) return null;
if (umur < 5) return { saran: 'PAUD/TK atau belum sekolah' };
if (umur <= 12) return { saran: 'SD (usia ' + umur + ' tahun)' };
if (umur <= 15) return { saran: 'SMP (usia ' + umur + ' tahun)' };
if (umur <= 18) return { saran: 'SMA/SMK (usia ' + umur + ' tahun)' };
if (umur <= 24) return { saran: 'Kuliah / Pelatihan Vokasi (usia ' + umur + ' tahun)' };
return { saran: 'Pelatihan / Bekerja (usia ' + umur + ' tahun)' };
};
window.validasiPendidikan = function (umur, pendidikan) {
if (umur === null || !pendidikan) return null;
var map = {
belum_sekolah:[0,5], paud:[3,7], sd:[6,13], smp:[12,16],
sma:[15,19], kuliah:[18,30], pesantren:[6,25],
pelatihan:[15,99], bekerja:[17,99], tidak_sekolah:[0,99]
};
var range = map[pendidikan];
if (!range) return null;
if (umur < range[0] || umur > range[1]) {
var saran = window.getSaranPendidikan(umur);
return { cocok: false, pesan: 'Umur ' + umur + ' th tidak sesuai dengan "' +
(window.labelPendidikan[pendidikan]||pendidikan) + '".' +
(saran ? ' Disarankan: ' + saran.saran : '') };
}
return { cocok: true, pesan: null };
};
window._hitungUmur = function (tglLahir) {
if (!tglLahir) return null;
var today = new Date(), lahir = new Date(tglLahir);
if (isNaN(lahir.getTime())) return null;
var u = today.getFullYear() - lahir.getFullYear();
var m = today.getMonth() - lahir.getMonth();
if (m < 0 || (m === 0 && today.getDate() < lahir.getDate())) u--;
return u;
};
/* ── Buka modal tambah / edit ── */
window.openTambahAnggotaModal = function (kemiskinanId, namaKK) {
document.getElementById('anggota-kem-id').value = kemiskinanId;
document.getElementById('anggota-edit-id').value = '';
document.getElementById('anggota-nama').value = '';
document.getElementById('anggota-tgl-lahir').value = '';
document.getElementById('anggota-jk').value = 'laki_laki';
document.getElementById('anggota-hubungan').value = 'anak';
document.getElementById('anggota-status').value = 'hidup';
document.getElementById('anggota-pendidikan').value = 'belum_sekolah';
document.getElementById('anggota-catatan').value = '';
var s = document.getElementById('anggota-saran');
if (s) { s.textContent = ''; s.className = 'saran-box'; s.style.display = 'none'; }
document.getElementById('modal-anggota-title').textContent = 'Tambah Anggota — ' + window.escapeHtml(namaKK);
var modal = document.getElementById('modal-anggota');
modal.style.display = 'flex';
document.getElementById('anggota-nama').focus();
modal.onclick = function (e) { if (e.target === this) this.style.display = 'none'; };
window._bindAnggotaEvents();
document.getElementById('btn-submit-anggota').onclick = window._submitTambahAnggota;
};
window.openEditAnggotaModal = function (a, namaKK) {
document.getElementById('anggota-kem-id').value = a.kemiskinan_id;
document.getElementById('anggota-edit-id').value = a.id;
document.getElementById('anggota-nama').value = a.nama;
document.getElementById('anggota-tgl-lahir').value = a.tanggal_lahir || '';
document.getElementById('anggota-jk').value = a.jenis_kelamin;
document.getElementById('anggota-hubungan').value = a.hubungan_keluarga;
document.getElementById('anggota-status').value = a.status_hidup;
document.getElementById('anggota-pendidikan').value = a.pendidikan;
document.getElementById('anggota-catatan').value = a.catatan_khusus || '';
document.getElementById('modal-anggota-title').textContent = 'Edit Anggota — ' + window.escapeHtml(namaKK);
var modal = document.getElementById('modal-anggota');
modal.style.display = 'flex';
document.getElementById('anggota-nama').focus();
modal.onclick = function (e) { if (e.target === this) this.style.display = 'none'; };
window._bindAnggotaEvents();
window._updateAnggotaSaran();
document.getElementById('btn-submit-anggota').onclick = window._submitEditAnggota;
};
window._bindAnggotaEvents = function () {
var tgl = document.getElementById('anggota-tgl-lahir');
var pend = document.getElementById('anggota-pendidikan');
if (!tgl._bound) {
tgl.addEventListener('change', window._updateAnggotaSaran);
pend.addEventListener('change', window._updateAnggotaSaran);
tgl._bound = true;
}
};
window._updateAnggotaSaran = function () {
var tgl = document.getElementById('anggota-tgl-lahir').value;
var pend = document.getElementById('anggota-pendidikan').value;
var s = document.getElementById('anggota-saran');
if (!s) return;
var umur = window._hitungUmur(tgl);
if (umur === null) { s.style.display = 'none'; s.textContent = ''; return; }
s.style.display = 'block';
var v = window.validasiPendidikan(umur, pend);
if (!v) { s.className = 'saran-box info'; s.textContent = 'Umur: ' + umur + ' tahun'; return; }
if (v.cocok) {
var sar = window.getSaranPendidikan(umur);
s.className = 'saran-box ok';
s.textContent = '✓ Umur ' + umur + ' tahun — pendidikan sesuai.';
} else {
s.className = 'saran-box warn';
s.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i> ' + v.pesan;
}
};
window._submitTambahAnggota = async function () {
var btn = document.getElementById('btn-submit-anggota');
btn.disabled = true; btn.textContent = 'Menyimpan...';
try {
await window.postForm('php/create_anggota.php', window._collectAnggotaForm());
document.getElementById('modal-anggota').style.display = 'none';
window.showToast('Anggota berhasil ditambahkan.', 'success');
if (window._currentDetailItem) window.openDetailPanel(window._currentDetailItem);
} catch (err) { window.showToast('Gagal: ' + err.message, 'error'); }
finally { btn.disabled = false; btn.textContent = 'Simpan'; }
};
window._submitEditAnggota = async function () {
var btn = document.getElementById('btn-submit-anggota');
btn.disabled = true; btn.textContent = 'Menyimpan...';
var payload = window._collectAnggotaForm();
payload.id = document.getElementById('anggota-edit-id').value;
try {
await window.postForm('php/update_anggota.php', payload);
document.getElementById('modal-anggota').style.display = 'none';
window.showToast('Data anggota diperbarui.', 'success');
if (window._currentDetailItem) window.openDetailPanel(window._currentDetailItem);
} catch (err) { window.showToast('Gagal: ' + err.message, 'error'); }
finally { btn.disabled = false; btn.textContent = 'Simpan'; }
};
window._collectAnggotaForm = function () {
return {
kemiskinan_id: document.getElementById('anggota-kem-id').value,
nama: document.getElementById('anggota-nama').value.trim(),
tanggal_lahir: document.getElementById('anggota-tgl-lahir').value,
jenis_kelamin: document.getElementById('anggota-jk').value,
hubungan_keluarga: document.getElementById('anggota-hubungan').value,
status_hidup: document.getElementById('anggota-status').value,
pendidikan: document.getElementById('anggota-pendidikan').value,
catatan_khusus: document.getElementById('anggota-catatan').value.trim()
};
};
window.deleteAnggota = async function (id, nama) {
if (!confirm('Hapus anggota "' + nama + '"?')) return;
try {
await window.postForm('php/delete_anggota.php', { id });
window.showToast('Anggota dihapus.', 'success');
if (window._currentDetailItem) window.openDetailPanel(window._currentDetailItem);
} catch (err) { window.showToast('Gagal: ' + err.message, 'error'); }
};
/* ── Render tab anggota ── */
window.renderTabAnggota = async function (item) {
var container = document.getElementById('detail-tab-anggota');
if (!container) return;
container.innerHTML = '<div class="loading">Memuat data anggota...</div>';
var data = await window.fetchJson('php/read_anggota.php?kemiskinan_id=' + item.id, []);
container.innerHTML = '';
if (window.appAuth.role === 'pengurus_masjid') {
var btnAdd = document.createElement('button');
btnAdd.className = 'btn btn-add';
btnAdd.innerHTML = '+ Tambah Anggota';
btnAdd.onclick = function () { window.openTambahAnggotaModal(item.id, item.nama_kepala_keluarga); };
container.appendChild(btnAdd);
}
if (!data.length) {
var empty = document.createElement('div');
empty.className = 'empty';
empty.textContent = 'Belum ada data anggota.';
container.appendChild(empty);
return;
}
data.forEach(function (a) {
var umur = a.umur !== null ? parseInt(a.umur) : null;
var umurTxt = umur !== null ? umur + ' tahun' : '—';
var tglTxt = a.tgl_lahir_fmt || '—';
var val = umur !== null ? window.validasiPendidikan(umur, a.pendidikan) : null;
var card = document.createElement('div');
card.className = 'anggota-card ' + a.status_hidup;
/* Status chip */
var chipColors = { hidup:'chip-green', meninggal:'chip-gray', pindah:'chip-amber', menikah_keluar:'chip-purple' };
var chipColor = chipColors[a.status_hidup] || 'chip-gray';
card.innerHTML =
'<div class="ang-head">' +
'<span class="ang-name">' + window.escapeHtml(a.nama) + '</span>' +
'<span class="chip ' + chipColor + '">' + (window.labelStatusHidup[a.status_hidup] || a.status_hidup) + '</span>' +
'</div>' +
'<div class="ang-row"><span class="ang-lbl">Hubungan</span>' + (window.labelHubungan[a.hubungan_keluarga] || a.hubungan_keluarga) + '</div>' +
'<div class="ang-row"><span class="ang-lbl">Lahir</span>' + tglTxt + ' <em class="umur-em">(' + umurTxt + ')</em></div>' +
'<div class="ang-row"><span class="ang-lbl">JK</span>' + (a.jenis_kelamin === 'laki_laki' ? 'Laki-laki' : 'Perempuan') + '</div>' +
'<div class="ang-row"><span class="ang-lbl">Pendidikan</span>' + (window.labelPendidikan[a.pendidikan] || a.pendidikan) + '</div>' +
(val && !val.cocok ? '<div class="ang-warn"><i class="fa-solid fa-triangle-exclamation"></i> ' + window.escapeHtml(val.pesan) + '</div>' : '') +
(a.catatan_khusus ? '<div class="ang-row"><span class="ang-lbl">Catatan</span><em>' + window.escapeHtml(a.catatan_khusus) + '</em></div>' : '') +
(a.updated_at_fmt ? '<div class="ang-row"><span class="ang-lbl">Update</span><em class="umur-em">' + a.updated_at_fmt + '</em></div>' : '');
if (window.appAuth.role === 'pengurus_masjid') {
var acts = document.createElement('div');
acts.className = 'ang-acts';
var btnE = document.createElement('button');
btnE.className = 'btn btn-edit'; btnE.innerHTML = '<i class="fa-solid fa-pen"></i> Edit';
btnE.onclick = (function(aa){ return function(){ window.openEditAnggotaModal(aa, item.nama_kepala_keluarga); }; })(a);
var btnH = document.createElement('button');
btnH.className = 'btn btn-delete'; btnH.textContent = 'Hapus';
btnH.onclick = (function(aa){ return function(){ window.deleteAnggota(aa.id, aa.nama); }; })(a);
acts.appendChild(btnE); acts.appendChild(btnH);
card.appendChild(acts);
}
container.appendChild(card);
});
};
+192
View File
@@ -0,0 +1,192 @@
/* ============================================================
js/auth.js — Login / Logout / Role management v2.1
============================================================ */
window.appAuth = { loggedIn: false, nama: '', role: '' };
window.checkAuthState = async function () {
try {
var res = await fetch('php/check_auth.php', { cache: 'no-store' });
var data = await res.json();
window.appAuth.loggedIn = !!data.loggedIn;
window.appAuth.nama = data.nama || '';
window.appAuth.role = data.role || '';
} catch (e) {
window.appAuth = { loggedIn: false, nama: '', role: '' };
}
window.renderAuthBar();
if (!window.appAuth.loggedIn) {
window.openLoginModal();
}
};
/* ── Render auth area di navbar ── */
window.renderAuthBar = function () {
var area = document.getElementById('auth-area');
if (!area) return;
area.innerHTML = '';
/* Clean up old nav buttons */
var navMenus = document.getElementById('nav-menus');
if (navMenus) {
['btn-pesan-nav','btn-laporan-mgr-nav'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.remove();
});
}
if (!window.appAuth.loggedIn) {
/* Show Masuk button */
var btnMasuk = document.createElement('button');
btnMasuk.className = 'nav-btn';
btnMasuk.id = 'btn-masuk-nav';
btnMasuk.innerHTML = '<i class="fa-solid fa-right-to-bracket"></i> Masuk';
btnMasuk.onclick = window.openLoginModal;
area.appendChild(btnMasuk);
} else {
/* Role chip */
var chip = document.createElement('span');
var isPimpinan = window.appAuth.role === 'pimpinan_daerah';
chip.className = 'role-chip ' + (isPimpinan ? 'pimpinan' : 'pengurus');
chip.innerHTML = isPimpinan
? '<i class="fa-solid fa-crown"></i> Pimpinan'
: '<i class="fa-solid fa-mosque"></i> Pengurus';
area.appendChild(chip);
var name = document.createElement('span');
name.className = 'auth-name';
name.textContent = window.appAuth.nama;
area.appendChild(name);
/* Pengurus: add Pesan & Laporan buttons to nav-menus */
if (window.appAuth.role === 'pengurus_masjid' && navMenus) {
var btnMsg = document.createElement('button');
btnMsg.className = 'nav-btn msg-btn';
btnMsg.id = 'btn-pesan-nav';
btnMsg.innerHTML = '<i class="fa-solid fa-message"></i> Pesan <span class="lumina-badge" id="nav-badge-pesan" style="display:none; background:var(--lumina-primary);"></span>';
btnMsg.onclick = window.openPesanPanel;
navMenus.appendChild(btnMsg);
var btnLap = document.createElement('button');
btnLap.className = 'nav-btn mgr-btn';
btnLap.id = 'btn-laporan-mgr-nav';
btnLap.innerHTML = '<i class="fa-solid fa-clipboard-list"></i> Laporan <span class="lumina-badge" id="nav-badge-laporan" style="display:none; background:var(--lumina-error);"></span>';
btnLap.onclick = window.openLaporanPanel;
navMenus.appendChild(btnLap);
}
var btnLogout = document.createElement('button');
btnLogout.className = 'nav-btn logout-btn';
btnLogout.id = 'btn-logout-nav';
btnLogout.innerHTML = '<i class="fa-solid fa-arrow-right-from-bracket"></i> Keluar';
btnLogout.onclick = window.doLogout;
area.appendChild(btnLogout);
}
window.updateToolbarForRole();
if (window.pollLaporanNotification) window.pollLaporanNotification();
if (window.pollPesanNotification) window.pollPesanNotification();
};
/* ── Role-based toolbar control ── */
window.updateToolbarForRole = function () {
var editBtn = document.getElementById('luminaModeEdit');
var menuKeluarga = document.getElementById('menu-tambah-keluarga');
var menuIbadah = document.getElementById('menu-tambah-ibadah');
var isPengurus = window.appAuth && window.appAuth.loggedIn && window.appAuth.role === 'pengurus_masjid';
if (isPengurus) {
if (editBtn) editBtn.style.display = 'block';
if (menuKeluarga) menuKeluarga.style.display = 'flex';
if (menuIbadah) menuIbadah.style.display = 'flex';
} else {
if (editBtn) editBtn.style.display = 'none';
if (menuKeluarga) menuKeluarga.style.display = 'none';
if (menuIbadah) menuIbadah.style.display = 'none';
if (window.appState && window.appState.mode === 'edit') window.setMode('view');
}
};
/* ── Open login modal ── */
window.openLoginModal = function () {
var overlay = document.getElementById('modal-login');
if (!overlay) return;
document.getElementById('login-username').value = '';
document.getElementById('login-password').value = '';
document.getElementById('login-error').textContent = '';
overlay.style.display = 'flex';
document.getElementById('login-username').focus();
// Do not allow closing
};
/* ── Login submit ── */
window.doLogin = async function (e) {
e.preventDefault();
var btn = document.getElementById('btn-submit-login');
var errEl = document.getElementById('login-error');
var uname = document.getElementById('login-username').value.trim();
var pass = document.getElementById('login-password').value;
if (!uname || !pass) {
errEl.textContent = 'Username dan password wajib diisi.';
errEl.style.display = 'block';
return;
}
btn.disabled = true; btn.textContent = 'Masuk...';
errEl.textContent = '';
errEl.style.display = 'none';
try {
var res = await fetch('php/login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ username: uname, password: pass })
});
var data = await res.json();
if (data.success) {
window.appAuth = { loggedIn: true, nama: data.nama, role: data.role };
document.getElementById('modal-login').style.display = 'none';
window.renderAuthBar();
window.loadAllData();
window.showToast('Selamat datang, ' + data.nama + '!', 'success');
} else {
errEl.textContent = data.error || 'Login gagal.';
errEl.style.display = 'block';
}
} catch (err) {
errEl.textContent = 'Koneksi gagal: ' + err.message;
errEl.style.display = 'block';
} finally {
btn.disabled = false; btn.innerHTML = 'Masuk Sekarang <i class="fa-solid fa-arrow-right"></i>';
}
};
/* ── Logout ── */
window.doLogout = async function () {
await fetch('php/logout.php', { cache: 'no-store' });
window.appAuth = { loggedIn: false, nama: '', role: '' };
window.renderAuthBar();
if (window.appState && window.appState.mode === 'edit') window.setMode('view');
window.loadAllData();
window.openLoginModal();
window.showToast('Anda telah keluar.', 'info');
};
/* ── Toggle Password Visibility ── */
window.togglePassword = function () {
var passInput = document.getElementById('login-password');
var eyeIcon = document.getElementById('login-password-eye');
if (passInput.type === 'password') {
passInput.type = 'text';
eyeIcon.classList.remove('fa-eye');
eyeIcon.classList.add('fa-eye-slash');
} else {
passInput.type = 'password';
eyeIcon.classList.remove('fa-eye-slash');
eyeIcon.classList.add('fa-eye');
}
};
+154
View File
@@ -0,0 +1,154 @@
/* ============================================================
js/bantuan.js — Histori bantuan v2.1
============================================================ */
window.labelJenisBantuan = {
sembako:'Sembako', uang:'Uang/Dana', pendidikan:'Pendidikan',
kesehatan:'Kesehatan', pakaian:'Pakaian', pelatihan:'Pelatihan', lainnya:'Lainnya'
};
window.labelStatusBantuan = {
tersalur:'Tersalur', belum:'Belum Tersalur', batal:'Dibatalkan'
};
/* Render tab bantuan */
window.renderTabBantuan = async function (item) {
var container = document.getElementById('detail-tab-bantuan');
if (!container) return;
container.innerHTML = '<div class="loading">Memuat histori bantuan...</div>';
var data = await window.fetchJson('php/read_bantuan.php?kemiskinan_id=' + item.id, []);
container.innerHTML = '';
/* Alert jatuh tempo */
var jt = data.filter(function(d){ return parseInt(d.jatuh_tempo) === 1; });
if (jt.length) {
var alert = document.createElement('div');
alert.className = 'bantuan-alert';
alert.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i> ' + jt.length + ' bantuan sudah jatuh tempo dan belum disalurkan!';
container.appendChild(alert);
}
if (window.appAuth.role === 'pengurus_masjid') {
var btnAdd = document.createElement('button');
btnAdd.className = 'btn btn-add';
btnAdd.innerHTML = '+ Catat Bantuan Baru';
btnAdd.onclick = function () { window.openTambahBantuanModal(item.id, item.nama_kepala_keluarga); };
container.appendChild(btnAdd);
}
if (!data.length) {
var empty = document.createElement('div');
empty.className = 'empty'; empty.textContent = 'Belum ada histori bantuan.';
container.appendChild(empty); return;
}
data.forEach(function (b) {
var jmlTxt = b.jumlah_bantuan ? b.jumlah_bantuan + ' ' + (b.satuan || '') : '—';
var isJt = parseInt(b.jatuh_tempo) === 1;
var card = document.createElement('div');
card.className = 'b-card ' + b.status_penyaluran + (isJt ? ' jatuh' : '');
var chipColors = { tersalur:'chip-green', belum:'chip-amber', batal:'chip-gray' };
card.innerHTML =
'<div class="b-head">' +
'<span class="b-jenis">' + (window.labelJenisBantuan[b.jenis_bantuan] || b.jenis_bantuan) + '</span>' +
'<span class="chip ' + (chipColors[b.status_penyaluran]||'chip-gray') + '">' + (window.labelStatusBantuan[b.status_penyaluran]||b.status_penyaluran) + '</span>' +
'</div>' +
'<div class="b-desc">' + window.escapeHtml(b.deskripsi_bantuan) + '</div>' +
'<div class="b-meta">📅 ' + (b.tanggal_fmt||'—') + ' · ' + window.escapeHtml(jmlTxt) + (b.nama_masjid ? ' · ' + window.escapeHtml(b.nama_masjid) : '') + '</div>' +
(b.periode_fmt ? '<div class="b-periode' + (isJt ? ' late' : '') + '">Berikutnya: ' + b.periode_fmt + (isJt ? ' — Jatuh Tempo!' : '') + '</div>' : '') +
(b.dicatat_oleh ? '<div class="b-meta">Dicatat: ' + window.escapeHtml(b.dicatat_oleh) + '</div>' : '');
if (window.appAuth.role === 'pengurus_masjid') {
var acts = document.createElement('div');
acts.className = 'b-acts';
if (b.status_penyaluran === 'belum') {
var btnS = document.createElement('button');
btnS.className = 'btn btn-salur'; btnS.textContent = '✓ Tandai Tersalur';
btnS.onclick = (function(bid){ return async function(){
try {
await window.postForm('php/update_bantuan_status.php', { id: bid, status_penyaluran: 'tersalur' });
window.showToast('Bantuan ditandai tersalur.', 'success');
window.renderTabBantuan(item);
} catch(e){ window.showToast('Gagal: ' + e.message, 'error'); }
}; })(b.id);
acts.appendChild(btnS);
var btnBt = document.createElement('button');
btnBt.className = 'btn btn-delete'; btnBt.textContent = 'Batalkan';
btnBt.onclick = (function(bid){ return async function(){
if (!confirm('Batalkan bantuan ini?')) return;
try {
await window.postForm('php/update_bantuan_status.php', { id: bid, status_penyaluran: 'batal' });
window.showToast('Bantuan dibatalkan.', 'info');
window.renderTabBantuan(item);
} catch(e){ window.showToast('Gagal: ' + e.message, 'error'); }
}; })(b.id);
acts.appendChild(btnBt);
}
var btnH = document.createElement('button');
btnH.className = 'btn btn-delete'; btnH.innerHTML = '<i class="fa-solid fa-trash"></i>';
btnH.onclick = (function(bid){ return async function(){
if (!confirm('Hapus catatan bantuan ini?')) return;
try {
await window.postForm('php/delete_bantuan.php', { id: bid });
window.showToast('Catatan dihapus.', 'success');
window.renderTabBantuan(item);
} catch(e){ window.showToast('Gagal: ' + e.message, 'error'); }
}; })(b.id);
acts.appendChild(btnH);
card.appendChild(acts);
}
container.appendChild(card);
});
};
/* Modal tambah bantuan */
window.openTambahBantuanModal = function (kemiskinanId, namaKK) {
document.getElementById('bantuan-kem-id').value = kemiskinanId;
document.getElementById('bantuan-jenis').value = 'sembako';
document.getElementById('bantuan-deskripsi').value = '';
document.getElementById('bantuan-jumlah').value = '';
document.getElementById('bantuan-satuan').value = '';
document.getElementById('bantuan-tanggal').value = new Date().toISOString().split('T')[0];
document.getElementById('bantuan-periode').value = '';
document.getElementById('bantuan-status').value = 'belum';
document.getElementById('bantuan-dicatat').value = window.appAuth.nama || '';
document.getElementById('modal-bantuan-title').textContent = 'Catat Bantuan — ' + namaKK;
var modal = document.getElementById('modal-bantuan');
modal.style.display = 'flex';
document.getElementById('bantuan-deskripsi').focus();
modal.onclick = function (e) { if (e.target === this) this.style.display = 'none'; };
document.getElementById('btn-submit-bantuan').onclick = async function () {
var btn = this;
btn.disabled = true; btn.textContent = 'Menyimpan...';
var desc = document.getElementById('bantuan-deskripsi').value.trim();
if (!desc) { window.showToast('Deskripsi wajib diisi.', 'error'); btn.disabled=false; btn.textContent='Simpan Bantuan'; return; }
var payload = {
kemiskinan_id: document.getElementById('bantuan-kem-id').value,
jenis_bantuan: document.getElementById('bantuan-jenis').value,
deskripsi_bantuan: desc,
jumlah_bantuan: document.getElementById('bantuan-jumlah').value,
satuan: document.getElementById('bantuan-satuan').value.trim(),
tanggal_bantuan: document.getElementById('bantuan-tanggal').value,
periode_berikutnya:document.getElementById('bantuan-periode').value,
status_penyaluran: document.getElementById('bantuan-status').value,
dicatat_oleh: document.getElementById('bantuan-dicatat').value.trim()
};
try {
await window.postForm('php/create_bantuan.php', payload);
modal.style.display = 'none';
window.showToast('Bantuan berhasil dicatat.', 'success');
if (window._currentDetailItem && window._currentDetailItem.id === parseInt(payload.kemiskinan_id)) {
window.renderTabBantuan(window._currentDetailItem);
}
} catch (err) { window.showToast('Gagal: ' + err.message, 'error'); }
finally { btn.disabled=false; btn.textContent='Simpan Bantuan'; }
};
};
+330
View File
@@ -0,0 +1,330 @@
/* ============================================================
js/laporan.js — Sistem pelaporan warga v2.2
============================================================ */
window.labelKategoriLaporan = {
sakit:'Sakit / Butuh Bantuan Medis', kematian:'Kematian',
kelahiran:'Kelahiran Bayi Baru', pernikahan:'Pernikahan',
pindah:'Pindah Tempat', darurat_ekonomi:'Darurat Ekonomi', lainnya:'Lainnya'
};
window.labelStatusLaporan = {
menunggu:'Menunggu', diproses:'Diproses', selesai:'Selesai', ditolak:'Ditolak'
};
var _kategoriEmoji = {
sakit:'🤒', kematian:'💔', kelahiran:'👶', pernikahan:'💍',
pindah:'📦', darurat_ekonomi:'🆘', lainnya:'📣'
};
/* ─────────────────────────────────────────────────────
Floating detail box
───────────────────────────────────────────────────── */
window.openLaporanDetail = function (laporan) {
var fl = document.getElementById('laporan-detail-float');
if (!fl) return;
var status = laporan.status_laporan || 'menunggu';
var emoji = _kategoriEmoji[laporan.kategori] || '📣';
var label = window.labelKategoriLaporan[laporan.kategori] || laporan.kategori;
var statusLabel = window.labelStatusLaporan[status] || status;
/* Status bar colour */
var bar = document.getElementById('ldf-status-bar');
bar.className = 'ldf-status-bar ' + status;
/* Icon */
var icon = document.getElementById('ldf-icon');
icon.textContent = emoji;
icon.className = 'ldf-icon ' + status;
/* Title / subtitle */
document.getElementById('ldf-title').textContent = label;
document.getElementById('ldf-subtitle').textContent = '● ' + statusLabel;
document.getElementById('ldf-subtitle').style.color = {
menunggu: 'var(--amber)', diproses: 'var(--blue)',
selesai: 'var(--green)', ditolak: 'var(--text-muted)'
}[status] || 'var(--text-muted)';
/* Deskripsi */
document.getElementById('ldf-deskripsi').textContent = laporan.deskripsi || '(tidak ada deskripsi)';
/* KK terkait */
var sectionKK = document.getElementById('ldf-section-kk');
var badgeKK = document.getElementById('ldf-kk-badge');
if (laporan.nama_kepala_keluarga) {
badgeKK.innerHTML = '<i class="fa-solid fa-location-dot"></i> ' + laporan.nama_kepala_keluarga;
sectionKK.style.display = '';
} else {
sectionKK.style.display = 'none';
}
/* Info rows */
document.getElementById('ldf-pelapor').textContent = laporan.nama_pelapor || '-';
var rowWA = document.getElementById('ldf-row-wa');
if (laporan.nomor_wa) {
document.getElementById('ldf-wa').textContent = laporan.nomor_wa;
rowWA.style.display = '';
} else {
rowWA.style.display = 'none';
}
document.getElementById('ldf-waktu').textContent = laporan.created_fmt || '-';
var rowDitangani = document.getElementById('ldf-row-ditangani');
if (laporan.ditangani_oleh) {
document.getElementById('ldf-ditangani').textContent = laporan.ditangani_oleh;
rowDitangani.style.display = '';
} else {
rowDitangani.style.display = 'none';
}
/* Action buttons (pengurus only) */
var footer = document.getElementById('ldf-footer');
footer.innerHTML = '';
if (window.appAuth && window.appAuth.role === 'pengurus_masjid' && status !== 'selesai' && status !== 'ditolak') {
footer.style.display = '';
var statuses = [
{ val: 'diproses', label: '<i class="fa-solid fa-rotate"></i> Proses', cls: 'btn-proses' },
{ val: 'selesai', label: '<i class="fa-solid fa-check"></i> Selesai', cls: 'btn-selesai' },
{ val: 'ditolak', label: '<i class="fa-solid fa-xmark"></i> Tolak', cls: 'btn-tolak' }
];
statuses.forEach(function (s) {
if (s.val === status) return;
var btn = document.createElement('button');
btn.className = 'btn ' + s.cls;
btn.innerHTML = s.label;
btn.onclick = (function (lid, sv) {
return async function () {
btn.disabled = true;
try {
await window.postForm('php/update_laporan_status.php', {
id: lid, status_laporan: sv, ditangani_oleh: window.appAuth.nama
});
window.showToast('Status diperbarui ke: ' + window.labelStatusLaporan[sv], 'success');
window.closeLaporanDetail();
window.loadLaporan(document.getElementById('laporan-filter-status').value);
} catch (e) {
window.showToast('Gagal: ' + e.message, 'error');
btn.disabled = false;
}
};
})(laporan.id, s.val);
footer.appendChild(btn);
});
} else {
footer.style.display = 'none';
}
/* Close button */
document.getElementById('ldf-close').onclick = window.closeLaporanDetail;
/* Show with animation */
fl.style.display = 'flex';
};
window.closeLaporanDetail = function () {
var fl = document.getElementById('laporan-detail-float');
if (fl) fl.style.display = 'none';
};
/* ── Form laporan publik ── */
window.openLaporanPublikModal = function (kemiskinanId, namaKK) {
var modal = document.getElementById('modal-laporan-publik');
document.getElementById('laporan-kem-id').value = kemiskinanId || '';
document.getElementById('laporan-nama-pelapor').value = '';
document.getElementById('laporan-nomor-wa').value = '';
document.getElementById('laporan-kategori').value = 'lainnya';
document.getElementById('laporan-deskripsi').value = '';
document.getElementById('laporan-judul-kkname').textContent = namaKK
? 'Laporkan Kondisi: ' + namaKK
: 'Laporkan Kondisi Warga';
modal.style.display = 'flex';
document.getElementById('laporan-nama-pelapor').focus();
modal.onclick = function (e) { if (e.target === this) this.style.display = 'none'; };
};
/* ── Tombol submit laporan dipanggil dari inline HTML ── */
window._submitLaporanBtn = async function () {
var btn = document.getElementById('btn-submit-laporan');
var nama = document.getElementById('laporan-nama-pelapor').value.trim();
var desc = document.getElementById('laporan-deskripsi').value.trim();
if (!nama || !desc) { window.showToast('Nama dan deskripsi wajib diisi.', 'error'); return; }
btn.disabled = true; btn.textContent = 'Mengirim...';
var payload = {
kemiskinan_id: document.getElementById('laporan-kem-id').value,
nama_pelapor: nama,
nomor_wa: document.getElementById('laporan-nomor-wa').value.trim(),
kategori: document.getElementById('laporan-kategori').value,
deskripsi: desc
};
try {
var res = await fetch('php/create_laporan.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(payload)
});
var text = (await res.text()).trim();
if (text === 'success') {
document.getElementById('modal-laporan-publik').style.display = 'none';
if (window.Swal) {
Swal.fire({
icon: 'success',
title: 'Laporan Terkirim',
text: 'Laporan berhasil dikirim. Terima kasih atas informasi Anda!',
confirmButtonText: 'Tutup'
});
} else {
window.showToast('Laporan berhasil dikirim. Terima kasih!', 'success');
}
} else {
window.showToast('Gagal: ' + text, 'error');
}
} catch (err) { window.showToast('Gagal: ' + err.message, 'error'); }
finally { btn.disabled = false; btn.textContent = 'Kirim Laporan'; }
};
/* ── Panel manajemen (pengurus) ── */
window.openLaporanPanel = async function () {
var panel = document.getElementById('panel-laporan');
if (panel) {
var sidebar = document.getElementById('sidebar');
if (sidebar) sidebar.style.display = 'none';
var pes = document.getElementById('panel-pesan');
if (pes) pes.style.display = 'none';
panel.style.display = 'flex';
document.getElementById('laporan-filter-status').value = 'menunggu';
window.loadLaporan('menunggu');
}
};
window.closeLaporanPanel = function () {
var p = document.getElementById('panel-laporan');
if (p) p.style.display = 'none';
window.closeLaporanDetail();
// Auto-open sidebar
var sidebar = document.getElementById('sidebar');
if (sidebar) sidebar.style.display = 'flex';
};
window.loadLaporan = async function (statusFilter) {
var container = document.getElementById('laporan-list-container');
if (!container) return;
container.innerHTML = '<div class="loading">Memuat laporan...</div>';
var url = 'php/read_laporan.php' + (statusFilter ? '?status=' + statusFilter : '');
var data = await window.fetchJson(url, []);
container.innerHTML = '';
if (!data.length) {
container.innerHTML = '<div class="empty">Tidak ada laporan.</div>'; return;
}
data.forEach(function (l) {
var chipColors = { menunggu:'chip-amber', diproses:'chip-blue', selesai:'chip-green', ditolak:'chip-gray' };
var emoji = _kategoriEmoji[l.kategori] || '📣';
var card = document.createElement('div');
card.className = 'l-card ' + l.status_laporan;
card.title = 'Klik untuk detail lengkap';
var badgeCls = { menunggu:'chip-amber', diproses:'chip-blue', selesai:'chip-green', ditolak:'chip-gray' }[l.status_laporan] || 'chip-gray';
var kkHtml = l.nama_kepala_keluarga
? '<div class="l-kk"><i class="fa-solid fa-location-dot"></i> KK: ' + window.escapeHtml(l.nama_kepala_keluarga) + '</div>' : '';
card.innerHTML =
'<div class="l-head">' +
'<span class="l-cat">' + emoji + ' ' + (window.labelKategoriLaporan[l.kategori] || l.kategori) + '</span>' +
'<span class="chip ' + badgeCls + '">' + window.escapeHtml(window.labelStatusLaporan[l.status_laporan] || l.status_laporan) + '</span>' +
'</div>' +
kkHtml +
'<div class="l-desc">' + window.escapeHtml(l.deskripsi) + '</div>' +
'<div class="l-meta"><i class="fa-solid fa-user"></i> ' + window.escapeHtml(l.nama_pelapor) +
(l.nomor_wa ? ' &middot; <i class="fa-solid fa-mobile-screen"></i> ' + window.escapeHtml(l.nomor_wa) : '') +
' &middot; <i class="fa-regular fa-clock"></i> ' + l.created_fmt + '</div>' +
(l.ditangani_oleh ? '<div class="l-meta"><i class="fa-solid fa-check"></i> Ditangani: ' + window.escapeHtml(l.ditangani_oleh) + '</div>' : '');
/* Klik kartu → buka floating detail */
card.addEventListener('click', function (e) {
/* Jangan trigger jika yang diklik tombol aksi */
if (e.target.closest('button')) return;
window.openLaporanDetail(l);
});
if (window.appAuth.role === 'pengurus_masjid' && l.status_laporan !== 'selesai' && l.status_laporan !== 'ditolak') {
var acts = document.createElement('div');
acts.className = 'l-acts';
var statuses = [
{ val: 'diproses', label: 'Proses', cls: 'btn-proses' },
{ val: 'selesai', label: 'Selesai', cls: 'btn-selesai' },
{ val: 'ditolak', label: 'Tolak', cls: 'btn-tolak' }
];
statuses.forEach(function (s) {
if (s.val === l.status_laporan) return;
var btn = document.createElement('button');
btn.className = 'btn ' + s.cls; btn.textContent = s.label;
btn.onclick = (function(lid, sv){ return async function(){
try {
await window.postForm('php/update_laporan_status.php', {
id: lid, status_laporan: sv, ditangani_oleh: window.appAuth.nama
});
window.showToast('Status diperbarui.', 'success');
window.closeLaporanDetail();
window.loadLaporan(document.getElementById('laporan-filter-status').value);
} catch(e){ window.showToast('Gagal: ' + e.message, 'error'); }
}; })(l.id, s.val);
acts.appendChild(btn);
});
card.appendChild(acts);
}
container.appendChild(card);
});
};
/* Polling for Laporan Notification */
window.pollLaporanNotification = async function() {
if (!window.appAuth || window.appAuth.role !== 'pengurus_masjid') {
var np = document.getElementById('notif-panel');
if (np) np.style.display = 'none';
return;
}
try {
var res = await fetch('php/read_laporan.php');
var text = await res.text();
var data = JSON.parse(text);
if(Array.isArray(data)) {
var pendingCount = data.filter(function(l) { return l.status_laporan === 'menunggu'; }).length;
var badge = document.getElementById('nav-badge-laporan');
if(badge) {
if(pendingCount > 0) {
badge.style.display = 'inline-flex';
badge.textContent = pendingCount;
} else {
badge.style.display = 'none';
}
}
var np = document.getElementById('notif-panel');
var nb = document.getElementById('notif-body');
if (np && nb) {
if(pendingCount > 0) {
var isPanelOpen = document.getElementById('panel-laporan') && document.getElementById('panel-laporan').style.display === 'flex';
if(!isPanelOpen && (!window._notifDismissedForCount || window._notifDismissedForCount < pendingCount)) {
np.style.display = 'block';
nb.innerHTML = 'Terdapat <b>' + pendingCount + ' laporan baru</b> dari warga yang belum diproses.<br>Harap segera ditindaklanjuti!';
window._notifDismissedForCount = pendingCount;
}
} else {
np.style.display = 'none';
window._notifDismissedForCount = 0;
}
}
}
} catch(e) {}
};
// Start polling every 10 seconds
setInterval(window.pollLaporanNotification, 10000);
+1105
View File
File diff suppressed because it is too large Load Diff
+377
View File
@@ -0,0 +1,377 @@
/* ============================================================
map.js WebGIS Pemetaan Kemiskinan & Masjid (SRS v1.0)
============================================================ */
window.appState = {
mode: "view",
activeType: "kemiskinan",
layers: {},
layerVisibility: {
kemiskinan: true,
masjid: true
},
isHeatmap: false,
isRouting: false,
routingWaypoints: []
};
/* ── Utility: escape HTML ── */
window.escapeHtml = function (value) {
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};
/* ── Utility: SVG pin marker ── */
window.createPinIcon = function (bgColor, svgSymbol) {
var svg = [
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="42" viewBox="0 0 32 42">',
'<defs>',
'<filter id="shadow" x="-30%" y="-20%" width="160%" height="160%">',
'<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,0.35)"/>',
'</filter>',
'</defs>',
/* Pin body */
'<path d="M16 2C9.37 2 4 7.37 4 14c0 9 12 26 12 26S28 23 28 14C28 7.37 22.63 2 16 2z"',
'fill="' + bgColor + '" filter="url(#shadow)"/>',
/* White inner circle */
'<circle cx="16" cy="14" r="8" fill="rgba(255,255,255,0.25)"/>',
/* Symbol */
svgSymbol,
'</svg>'
].join('');
return L.divIcon({
className: 'custom-pin-icon',
html: svg,
iconSize: [32, 42],
iconAnchor: [16, 42],
popupAnchor: [0, -44]
});
};
/* Kemiskinan merah (tidak terjangkau) */
window.redIcon = window.createPinIcon('#dc2626',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128106;</text>'
);
/* Kemiskinan hijau (terjangkau) */
window.greenIcon = window.createPinIcon('#16a34a',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128106;</text>'
);
/* Masjid biru */
window.blueIcon = window.createPinIcon('#1d4ed8',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128332;</text>'
);
/* Fallback orange (tidak digunakan, tapi tetap didefinisikan) */
window.orangeIcon = window.createPinIcon('#f97316',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128106;</text>'
);
/* ── Utility: POST form-encoded ── */
window.postForm = async function (url, payload) {
var response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(payload)
});
var text = (await response.text()).trim();
if (!response.ok || text !== "success") {
throw new Error(text || "Permintaan gagal.");
}
};
/* ── Navbar hint ── */
window.updateMapHint = function () {
var hint = document.getElementById("mapHint");
if (!hint) { return; }
if (appState.mode === "view") {
hint.textContent = "Mode Lihat aktif — data keluarga prasejahtera & rumah ibadah ditampilkan. Klik kartu untuk navigasi peta.";
return;
}
if (appState.activeType === "kemiskinan") {
hint.textContent = "Mode Edit aktif — klik titik di peta untuk menambahkan data keluarga prasejahtera.";
return;
}
hint.textContent = "Mode Edit aktif — klik titik di peta untuk menambahkan data rumah ibadah baru.";
};
/* ── Toolbar state ── */
window.updateToolbarState = function () {
var states = {
modeViewBtn: appState.mode === "view",
modeEditBtn: appState.mode === "edit",
typeKemiskinanBtn: appState.activeType === "kemiskinan",
typeMasjidBtn: appState.activeType === "masjid"
};
Object.keys(states).forEach(function (id) {
var btn = document.getElementById(id);
if (!btn) { return; }
btn.classList.toggle("active", states[id]);
/* Mode buttons get a different active colour */
if (id === "modeEditBtn" && states[id]) {
btn.classList.add("mode-active");
} else {
btn.classList.remove("mode-active");
}
});
var viewBtn = document.getElementById("luminaModeView");
var editBtn = document.getElementById("luminaModeEdit");
if (viewBtn && editBtn) {
viewBtn.style.opacity = appState.mode === "view" ? "1" : "0.5";
viewBtn.style.boxShadow = appState.mode === "view" ? "0 4px 12px rgba(99,102,241,0.25)" : "none";
editBtn.style.opacity = appState.mode === "edit" ? "1" : "0.5";
editBtn.style.boxShadow = appState.mode === "edit" ? "0 4px 12px rgba(20,184,166,0.25)" : "none";
}
};
window.setActiveMenu = function(element) {
var items = document.querySelectorAll('.lumina-menu-item');
items.forEach(function(item) {
item.classList.remove('active');
});
element.classList.add('active');
};
/* ── Layer visibility ── */
window.setLayerVisibility = function (key, isVisible) {
appState.layerVisibility[key] = !!isVisible;
var layer = appState.layers[key];
if (!layer || !window.map) { return; }
if (isVisible) {
if (!map.hasLayer(layer)) { map.addLayer(layer); }
} else {
if (map.hasLayer(layer)) { map.removeLayer(layer); }
}
};
/* ── Mode & type switches ── */
window.setMode = function (mode) {
appState.mode = mode;
window.updateToolbarState();
window.updateMapHint();
var mapEl = document.getElementById("map");
if (mapEl) {
if (mode === "edit") {
mapEl.classList.add("edit-mode");
} else {
mapEl.classList.remove("edit-mode");
}
}
if (typeof window.loadAllData === "function") {
window.loadAllData();
}
};
window.setDataType = function (type) {
appState.activeType = type;
window.updateToolbarState();
window.updateMapHint();
if (typeof window.filterSidebar === 'function') {
window.filterSidebar(type);
}
};
window.filterSidebar = function(type) {
var secKem = document.getElementById('sec-kemiskinan');
var secMsj = document.getElementById('sec-masjid');
if (secKem) secKem.style.display = (type === 'semua' || type === 'kemiskinan') ? 'block' : 'none';
if (secMsj) secMsj.style.display = (type === 'semua' || type === 'masjid') ? 'block' : 'none';
var btnSemua = document.getElementById('filter-btn-semua');
var btnKem = document.getElementById('filter-btn-kemiskinan');
var btnMsj = document.getElementById('filter-btn-masjid');
if (btnSemua) btnSemua.classList.toggle('active', type === 'semua');
if (btnKem) btnKem.classList.toggle('active', type === 'kemiskinan');
if (btnMsj) btnMsj.classList.toggle('active', type === 'masjid');
var sidebarSub = document.getElementById('sidebar-sub');
if (sidebarSub) {
if (type === 'semua') sidebarSub.textContent = 'Menampilkan semua data';
else if (type === 'kemiskinan') sidebarSub.textContent = 'Menampilkan list Data Keluarga';
else sidebarSub.textContent = 'Menampilkan list Rumah Ibadah';
}
var sidebar = document.getElementById('sidebar');
if (sidebar && sidebar.style.display === 'none') {
sidebar.style.display = 'flex';
var pes = document.getElementById('panel-pesan');
if (pes) pes.style.display = 'none';
var lap = document.getElementById('panel-laporan');
if (lap) lap.style.display = 'none';
}
};
/* ── Format helpers ── */
window.formatMeters = function (value) {
return Number(value).toLocaleString("id-ID", { maximumFractionDigits: 0 }) + " m";
};
window.isValidCoordinate = function (lat, lng) {
return Number.isFinite(Number(lat)) && Number.isFinite(Number(lng));
};
window.isValidRadius = function (radius) {
var r = Number(radius);
return Number.isFinite(r) && r >= 100 && r <= 5000;
};
/* ─────────────────────────────────────────────
DOMContentLoaded inisialisasi peta (KF-01)
───────────────────────────────────────────── */
document.addEventListener("DOMContentLoaded", function () {
/* Inisialisasi peta terpusat di Pontianak */
window.map = L.map("map").setView([-0.0263, 109.3425], 13);
// --- Base maps ---
window.osmBase = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
});
window.darkBase = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; OpenStreetMap contributors &copy; CARTO'
});
// Add default basemap
window.osmBase.addTo(map);
/* ── Layers ── */
appState.layers.kemiskinan = L.layerGroup().addTo(map);
appState.layers.masjid = L.layerGroup().addTo(map);
appState.layers.heatmap = L.layerGroup(); // Do not add yet
/* Klik peta hanya aktif di Mode Edit (KF-03, KF-07) */
map.on("click", function (event) {
if (appState.mode !== "edit") { return; }
var lat = event.latlng.lat;
var lng = event.latlng.lng;
if (appState.activeType === "kemiskinan") {
if (typeof window.openKemiskinanModal === "function") {
window.openKemiskinanModal(lat, lng);
}
return;
}
if (appState.activeType === "masjid") {
if (typeof window.openMasjidModal === "function") {
window.openMasjidModal(lat, lng);
}
}
});
/* Init state awal */
window.setMode("view");
window.setDataType("kemiskinan");
setTimeout(function () { map.invalidateSize(); }, 300);
/* Cek session login, lalu load data */
if (typeof window.checkAuthState === "function") {
window.checkAuthState().then(function () {
if (typeof window.loadAllData === "function") { window.loadAllData(); }
});
} else if (typeof window.loadAllData === "function") {
window.loadAllData();
}
});
/* ── Heatmap & Routing Features ── */
window.toggleHeatmap = function(btnEl) {
if(!window.appState) return;
window.appState.isHeatmap = !window.appState.isHeatmap;
btnEl.classList.toggle('active', window.appState.isHeatmap);
if(window.appState.isHeatmap) {
document.getElementById('desc-heatmap').style.display = 'block';
// Hide kemiskinan markers
window.appState.layers.kemiskinan.eachLayer(function(l) { l.setOpacity(0); });
// Show heatmap
if(!window.map.hasLayer(window.appState.layers.heatmap)) {
window.appState.layers.heatmap.addTo(window.map);
}
// Switch to Dark mode for better visual
window.map.removeLayer(window.osmBase);
window.darkBase.addTo(window.map);
} else {
document.getElementById('desc-heatmap').style.display = 'none';
// Show kemiskinan markers
window.appState.layers.kemiskinan.eachLayer(function(l) { l.setOpacity(1); });
// Hide heatmap
if(window.map.hasLayer(window.appState.layers.heatmap)) {
window.map.removeLayer(window.appState.layers.heatmap);
}
// Restore map base
window.map.removeLayer(window.darkBase);
window.osmBase.addTo(window.map);
}
};
window.toggleRoutingMode = function(btnEl) {
if(!window.appState) return;
window.appState.isRouting = true;
window.appState.routingWaypoints = [];
btnEl.classList.add('active');
document.getElementById('desc-routing').style.display = 'block';
// Show instruction panel
document.getElementById('routing-instruction-panel').style.display = 'block';
document.getElementById('routing-instruction-text').innerHTML = 'Silakan klik <b>Masjid</b> atau <b>Rumah Warga</b> di peta sebagai titik awal rute.';
};
window.cancelRouting = function() {
if(!window.appState) return;
window.appState.isRouting = false;
window.appState.routingWaypoints = [];
document.getElementById('btn-toggle-routing').classList.remove('active');
document.getElementById('desc-routing').style.display = 'none';
document.getElementById('routing-instruction-panel').style.display = 'none';
// Remove routing control if exists
if(window.routingControl) {
window.map.removeControl(window.routingControl);
window.routingControl = null;
}
};
window.handleRoutingClick = function(lat, lng, name) {
window.appState.routingWaypoints.push(L.latLng(lat, lng));
var pts = window.appState.routingWaypoints;
var inst = document.getElementById('routing-instruction-text');
if(pts.length === 1) {
inst.innerHTML = '<div style="margin-bottom:6px; color:#16a34a;"><i class="fa-solid fa-check"></i> Titik Awal: <b>' + window.escapeHtml(name) + '</b></div>' +
'<div style="margin-bottom:6px;"><b>Langkah 2:</b> Pilih Tujuan</div>' +
'<span style="color:var(--lumina-text-muted);">Klik rumah/masjid lain di peta sebagai tujuan akhir.</span>';
} else if(pts.length === 2) {
inst.innerHTML = '<div style="margin-bottom:6px; color:#16a34a;"><i class="fa-solid fa-check"></i> Rute Berhasil Dibuat</div>' +
'<span style="color:var(--lumina-text-muted);">Detail arah jalan (navigasi) muncul di layar sebelah kanan peta.</span>';
// Remove existing control
if(window.routingControl) {
window.map.removeControl(window.routingControl);
}
window.routingControl = L.Routing.control({
waypoints: pts,
routeWhileDragging: false,
addWaypoints: false,
show: true,
lineOptions: {
styles: [{color: '#2563eb', opacity: 0.8, weight: 6}]
}
}).addTo(window.map);
window.appState.isRouting = false; // Turn off click interception after 2 points
document.getElementById('btn-toggle-routing').classList.remove('active');
}
};
+212
View File
@@ -0,0 +1,212 @@
/* ============================================================
js/pesan.js — Media komunikasi pengurus v2.2
============================================================ */
window.labelJenisPesan = {
permintaan_bantuan: 'Permintaan Bantuan',
koordinasi: 'Koordinasi',
pengumuman: 'Pengumuman'
};
var _jenisEmoji = {
permintaan_bantuan: '🆘',
koordinasi: '📋',
pengumuman: '📢'
};
/* ─────────────────────────────────────────────────────
Floating detail box
───────────────────────────────────────────────────── */
window.openPesanDetail = async function (p) {
var fl = document.getElementById('pesan-detail-float');
if (!fl) return;
var jenis = p.jenis || 'koordinasi';
var emoji = _jenisEmoji[jenis] || '💬';
var jenisLabel = window.labelJenisPesan[jenis] || jenis;
var isBroadcast = !p.penerima_id;
var roleLabel = p.role_pengirim === 'pimpinan_daerah' ? '<i class="fa-solid fa-crown"></i> Pimpinan Daerah' : '<i class="fa-solid fa-mosque"></i> Pengurus Masjid';
/* Accent bar */
document.getElementById('pdf-accent-bar').className = 'pdf-accent-bar ' + jenis;
/* Icon */
var icon = document.getElementById('pdf-icon');
icon.textContent = emoji;
icon.className = 'pdf-icon ' + jenis;
/* Title (judul pesan) + subtitle (jenis) */
document.getElementById('pdf-title').textContent = p.judul || 'Pesan';
document.getElementById('pdf-subtitle').textContent = jenisLabel;
/* Isi */
document.getElementById('pdf-isi').textContent = p.isi || '(tidak ada isi)';
/* Info rows */
document.getElementById('pdf-pengirim').textContent = p.nama_pengirim || '-';
document.getElementById('pdf-role').innerHTML = roleLabel;
document.getElementById('pdf-jenis-label').textContent = emoji + ' ' + jenisLabel;
document.getElementById('pdf-waktu').textContent = p.created_fmt || '-';
/* Broadcast badge */
document.getElementById('pdf-row-broadcast').style.display = isBroadcast ? '' : 'none';
/* Baru badge */
document.getElementById('pdf-row-baru').style.display = (p.status === 'baru') ? '' : 'none';
/* Close */
document.getElementById('pdf-close').onclick = window.closePesanDetail;
/* Show */
fl.style.display = 'flex';
/* Mark as read if it is new and we are not the sender */
if (p.status === 'baru' && p.pengirim_id != window.appAuth.id) {
try {
var formData = new FormData();
formData.append('id', p.id);
formData.append('status', 'dibaca');
await fetch('php/update_pesan_status.php', { method: 'POST', body: formData });
p.status = 'dibaca';
// Remove 'baru' indicator from card visually
var cardEl = document.querySelector('.p-card[data-id="' + p.id + '"]');
if (cardEl) {
cardEl.classList.remove('baru');
var dot = cardEl.querySelector('.p-new');
if (dot) dot.remove();
}
// Immediately update the notification badge
if (window.pollPesanNotification) window.pollPesanNotification();
} catch (e) {
console.error('Failed to update message status', e);
}
}
};
window.closePesanDetail = function () {
var fl = document.getElementById('pesan-detail-float');
if (fl) fl.style.display = 'none';
};
/* ── Panel open/close ── */
window.openPesanPanel = async function () {
var panel = document.getElementById('panel-pesan');
if (panel) {
var sidebar = document.getElementById('sidebar');
if (sidebar) sidebar.style.display = 'none';
var lap = document.getElementById('panel-laporan');
if (lap) lap.style.display = 'none';
panel.style.display = 'flex';
await window.loadPesan();
}
};
window.closePesanPanel = function () {
var p = document.getElementById('panel-pesan');
if (p) p.style.display = 'none';
window.closePesanDetail();
// Auto-open sidebar
var sidebar = document.getElementById('sidebar');
if (sidebar) sidebar.style.display = 'flex';
};
/* ── Load & render pesan ── */
window.loadPesan = async function () {
var container = document.getElementById('pesan-list-container');
if (!container) return;
container.innerHTML = '<div class="loading">Memuat pesan...</div>';
var data = await window.fetchJson('php/read_pesan.php', []);
container.innerHTML = '';
if (!data.length) {
container.innerHTML = '<div class="empty">Belum ada pesan. Mulai komunikasi di bawah.</div>';
return;
}
data.forEach(function (p) {
var jenisClass = { permintaan_bantuan:'bantuan', koordinasi:'', pengumuman:'pengumuman' }[p.jenis] || '';
var chipColors = { permintaan_bantuan:'chip-red', koordinasi:'chip-blue', pengumuman:'chip-amber' };
var isBroadcast = !p.penerima_id;
var emoji = _jenisEmoji[p.jenis] || '💬';
var card = document.createElement('div');
card.className = 'p-card' + (jenisClass ? ' ' + jenisClass : '') + (p.status === 'baru' ? ' baru' : '');
card.title = 'Klik untuk detail lengkap';
card.dataset.id = p.id;
card.innerHTML =
'<div class="p-head">' +
'<span class="chip ' + (chipColors[p.jenis]||'chip-gray') + '">' + emoji + ' ' + (window.labelJenisPesan[p.jenis]||p.jenis) + '</span>' +
(isBroadcast ? '<span class="chip chip-purple">📡 Broadcast</span>' : '') +
(p.status === 'baru' ? '<span class="p-new">●</span>' : '') +
'</div>' +
'<div class="p-judul">' + window.escapeHtml(p.judul) + '</div>' +
'<div class="p-isi">' + window.escapeHtml(p.isi) + '</div>' +
'<div class="p-meta">👤 ' + window.escapeHtml(p.nama_pengirim) +
' (' + (p.role_pengirim === 'pimpinan_daerah' ? 'Pimpinan' : 'Pengurus') + ')' +
' · 🕐 ' + p.created_fmt + '</div>';
/* Klik kartu → buka floating detail */
card.addEventListener('click', function () {
window.openPesanDetail(p);
});
container.appendChild(card);
});
};
/* ── Submit kirim pesan ── */
window.submitPesan = async function (e) {
e.preventDefault();
if (!window.appAuth.loggedIn) { window.showToast('Login diperlukan.', 'error'); return; }
var btn = document.getElementById('btn-submit-pesan');
var judul = document.getElementById('pesan-judul').value.trim();
var isi = document.getElementById('pesan-isi').value.trim();
var jenis = document.getElementById('pesan-jenis').value;
if (!judul || !isi) { window.showToast('Judul dan isi wajib diisi.', 'error'); return; }
btn.disabled = true; btn.textContent = 'Mengirim...';
try {
await window.postForm('php/send_pesan.php', { judul, isi, jenis });
document.getElementById('pesan-judul').value = '';
document.getElementById('pesan-isi').value = '';
window.showToast('Pesan terkirim.', 'success');
await window.loadPesan();
} catch (err) { window.showToast('Gagal: ' + err.message, 'error'); }
finally { btn.disabled = false; btn.textContent = 'Kirim Pesan'; }
};
/* Polling for Pesan Notification */
window.pollPesanNotification = async function() {
if (!window.appAuth) return;
try {
var res = await fetch('php/read_pesan.php');
var text = await res.text();
var data = JSON.parse(text);
if(Array.isArray(data)) {
// Count messages from OTHER users that are considered "baru"
// Since we don't have a read-status per user in this simple app, we can just count messages from others in the last 24 hours, or just count 'baru'
var pendingCount = data.filter(function(m) {
return m.status === 'baru' && m.pengirim_id != window.appAuth.id;
}).length;
var badge = document.getElementById('nav-badge-pesan');
if(badge) {
if(pendingCount > 0) {
badge.style.display = 'inline-flex';
badge.textContent = pendingCount;
} else {
badge.style.display = 'none';
}
}
}
} catch(e) {}
};
// Start polling every 15 seconds
setInterval(window.pollPesanNotification, 15000);