246 lines
7.5 KiB
JavaScript
246 lines
7.5 KiB
JavaScript
/* ============================================================
|
|
app.js — Main app orchestrator with authentication
|
|
============================================================ */
|
|
'use strict';
|
|
|
|
// Flag to prevent duplicate initialization
|
|
let isAppInitialized = false;
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Prevent duplicate initialization
|
|
if (isAppInitialized) {
|
|
console.warn('App already initialized, skipping duplicate call');
|
|
return;
|
|
}
|
|
|
|
// First check authentication
|
|
const isAuthed = await checkAuth();
|
|
|
|
if (!isAuthed) {
|
|
// Not logged in, redirect to login
|
|
window.location.href = 'login.html';
|
|
return;
|
|
}
|
|
|
|
// Initialize UI based on role
|
|
initUIByRole();
|
|
|
|
// Add logout button to sidebar
|
|
addLogoutButton();
|
|
|
|
// Init map
|
|
initMap();
|
|
|
|
// Init UI
|
|
initNavTabs();
|
|
initFilters();
|
|
initFormTabs();
|
|
initHelpModal();
|
|
|
|
// Load data
|
|
await loadAllData();
|
|
|
|
// Nav tab: activate placement modes
|
|
hookTabPlacementModes();
|
|
|
|
isAppInitialized = true;
|
|
});
|
|
|
|
async function refreshCenters() {
|
|
const r = await ApiCenters.list();
|
|
if (r.ok && r.data?.success) {
|
|
State.centers = r.data.data.centers || [];
|
|
recountCenterHouseholds(); // Hitung ulang setelah dapat data baru
|
|
renderCenters();
|
|
updateLayerCounts();
|
|
}
|
|
}
|
|
|
|
// Override loadAllData untuk memastikan recount
|
|
async function loadAllData() {
|
|
showLoading(true);
|
|
|
|
const params = {};
|
|
if (State.povertyFilter) params.poverty_status = State.povertyFilter;
|
|
if (State.aidFilter) params.aid_status = State.aidFilter;
|
|
if (State.searchQuery) params.q = State.searchQuery;
|
|
if (State.conditionFilter) params.house_condition = State.conditionFilter;
|
|
params.limit = 500;
|
|
|
|
const [centersRes, housesRes] = await Promise.all([
|
|
ApiCenters.list(),
|
|
ApiHouses.list(params),
|
|
loadStats()
|
|
]);
|
|
|
|
if (centersRes.ok && centersRes.data?.success) {
|
|
State.centers = centersRes.data.data.centers || [];
|
|
}
|
|
|
|
if (housesRes.ok && housesRes.data?.success) {
|
|
State.houses = housesRes.data.data.households || [];
|
|
}
|
|
|
|
recountCenterHouseholds(); // ⭐ PASTIKAN HITUNG ULANG
|
|
renderCenters();
|
|
renderHouses();
|
|
|
|
showLoading(false);
|
|
}
|
|
|
|
// Export fungsi refresh
|
|
window.refreshCenters = refreshCenters;
|
|
|
|
async function loadCenters() {
|
|
const r = await ApiCenters.list();
|
|
if (r.ok && r.data?.success) {
|
|
State.centers = r.data.data.centers || [];
|
|
}
|
|
renderCenters();
|
|
}
|
|
|
|
async function loadHouses() {
|
|
const params = {};
|
|
if (State.povertyFilter) params.poverty_status = State.povertyFilter;
|
|
if (State.aidFilter) params.aid_status = State.aidFilter;
|
|
if (State.searchQuery) params.q = State.searchQuery;
|
|
if (State.conditionFilter) params.house_condition = State.conditionFilter;
|
|
|
|
const r = await ApiHouses.list({ ...params, limit: 500 });
|
|
if (r.ok && r.data?.success) {
|
|
State.houses = r.data.data.households || [];
|
|
}
|
|
renderHouses();
|
|
}
|
|
|
|
async function loadStats() {
|
|
const r = await ApiStats.overview();
|
|
if (!r.ok || !r.data?.success) return;
|
|
|
|
const d = r.data.data;
|
|
State.stats = d;
|
|
|
|
animateCount('statCenters', d.centers);
|
|
animateCount('statHouses', d.households);
|
|
animateCount('statPopulation', d.population);
|
|
animateCount('statAided', d.aid_received);
|
|
animateCount('statPending', d.pending_public ?? 0);
|
|
}
|
|
|
|
// Export for public-reports.js
|
|
window.loadAllData = loadAllData;
|
|
window.loadStats = loadStats;
|
|
|
|
function animateCount(id, target) {
|
|
const el = document.getElementById(id);
|
|
if (!el) return;
|
|
const start = parseInt(el.textContent.replace(/\D/g,'')) || 0;
|
|
const diff = target - start;
|
|
const steps = 20;
|
|
let step = 0;
|
|
const interval = setInterval(() => {
|
|
step++;
|
|
el.textContent = Math.round(start + (diff * step / steps)).toLocaleString('id-ID');
|
|
if (step >= steps) clearInterval(interval);
|
|
}, 30);
|
|
}
|
|
|
|
function initNavTabs() {
|
|
document.querySelectorAll('.nav-tab').forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
const key = tab.dataset.tab;
|
|
document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
|
|
if (key === 'dashboard') {
|
|
openDashboard();
|
|
setTimeout(() => {
|
|
document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
|
|
document.querySelector('[data-tab="overview"]').classList.add('active');
|
|
}, 200);
|
|
} else if (key === 'overview') {
|
|
cancelPlacementMode();
|
|
State.activeFilter = 'all';
|
|
renderAllLayers();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function hookTabPlacementModes() {
|
|
document.querySelector('[data-tab="centers"]')?.addEventListener('click', () => {
|
|
setPlacementMode('center');
|
|
showToast('Klik peta untuk menambah tempat ibadah.', 'success', 3000);
|
|
});
|
|
|
|
document.querySelector('[data-tab="houses"]')?.addEventListener('click', () => {
|
|
setPlacementMode('house');
|
|
showToast('Klik peta untuk menambah rumah tangga.', 'success', 3000);
|
|
});
|
|
}
|
|
|
|
function initFilters() {
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
State.activeFilter = btn.dataset.filter;
|
|
renderAllLayers();
|
|
});
|
|
});
|
|
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', debounce(async (e) => {
|
|
State.searchQuery = e.target.value.trim();
|
|
renderAllLayers();
|
|
}, 300));
|
|
}
|
|
}
|
|
|
|
let helpStep = 1;
|
|
const helpTotal = 5;
|
|
|
|
function initHelpModal() {
|
|
document.getElementById('helpBtn')?.addEventListener('click', () => {
|
|
helpStep = 1;
|
|
updateHelpStep();
|
|
openModal('helpModal');
|
|
});
|
|
}
|
|
|
|
function helpNav(dir) {
|
|
helpStep = Math.min(helpTotal, Math.max(1, helpStep + dir));
|
|
updateHelpStep();
|
|
}
|
|
|
|
function updateHelpStep() {
|
|
document.querySelectorAll('.help-step').forEach(s => s.classList.remove('active'));
|
|
document.querySelector(`.help-step[data-step="${helpStep}"]`)?.classList.add('active');
|
|
document.getElementById('helpProgress').textContent = helpStep + ' / ' + helpTotal;
|
|
document.getElementById('helpPrev').disabled = helpStep === 1;
|
|
document.getElementById('helpNext').disabled = helpStep === helpTotal;
|
|
if (helpStep === helpTotal) {
|
|
document.getElementById('helpNext').textContent = 'Selesai';
|
|
document.getElementById('helpNext').onclick = () => closeModal('helpModal');
|
|
} else {
|
|
document.getElementById('helpNext').textContent = 'Berikutnya →';
|
|
document.getElementById('helpNext').onclick = () => helpNav(1);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
cancelPlacementMode();
|
|
document.querySelectorAll('.modal-overlay').forEach(m => {
|
|
if (m.style.display === 'flex') {
|
|
m.style.display = 'none';
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
}
|
|
if (e.ctrlKey && e.key === 'r') {
|
|
e.preventDefault();
|
|
loadAllData().then(() => showToast('Data diperbarui.', 'success'));
|
|
}
|
|
}); |