Files
boc/landvex-finance-sprint3/js/main.mjs
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- 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
2026-06-29 16:24:48 +00:00

194 lines
4.8 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.
/**
* 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} ${['JanMar', 'AprJun', 'JulSep', 'OktDec'][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();
}