/* Účtovníček — kostra appky: prihlásenie, výber režimu a firmy, bočné menu
* podľa režimu, obsah vpravo. Adresa: #rezim/firma/stranka[:akcia:id]. */
function parseHash() {
const h = (location.hash || '').slice(1);
const parts = h.split('/');
if (parts.length >= 3) return { mode: parts[0], firma: parts[1], route: parts.slice(2).join('/') || 'dashboard' };
return { mode: '', firma: '', route: h || 'dashboard' }; // stará adresa bez režimu
}
function App({ onLogout }) {
const [, setTick] = React.useState(0);
const rerender = () => setTick(v => v + 1);
const [route, setRoute] = React.useState(() => parseHash().route);
const [busy, setBusy] = React.useState(false);
const [setToast, Toast] = useToast();
React.useEffect(() => {
const onHash = async () => {
const p = parseHash();
if (p.mode && uctStore.ctx.resolve(p.mode, p.firma)) {
setBusy(true);
try { await uctStore.__hydrate(); } catch (e) { if (!(e instanceof AuthError)) setToast(e.message); }
setBusy(false);
}
setRoute(p.route);
};
window.addEventListener('hashchange', onHash);
return () => window.removeEventListener('hashchange', onHash);
}, []);
React.useEffect(() => {
window.addEventListener('uct:changed', rerender);
return () => window.removeEventListener('uct:changed', rerender);
}, []);
const mode = uctStore.ctx.mode();
const firma = uctStore.ctx.firma();
const go = (r) => {
const h = '#' + mode + '/' + firma + '/' + r;
if (location.hash !== h) history.replaceState(null, '', h);
setRoute(r);
};
const switchTo = async (m, f) => {
setBusy(true);
try {
await uctStore.ctx.set(m, f);
const r = 'dashboard';
history.replaceState(null, '', '#' + uctStore.ctx.mode() + '/' + uctStore.ctx.firma() + '/' + r);
setRoute(r);
} catch (e) { if (!(e instanceof AuthError)) setToast(e.message); }
setBusy(false);
};
const top = route.split(':')[0];
const company = uctStore.company.get();
const settings = uctStore.settings.get();
const unpaid = (kind) => uctStore.invoices.list(kind).filter(i => i.status === 'vystavena' || i.status === 'ciastocne').length;
const modes = uctStore.ctx.modes();
const firms = uctStore.ctx.companiesFor(mode);
const user = uctStore.auth.user();
const modeLabel = (uctStore.MODES[mode] || {}).label || '';
const logout = async () => { await uctStore.auth.logout(); onLogout(); };
return (
{modeLabel}
›
{company.name || firma}
›
{labelFor(top)}
{fmt.dateLong(new Date())}
·
{user ? (user.name || user.login) : ''}
{busy && Načítavam dáta firmy…
}
{settings.demoDataPresent && (
V appke sú ukážkové (vymyslené) dáta, aby bolo vidno, ako vyzerá. Keď začneš účtovať naozaj, vymaž ich.
)}
{top === 'dashboard' && }
{top === 'invoices' && }
{top === 'proforma' && }
{top === 'purchase' && }
{top === 'bank' && }
{top === 'cash' && }
{top === 'internal' && }
{top === 'journal' && }
{top === 'ledger' && }
{top === 'saldo' && }
{top === 'accounts' && }
{top === 'receivables' && }
{top === 'incomes' && }
{top === 'taxcalc' && }
{top === 'partners' && }
{top === 'settings' && }
{Toast}
);
}
function NavBtn({ icon, label, active, onClick, count, soon }) {
return (
);
}
function labelFor(top) {
return ({
dashboard: 'Nástenka', invoices: 'Vystavené faktúry', proforma: 'Zálohové faktúry', purchase: 'Prijaté faktúry', bank: 'Banka',
cash: 'Pokladňa', internal: 'Interné doklady', journal: 'Účtovný denník', ledger: 'Hlavná kniha', saldo: 'Saldokonto',
accounts: 'Účtová osnova', partners: 'Partneri', settings: 'Nastavenia', receivables: 'Pohľadávky', incomes: 'Príjmy', taxcalc: 'Výpočet dane',
})[top] || top;
}
/* --- Prihlásenie / prvé nastavenie účtu --- */
function LoginScreen({ state, onDone }) {
const setupNeeded = !!state.setupNeeded;
const [login, setLogin] = React.useState('');
const [name, setName] = React.useState('');
const [pw, setPw] = React.useState('');
const [pw2, setPw2] = React.useState('');
const [err, setErr] = React.useState(null);
const [busy, setBusy] = React.useState(false);
const submit = async (e) => {
e.preventDefault();
setErr(null);
if (setupNeeded && pw !== pw2) { setErr('Heslá sa nezhodujú.'); return; }
setBusy(true);
try {
if (setupNeeded) await uctStore.auth.setup(login, name, pw);
else await uctStore.auth.login(login, pw);
onDone();
} catch (e2) { setErr(e2.message); }
setBusy(false);
};
const provider = state.provider || 'Authentik';
return (
Účtovníček
{state.oidc ? (
<>
Prihlás sa firemným účtom cez {provider}. Dáta sú na serveri Účtovníčka, rovnaké v PC aj v telefóne.
Prihlásiť sa cez {provider}
>
) : (
<>
{setupNeeded ? 'Prvé spustenie bez servera: vytvor si miestny účet.' : 'Skúšobný režim bez Authentiku: miestny účet a heslo.'}
>
)}
);
}
/* --- Štart: stav prihlásenia -> login alebo appka --- */
function Root() {
const [phase, setPhase] = React.useState('loading'); // loading | login | app | error
const [authState, setAuthState] = React.useState({});
const [error, setError] = React.useState('');
const start = async () => {
try {
const st = await uctStore.auth.load();
if (!st.user) { setAuthState(st); setPhase('login'); return; }
const p = parseHash();
uctStore.ctx.resolve(p.mode, p.firma);
if (!uctStore.ctx.mode()) { setError('Používateľ nemá priradenú žiadnu firmu.'); setPhase('error'); return; }
const r = p.mode ? p.route : 'dashboard';
history.replaceState(null, '', '#' + uctStore.ctx.mode() + '/' + uctStore.ctx.firma() + '/' + r);
await uctStore.__hydrate();
setPhase('app');
} catch (e) {
if (e instanceof AuthError) { setAuthState(uctStore.auth.state() || {}); setPhase('login'); return; }
setError(e.message || String(e)); setPhase('error');
}
};
React.useEffect(() => { start(); }, []);
React.useEffect(() => {
const onLogout = () => setPhase('login');
window.addEventListener('uct:logout', onLogout);
return () => window.removeEventListener('uct:logout', onLogout);
}, []);
if (phase === 'loading') return Načítavam dáta…
;
if (phase === 'error') return Účtovníček sa nepodarilo načítať
{error}
Skús okno zavrieť a Účtovníček spustiť znova.
;
if (phase === 'login') return { setPhase('loading'); start(); }} />;
return setPhase('login')} />;
}
const __root = document.getElementById('root');
ReactDOM.createRoot(__root).render();