/* ============================================================
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,
});