Files
WebGIS-Project/webgis-v3/assets/js/app.js
T
2026-06-10 19:48:00 +07:00

279 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ════════════════════════════════════════
GLOBAL HELPERS
════════════════════════════════════════ */
const ge = id => document.getElementById(id);
const gv = id => (document.getElementById(id)?.value ?? '').trim();
const sv = (id, val) => { const el = document.getElementById(id); if (el) el.value = val ?? ''; };
const rp = n => n > 0 ? 'Rp ' + parseInt(n).toLocaleString('id-ID') : '—';
const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1).replace(/_/g, ' ') : '—';
const bdg = (val, _cls) => {
const v = val || '';
const css = v.replace(/_/g,'-').toLowerCase();
return `<span class="bdg bdg-${css}">${v.replace(/_/g,' ').toUpperCase()}</span>`;
};
/* ════════════════════════════════════════
ROLE CONFIG
1 = Admin, 2 = Pengurus RI, 3 = Pimpinan, 4 = Masyarakat
════════════════════════════════════════ */
const ROLE_MENUS = {
1: ['dashboard','peta','penduduk','keluarga','ri','bantuan','prioritas','laporan','chat','log'],
2: ['dashboard','peta','penduduk','keluarga','ri','bantuan','prioritas','laporan','chat'],
3: ['dashboard','peta','penduduk','keluarga','laporan'],
4: ['dashboard','peta'],
99: ['dashboard','peta'],
};
const ROLE_LABELS = {
1: { icon:'⚙️', label:'Administrator', color:'#e74c3c' },
2: { icon:'🕌', label:'Pengurus Rumah Ibadah', color:'#27ae60' },
3: { icon:'🏛️', label:'Pimpinan Daerah', color:'#2980b9' },
4: { icon:'👤', label:'Masyarakat', color:'#8e44ad' },
};
/* ════════════════════════════════════════
APP CONTROLLER
════════════════════════════════════════ */
const App = (() => {
let _user = null, _autoTimer = null;
const currentUser = () => _user;
const userRole = () => _user?.id_role ?? 99;
const canEdit = () => [1,2].includes(userRole());
/* ── Login ── */
async function doLogin() {
const username = gv('login-user'), password = gv('login-pass');
const errEl = ge('login-err');
const btnMasuk = ge('btn-masuk-submit');
errEl.classList.add('hidden');
if (btnMasuk) { btnMasuk.disabled = true; btnMasuk.textContent = 'Memproses...'; }
try {
const res = await fetch('api/index.php?r=auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
credentials: 'same-origin',
});
let json;
const text = await res.text();
try { json = JSON.parse(text); }
catch { throw new Error(`Respons server tidak valid (bukan JSON). Cek error PHP di server.`); }
if (!json.success) throw new Error(json.message || 'Login gagal');
_user = json.data;
setUserUI(_user);
closeModal('modal-login');
ge('login-pass').value = '';
toast(`Selamat datang, ${_user.nama_lengkap}! 👋`, 'success');
// Arahkan ke panel default sesuai role
const defaultPanel = { 1:'dashboard', 2:'dashboard', 3:'dashboard', 4:'dashboard' };
switchPanel(defaultPanel[_user.id_role] || 'dashboard');
startAutoRefresh();
} catch(e) {
errEl.textContent = e.message || 'Username atau password salah';
errEl.classList.remove('hidden');
} finally {
if (btnMasuk) { btnMasuk.disabled = false; btnMasuk.textContent = 'Masuk →'; }
}
}
async function logout() {
try {
await fetch('api/index.php?r=auth/logout', { method:'DELETE', credentials:'same-origin' });
} catch {}
stopAutoRefresh();
_user = null;
setUserUI(null);
toast('Anda telah logout', 'info');
switchPanel('dashboard');
}
/* ── Apply UI per role ── */
function setUserUI(u) {
if (u) {
const info = ROLE_LABELS[u.id_role] || { icon:'👤', label: u.nama_role, color:'#555' };
ge('user-name-el').textContent = u.nama_lengkap;
ge('user-avatar-el').textContent = info.icon;
ge('user-role-el').textContent = info.label.toUpperCase();
ge('user-info').classList.remove('hidden');
ge('btn-login').classList.add('hidden');
applyRoleUI(u.id_role);
// Tampilkan role banner
showRoleBanner(u.id_role, u.nama_lengkap, info);
} else {
ge('user-info').classList.add('hidden');
ge('btn-login').classList.remove('hidden');
applyRoleUI(99);
hideRoleBanner();
}
}
function showRoleBanner(role, nama, info) {
let banner = ge('role-banner');
if (!banner) {
banner = document.createElement('div');
banner.id = 'role-banner';
banner.style.cssText = `
position:fixed; top:0; left:0; right:0; z-index:9999;
padding:6px 16px; font-size:12px; font-weight:600;
display:flex; align-items:center; gap:8px;
transition: all 0.3s;
`;
document.body.prepend(banner);
}
banner.style.background = info.color;
banner.style.color = '#fff';
banner.innerHTML = `
${info.icon} Login sebagai: <strong>${nama}</strong>
&nbsp;|&nbsp; Role: <strong>${info.label}</strong>
&nbsp;|&nbsp; <span style="opacity:0.8;font-size:11px">${roleDescription(role)}</span>
`;
// Geser konten ke bawah sedikit
document.body.style.paddingTop = '30px';
}
function hideRoleBanner() {
const banner = ge('role-banner');
if (banner) banner.remove();
document.body.style.paddingTop = '';
}
function roleDescription(role) {
return {
1: 'Akses penuh — kelola semua data & pengguna',
2: 'Kelola penduduk, bantuan & verifikasi laporan',
3: 'Read-only — monitoring & dashboard',
4: 'Hanya bisa melihat peta & kirim laporan',
}[role] || '';
}
function applyRoleUI(role) {
const allowed = ROLE_MENUS[role] || ROLE_MENUS[99];
// Sembunyikan/tampilkan nav menu
document.querySelectorAll('.nav-btn[data-panel]').forEach(btn => {
const panel = btn.dataset.panel;
btn.style.display = allowed.includes(panel) ? '' : 'none';
});
// Sembunyikan tombol aksi edit/tambah/salur untuk non-editor
const canEditRole = [1,2].includes(role);
document.querySelectorAll('[id^="btn-tambah"],[id^="btn-salur"],[id^="btn-edit"]')
.forEach(el => el.style.display = canEditRole ? '' : 'none');
// Khusus Admin: tampilkan menu log
const logBtn = document.querySelector('[data-panel="log"]');
if (logBtn) logBtn.style.display = role === 1 ? '' : 'none';
// Kirim Laporan hanya untuk masyarakat & umum
const kirimBtn = ge('nav-kirim-laporan');
if (kirimBtn) kirimBtn.style.display = [4,99].includes(role) ? '' : 'none';
}
/* ── Panel routing ── */
let _cur = 'dashboard';
function switchPanel(name) {
const role = userRole();
const allowed = ROLE_MENUS[role] || ROLE_MENUS[99];
// Jika panel tidak boleh diakses role ini, redirect ke dashboard
if (!allowed.includes(name)) {
toast('Anda tidak punya akses ke halaman ini', 'error');
name = 'dashboard';
}
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
ge(`panel-${name}`)?.classList.add('active');
document.querySelector(`[data-panel="${name}"]`)?.classList.add('active');
_cur = name;
if (name === 'peta') setTimeout(() => MapApp.init(), 80);
else loadPanel(name);
}
function loadCurrentPanel() { loadPanel(_cur); }
function loadPanel(n) {
({ dashboard: Dashboard.load, penduduk: Penduduk.load, keluarga: Keluarga.load,
bantuan: Bantuan.load, laporan: LaporanPanel.load, ri: RI.load,
prioritas: PrioritasPanel.load, chat: Chat.init, log: LogPanel.load })[n]?.();
}
/* ── Auto refresh ── */
function startAutoRefresh() {
stopAutoRefresh();
_autoTimer = setInterval(() => {
if (_cur === 'dashboard') Dashboard.load();
if (_cur === 'laporan') LaporanPanel.load();
updateHeaderStats();
}, 30000);
}
function stopAutoRefresh() { clearInterval(_autoTimer); }
/* ── Modals ── */
function showModal(id) { ge(id)?.classList.remove('hidden'); }
function closeModal(id) { ge(id)?.classList.add('hidden'); }
/* ── Toast ── */
function toast(msg, type='info', dur=3800) {
const icons = { success:'✅', error:'❌', info:'️', warning:'⚠️' };
const el = document.createElement('div');
el.className = `toast-item ${type}`;
el.innerHTML = `<span class="toast-icon">${icons[type]||'️'}</span><span>${msg}</span>`;
ge('toast-container').appendChild(el);
setTimeout(() => { el.classList.add('out'); setTimeout(() => el.remove(), 300); }, dur);
}
/* ── Header stats ── */
async function updateHeaderStats() {
try {
const d = await Api.get('/dashboard');
const s = d.statistik;
ge('hp-miskin-n').textContent = s.total_miskin;
ge('hp-rentan-n').textContent = s.total_rentan;
ge('hp-mampu-n').textContent = s.total_mampu;
ge('hp-lap-n').textContent = s.laporan_pending;
const nb = ge('nb-laporan');
s.laporan_pending > 0
? (nb.textContent = s.laporan_pending, nb.classList.remove('hidden'))
: nb.classList.add('hidden');
} catch {}
}
/* ── Events ── */
ge('btn-toggle-sidebar').addEventListener('click', () => {
ge('sidebar').classList.toggle('collapsed');
});
document.addEventListener('click', e => {
if (e.target.classList.contains('modal-overlay')) e.target.classList.add('hidden');
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape')
document.querySelectorAll('.modal-overlay:not(.hidden)').forEach(m => m.classList.add('hidden'));
});
/* ── Init ── */
(async function init() {
try {
const res = await fetch('api/index.php?r=auth/me', { credentials:'same-origin' });
const text = await res.text();
const json = JSON.parse(text);
if (json.success) { _user = json.data; setUserUI(_user); startAutoRefresh(); }
} catch {}
Dashboard.load();
updateHeaderStats();
})();
return { currentUser, userRole, canEdit, doLogin, logout, switchPanel,
showModal, closeModal, toast, updateHeaderStats };
})();