/* ============================================================ KODILA — skupne UI komponente in pripomočki ============================================================ */ const { useState, useEffect, useRef, useCallback, useMemo } = React; /* ---------- Formatiranje ---------- */ function fmtEur(n, withSign) { if (n === null || n === undefined || n === '') return ''; const s = Number(n).toLocaleString('sl-SI', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return (withSign && n > 0 ? '+' : '') + s + ' €'; } function fmtNum(n, dec) { if (n === null || n === undefined || n === '') return ''; return Number(n).toLocaleString('sl-SI', { minimumFractionDigits: dec || 0, maximumFractionDigits: dec === undefined ? 2 : dec }); } function fmtDate(iso) { if (!iso || iso === '—') return '—'; const [y, m, d] = iso.split('-'); return `${d}. ${m}. ${y}`; } function vatRate(s) { return parseFloat(String(s).replace('%','').replace(',','.').trim()) || 0; } /* ---------- Click-outside ---------- */ function useClickOutside(onClose) { const ref = useRef(null); useEffect(() => { function h(e) { if (ref.current && !ref.current.contains(e.target)) onClose(); } document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, [onClose]); return ref; } /* ---------- Dropdown ---------- */ function Dropdown({ trigger, children, align, width }) { const [open, setOpen] = useState(false); const ref = useClickOutside(() => setOpen(false)); return (
setOpen(o => !o)}>{trigger(open)}
{open && (
{ if (e.target.closest('.dd-item')) setOpen(false); }}> {typeof children === 'function' ? children(setOpen) : children}
)}
); } /* ---------- Status badge ---------- */ const STATUS_MAP = { // artikli 'Aktiven': 'ok', 'Razprodano': 'warn', 'Nedejaven': 'default', // dokumenti 'Osnutek': 'default','Izdan': 'info', 'Plačan': 'ok', 'Zapadel': 'dng', 'Storniran': 'warn', // šifranti (valute, države) 'Aktivna': 'ok', 'Nedejavna': 'default', }; function StatusBadge({ value }) { const cls = STATUS_MAP[value] || 'default'; return {value}; } /* ---------- Toast (global) ---------- */ const ToastCtx = React.createContext(() => {}); function ToastProvider({ children }) { const [items, setItems] = useState([]); const push = useCallback((msg, icon) => { const id = Math.random(); setItems(x => [...x, { id, msg, icon: icon || 'check' }]); setTimeout(() => setItems(x => x.filter(i => i.id !== id)), 2600); }, []); return ( {children}
{items.map(i => (
{i.msg}
))}
); } function useToast() { return React.useContext(ToastCtx); } /* ---------- Sparkline (KPI) ---------- */ function Sparkline({ data, up }) { const w = 88, h = 40, max = Math.max(...data), min = Math.min(...data); const pts = data.map((v, i) => { const x = (i / (data.length - 1)) * w; const y = h - 4 - ((v - min) / (max - min || 1)) * (h - 10); return [x, y]; }); const d = pts.map((p, i) => (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' '); const area = d + ` L${w} ${h} L0 ${h} Z`; const col = up ? 'var(--success)' : 'var(--danger)'; const gid = 'sg' + Math.random().toString(36).slice(2, 7); return ( ); } /* ---------- Tint helper ---------- */ function tintStyle(tint) { const map = { brand: { bg: 'var(--brand-soft)', fg: 'var(--brand-strong)' }, info: { bg: 'var(--info-soft)', fg: 'var(--info)' }, success: { bg: 'var(--success-soft)', fg: 'var(--success)' }, warning: { bg: 'var(--warning-soft)', fg: 'color-mix(in oklch, var(--warning), black 15%)' }, danger: { bg: 'var(--danger-soft)', fg: 'var(--danger)' }, }; const t = map[tint] || map.brand; return { background: t.bg, color: t.fg }; } /* ---------- Stran v izgradnji ---------- */ function UnderConstruction({ title }) { return (

Modul v izgradnji

{title || 'Ta modul še ni zgrajen.'} Dodali ga bomo, ko bo pripravljen.

); } Object.assign(window, { fmtEur, fmtNum, fmtDate, vatRate, useClickOutside, Dropdown, StatusBadge, STATUS_MAP, ToastProvider, useToast, Sparkline, tintStyle, UnderConstruction, });