/* ════════════════════════════════════════
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 `${v.replace(/_/g,' ').toUpperCase()}`;
};
/* ════════════════════════════════════════
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: ${nama}
| Role: ${info.label}
| ${roleDescription(role)}
`;
// 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 = `${icons[type]||'ℹ️'}${msg}`;
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 };
})();