/* Účtovníček — spoločné pomôcky (formátovanie, ikonky, toast, potvrdenie) */ const fmt = { eur: (n) => (n == null || isNaN(n)) ? '—' : new Intl.NumberFormat('sk-SK', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2 }).format(n), num: (n, d = 2) => (n == null || isNaN(n)) ? '—' : new Intl.NumberFormat('sk-SK', { minimumFractionDigits: d, maximumFractionDigits: d }).format(n), qty: (n) => (n == null || isNaN(n)) ? '—' : new Intl.NumberFormat('sk-SK', { maximumFractionDigits: 3 }).format(n), date: (s) => { if (!s) return '—'; const m = String(s).match(/^(\d{4})-(\d{2})-(\d{2})/); if (!m) return s; return `${+m[3]}. ${+m[2]}. ${m[1]}`; }, dateLong: (d) => new Intl.DateTimeFormat('sk-SK', { weekday: 'long', day: 'numeric', month: 'long' }).format(d), }; function plural(n, one, few, many) { const k = Math.abs(+n || 0); if (k === 1) return one; if (k >= 2 && k <= 4) return few; return many; } const STATUS = { vystavena: { label: 'Neuhradená', cls: 'orange' }, ciastocne: { label: 'Čiastočne uhradená', cls: 'blue' }, uhradena: { label: 'Uhradená', cls: 'green' }, storno: { label: 'Stornovaná', cls: 'gray' }, }; function StatusBadge({ inv }) { if (uctStore.invoices.isOverdue(inv)) return Po splatnosti; const s = STATUS[inv.status] || { label: inv.status, cls: 'gray' }; return {s.label}; } /* Čiarové ikonky (rovnaká sada ako Mzdárik + pár účtovných) */ function Icon({ name, size = 16, className = '' }) { const props = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', className }; const paths = { dashboard: <>, users: <>, file: <>, fileIn: <>, bank: <>, wallet: <>, book: <>, settings: <>, plus: <>, edit: <>, trash: <>, download: <>, upload: <>, print: <>, chevronRight: <>, x: <>, check: <>, search: <>, info: <>, clock: <>, coins: <>, list: <>, ledger: <>, scale: <>, grid: <>, arrowDown: <>, arrowUp: <>, building: <>, }; return {paths[name]}; } function useToast() { const [msg, setMsg] = React.useState(null); React.useEffect(() => { if (!msg) return; const t = setTimeout(() => setMsg(null), 2600); return () => clearTimeout(t); }, [msg]); const Toast = msg ?
{msg}
: null; return [setMsg, Toast]; } /* Potvrdenie v štýle appky (prevzaté z Mzdárika). askConfirm vracia Promise. */ function useConfirm() { const [req, setReq] = React.useState(null); const askConfirm = React.useCallback((opts) => new Promise((resolve) => { const base = typeof opts === 'string' ? { message: opts } : (opts || {}); setReq({ title: 'Potvrdenie', confirmLabel: 'Potvrdiť', cancelLabel: 'Zrušiť', danger: false, ...base, resolve }); }), []); const close = (value) => { if (!req) return; req.resolve(value); setReq(null); }; React.useEffect(() => { if (!req) return; const onKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); req.resolve(false); setReq(null); } if (e.key === 'Enter') { e.preventDefault(); req.resolve(true); setReq(null); } }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [req]); const ConfirmUI = req ? (
close(false)}>
e.stopPropagation()} role="alertdialog" aria-modal="true">

{req.title}

{req.message}
) : null; return [askConfirm, ConfirmUI]; } /* Desatinné pole bez neduhov type=number (prevzaté z Mzdárika). Prijíma čiarku aj bodku. */ function DecimalInput({ value, onCommit, className, ...rest }) { const fromValue = (v) => (v === '' || v == null || (typeof v === 'number' && isNaN(v))) ? '' : String(v).replace('.', ','); const [text, setText] = React.useState(() => fromValue(value)); const [editing, setEditing] = React.useState(false); React.useEffect(() => { if (!editing) setText(fromValue(value)); }, [value, editing]); const handle = (e) => { let s = String(e.target.value); const neg = s.trim().startsWith('-'); s = s.replace(/[^0-9.,]/g, '').replace(/\./g, ','); const parts = s.split(','); if (parts.length > 2) s = parts[0] + ',' + parts.slice(1).join(''); s = (neg ? '-' : '') + s; setText(s); const n = parseFloat(s.replace(',', '.')); onCommit(isNaN(n) ? 0 : n); }; return { setEditing(true); e.target.select(); }} onBlur={() => { setEditing(false); setText(fromValue(value)); }} onChange={handle} />; } function Stat({ label, value, delta, muted, dir }) { return (
{label}
{value}
{delta &&
{delta}
}
); } /* Bočný panel (drawer) ako v Mzdáriku. */ function Drawer({ title, sub, onClose, footer, children, wide }) { React.useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose]); return (
e.stopPropagation()}>

{title}

{sub &&
{sub}
}
{children}
{footer &&
{footer}
}
); } function Field({ label, hint, children, style }) { return (
{label && } {children} {hint &&
{hint}
}
); } /* Chybová hláška pre formuláre - vždy povie, čo sa stalo. */ function ErrorNote({ error, onClose }) { if (!error) return null; return (
Nepodarilo sa uložiť.
{error}
{onClose && }
); } /* Výber účtu z osnovy: píšeš číslo alebo názov, ponúkne sa zoznam (datalist). */ function AccountPicker({ value, onChange, prefixes, placeholder, style, className }) { const listId = React.useMemo(() => 'acc-dl-' + Math.random().toString(36).slice(2), []); const accounts = prefixes && prefixes.length ? uctStore.accounts.byClass(...prefixes) : uctStore.accounts.list(); const name = uctStore.accounts.name(value); return (
onChange(e.target.value.replace(/\s.*$/, '').trim())} title={name} /> {accounts.map(a => )} {value &&
{name || 'účet nie je v osnove'}
}
); } /* Výber partnera: select + rýchle pridanie. onPick dostane partnera alebo null. */ function PartnerSelect({ value, onPick, allowNew = true, placeholder = '— vyber partnera —' }) { const [adding, setAdding] = React.useState(false); const partners = uctStore.partners.list(); return (
{allowNew && } {adding && setAdding(false)} onSaved={(p) => { setAdding(false); onPick(p); }} />}
); } /* Spinner / načítavam */ function Loading({ text = 'Načítavam…' }) { return
{text}
; } window.fmt = fmt; window.Icon = Icon; window.plural = plural; window.STATUS = STATUS;