/* Aktivitet (KUN superadmin): full oversikt over hva hver bruker GJØR og SER PÅ. Bygger på audit-loggen: alle skrive-handlinger + GET-visninger (dedupliseret til én pr. minutt pr. side). Filtrer på bruker, se tidslinje med menneskelesbare beskrivelser. */ const ACT_LABELS = [ [/\/route-plan/, 'Kjørerute-planlegger'], [/\/refill\/plans\/[^/]+\/manifest/, 'Digitalt fraktbrev'], [/\/refill\/plans\/[^/]+\/stops\/[^/]+\/fill-status/, 'Fylle-status (stopp)'], [/\/refill\/plans\/[^/]+\/stops\/[^/]+\/arrive/, 'Ankom stopp (åpnet dører)'], [/\/refill\/plans\/[^/]+\/stops\/[^/]+\/rescan/, 'Re-skann av kammer'], [/\/refill\/plans\/[^/]+\/stops\/[^/]+\/complete/, 'Signerte fylle-tall'], [/\/refill\/plans/, 'Fylleplaner'], [/\/refill\/.+\/overview/, 'Utkjøringslista'], [/\/service\/(.+)/, (m) => `Servicelogg: ${m[1]}`], [/\/automats\/bulk-edit/, 'Bulk-redigering av automater'], [/\/automats\/([^/]+)\/shelves/, (m) => `Hyller på ${m[1]}`], [/\/shelves\/([^/]+)\/shelves\/\d+\/override/, (m) => `Overstyrte hylle på ${m[1]}`], [/\/shelves\/([^/]+)\/shelves/, (m) => `Lager på ${m[1]}`], [/\/shelves\/([^/]+)\/fill/, (m) => `Påfyll på ${m[1]}`], [/\/automats\/([^/]+)\/settings/, (m) => `Innstillinger for ${m[1]}`], [/\/automats\/([^/]+)\/location/, (m) => `Posisjon for ${m[1]}`], [/\/automats\/([^/]+)\/telemetry/, (m) => `Telemetri for ${m[1]}`], [/\/automats\/([^/]+)$/, (m) => `Automat ${m[1]}`], [/\/automats$/, 'Automat-oversikten'], [/\/sales\/orders/, 'Salgshistorikk'], [/\/sales\/summary/, 'Salgssammendrag'], [/\/sales/, 'Salg'], [/\/orders\/alarms/, 'Alarmer'], [/\/orders\/([^/]+)/, (m) => `Ordre ${m[1]}`], [/\/areas/, 'Områder'], [/\/bottles\/price-overrides/, 'Prisoverstyringer'], [/\/bottles/, 'Flasketyper'], [/\/analytics/, 'Statistikk'], [/\/tenants/, 'Tenants'], [/\/users/, 'Brukere'], [/\/audit/, 'Aktivitetsloggen'], [/\/exports/, 'Eksport'], [/\/cameras/, 'Kameraer'], [/\/production/, 'Produksjonskonsoll'], ]; function actLabel(path) { const p = path.replace('/api/admin', ''); for (const [re, label] of ACT_LABELS) { const m = p.match(re); if (m) return typeof label === 'function' ? label(m) : label; } return p; } const ActivityPage = ({ lang, dataVersion }) => { const no = lang === 'no'; const [tab, setTab] = React.useState('activity'); // activity | logins const [rows, setRows] = React.useState(null); const [logins, setLogins] = React.useState(null); const [userFilter, setUserFilter] = React.useState(''); const [live, setLive] = React.useState(true); const refresh = React.useCallback(async () => { try { if (tab === 'logins') { setLogins(await window.api.logins(userFilter)); } else { const q = userFilter ? `&user_email=${encodeURIComponent(userFilter)}` : ''; setRows(await window.api.req('GET', `/api/admin/audit?limit=300${q}`)); } } catch (e) { if (tab === 'logins') setLogins({ error: e.message }); else setRows({ error: e.message }); } }, [userFilter, tab]); React.useEffect(() => { refresh(); if (!live) return; const iv = setInterval(refresh, 10000); return () => clearInterval(iv); }, [refresh, live, dataVersion]); const users = React.useMemo(() => { if (!Array.isArray(rows)) return []; const m = new Map(); rows.forEach(r => { if (!r.user_email) return; const u = m.get(r.user_email) || { email: r.user_email, n: 0, last: r.created_at }; u.n += 1; m.set(r.user_email, u); }); return [...m.values()].sort((a, b) => b.n - a.n); }, [rows]); const fmtTime = (iso) => (iso || '').replace('T', ' ').slice(5, 16); return (