bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
194 lines
4.8 KiB
JavaScript
194 lines
4.8 KiB
JavaScript
/**
|
||
* LandveX Finance — Entry Point
|
||
* Sprint 3: UI-refaktorering
|
||
*
|
||
* Initierar appen, laddar state, sätter upp event listeners.
|
||
*/
|
||
|
||
import state from './state.mjs';
|
||
import { getHealth } from './api.mjs';
|
||
import { $, on } from './utils.mjs';
|
||
|
||
// ── Tab-modules (lazy-loaded) ──
|
||
const tabModules = {
|
||
overview: () => import('./tabs/overview.mjs'),
|
||
transactions: () => import('./tabs/transactions.mjs'),
|
||
invoices: () => import('./tabs/invoices.mjs'),
|
||
vat: () => import('./tabs/vat.mjs'),
|
||
payroll: () => import('./tabs/payroll.mjs'),
|
||
period: () => import('./tabs/period.mjs'),
|
||
tasks: () => import('./tabs/tasks.mjs'),
|
||
forecast: () => import('./tabs/forecast.mjs'),
|
||
};
|
||
|
||
let currentTabModule = null;
|
||
|
||
// ── Init ──
|
||
export async function init() {
|
||
console.log('[finance] init start');
|
||
|
||
// 1. Bygg period-väljare
|
||
buildPeriodSelects();
|
||
|
||
// 2. Sätt upp globala event listeners
|
||
setupGlobalEvents();
|
||
|
||
// 3. Ladda initial tab
|
||
await switchTab(state.get().tab);
|
||
|
||
// 4. Kolla health
|
||
checkHealth();
|
||
|
||
// 5. Sätt upp state-subscribers
|
||
state.subscribe(onStateChange);
|
||
|
||
console.log('[finance] init done');
|
||
}
|
||
|
||
// ── Tab-switching ──
|
||
export async function switchTab(tab) {
|
||
if (!tabModules[tab]) {
|
||
console.error('[finance] unknown tab:', tab);
|
||
return;
|
||
}
|
||
|
||
// Deaktivera nuvarande tab
|
||
if (currentTabModule?.deactivate) {
|
||
currentTabModule.deactivate();
|
||
}
|
||
|
||
// Uppdatera UI
|
||
document.querySelectorAll('.tab-sec').forEach(s => s.classList.remove('active'));
|
||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||
|
||
const tabEl = $(`tab-${tab}`);
|
||
if (tabEl) tabEl.classList.add('active');
|
||
|
||
const tabIndex = {
|
||
overview: 0, transactions: 1, invoices: 2, vat: 3,
|
||
payroll: 4, period: 5, tasks: 6, forecast: 7
|
||
};
|
||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||
if (tabBtns[tabIndex[tab] ?? 0]) {
|
||
tabBtns[tabIndex[tab] ?? 0].classList.add('active');
|
||
}
|
||
|
||
// Uppdatera state
|
||
state.setTab(tab);
|
||
|
||
// Lazy-load modul
|
||
try {
|
||
const module = await tabModules[tab]();
|
||
currentTabModule = module;
|
||
|
||
if (module.activate) {
|
||
await module.activate(state.get().period);
|
||
}
|
||
} catch (e) {
|
||
console.error('[finance] failed to load tab:', tab, e);
|
||
showError(`Kunde inte ladda ${tab}`);
|
||
}
|
||
}
|
||
|
||
// ── Period ──
|
||
function buildPeriodSelects() {
|
||
const FISCAL = state.get().fiscalYear;
|
||
const ms = $('sel-month');
|
||
if (!ms) return;
|
||
|
||
ms.innerHTML = '';
|
||
for (let m = 1; m <= 12; m++) {
|
||
const v = `${FISCAL}-${String(m).padStart(2, '0')}`;
|
||
const o = document.createElement('option');
|
||
o.value = v;
|
||
o.textContent = `${MN[m - 1]} ${FISCAL}`;
|
||
if (v === state.get().period) o.selected = true;
|
||
ms.appendChild(o);
|
||
}
|
||
ms.onchange = () => state.setPeriod(ms.value);
|
||
|
||
const qs = $('sel-quarter');
|
||
if (qs) {
|
||
qs.innerHTML = '';
|
||
const curM = parseInt((state.get().period || '').split('-')[1] || 6);
|
||
for (let q = 1; q <= 4; q++) {
|
||
const o = document.createElement('option');
|
||
o.value = `Q${q}-${FISCAL}`;
|
||
o.textContent = `Q${q} ${['Jan–Mar', 'Apr–Jun', 'Jul–Sep', 'Okt–Dec'][q - 1]} ${FISCAL}`;
|
||
if (Math.ceil(curM / 3) === q) o.selected = true;
|
||
qs.appendChild(o);
|
||
}
|
||
qs.onchange = () => state.setPeriod(qs.value);
|
||
}
|
||
}
|
||
|
||
// ── Globala events ──
|
||
function setupGlobalEvents() {
|
||
// Escape stänger modaler
|
||
on(document, 'keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
closeAllModals();
|
||
}
|
||
});
|
||
|
||
// Window resize (debounced)
|
||
let resizeTimer;
|
||
on(window, 'resize', () => {
|
||
clearTimeout(resizeTimer);
|
||
resizeTimer = setTimeout(() => {
|
||
state.set({ windowWidth: window.innerWidth });
|
||
}, 250);
|
||
});
|
||
}
|
||
|
||
// ── State change handler ──
|
||
function onStateChange(s) {
|
||
// Uppdatera loading-indikatorer
|
||
const isLoading = s.loading.size > 0;
|
||
const statusEl = $('data-status');
|
||
if (statusEl) {
|
||
statusEl.innerHTML = isLoading
|
||
? '<span class="sdot"></span><span>Laddar…</span>'
|
||
: '<span class="sdot live"></span><span>Live</span>';
|
||
}
|
||
}
|
||
|
||
// ── Health check ──
|
||
async function checkHealth() {
|
||
try {
|
||
await getHealth();
|
||
console.log('[finance] health: ok');
|
||
} catch (e) {
|
||
console.warn('[finance] health check failed:', e.message);
|
||
}
|
||
}
|
||
|
||
// ── Modaler ──
|
||
export function openModal(id) {
|
||
const el = $(id);
|
||
if (el) el.classList.remove('hidden');
|
||
}
|
||
|
||
export function closeModal(id) {
|
||
const el = $(id);
|
||
if (el) el.classList.add('hidden');
|
||
}
|
||
|
||
export function closeAllModals() {
|
||
document.querySelectorAll('.modal-bg').forEach(m => m.classList.add('hidden'));
|
||
}
|
||
|
||
// ── Felhantering ──
|
||
export function showError(msg) {
|
||
console.error('[finance]', msg);
|
||
// TODO: Toast/notifikation
|
||
alert(msg); // Fallback
|
||
}
|
||
|
||
// ── Start ──
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|