279 lines
10 KiB
JavaScript
279 lines
10 KiB
JavaScript
/* ════════════════════════════════════════
|
||
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>
|
||
| Role: <strong>${info.label}</strong>
|
||
| <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 };
|
||
})();
|