/* Tenant overview page: KPIs, machines grid/list, alarms */
const Overview = ({ lang, onOpenMachine, tenant, dataVersion }) => {
const t = I18N[lang];
// Bruk filtrert liste (basert på valgt tenant), faller tilbake til alle.
// Filtrer eksplisitt fra prop for å unngå å lese mutert DATA.machinesView
// som ikke trigger re-render ved tenant-bytte.
const activeTenant = tenant || DATA.activeTenant || { id: '__all__' };
const machines = React.useMemo(() => {
const all = DATA.machines || [];
if (!activeTenant || activeTenant.id === '__all__') return all;
return all.filter(m => m.tenantId === activeTenant.id);
}, [activeTenant.id, dataVersion]);
// Varsler KUN for valgt tenants automater (2026-09-04: før lekket varsler
// fra alle tenants inn i oversikten og telleren når superadmin så som
// tenant / brukeren var medlem av flere tenants).
const ALARMS = React.useMemo(() => {
const ids = new Set(machines.map(m => m.id));
return (DATA.ALARMS || []).filter(a => ids.has(a.machineId));
}, [machines]);
const [view, setView] = React.useState('grid');
const [filter, setFilter] = React.useState('all');
// Område-filter + multi-select for bulk-redigering
const [areaFilter, setAreaFilter] = React.useState('');
const [selectedIds, setSelectedIds] = React.useState(() => new Set());
const [bulkOpen, setBulkOpen] = React.useState(false);
const areas = React.useMemo(() => {
const all = DATA.AREAS || [];
if (!activeTenant || activeTenant.id === '__all__') return all;
return all.filter(a => a.tenant_id === activeTenant.id);
}, [activeTenant.id, dataVersion]);
const toggleSelect = (id) => setSelectedIds(prev => {
const next = new Set(prev);
if (next.has(id)) next.delete(id); else next.add(id);
return next;
});
// Beregn KPIer for FILTRERT sett (per tenant)
const k = React.useMemo(() => {
const empty = machines.reduce((s, m) => s + (m.emptyCount || 0), 0);
const vacant = machines.reduce((s, m) => s + (m.vacantCount || 0), 0);
const todayRev = machines.reduce((s, m) => s + (m.todayRev || 0), 0);
const monthRev = machines.reduce((s, m) => s + (m.monthRev || 0), 0);
const fullTotal = machines.reduce((s, m) => s + (m.fullTotal || 0), 0);
const byMaterial = {};
machines.forEach(m => Object.entries(m.fullByMaterial || {}).forEach(([mat, n]) => {
byMaterial[mat] = (byMaterial[mat] || 0) + n;
}));
// Ekte 14-dagers omsetningskurve: sum av automatenes spark (kr/dag).
// (Før: hardkodede tall — falsk graf.)
const spark = Array.from({ length: 14 }, (_, i) =>
machines.reduce((s, m) => s + (((m.spark || [])[i]) || 0), 0));
const hasSpark = spark.some(v => v > 0);
return { empty, vacant, todayRev, monthRev, fullTotal, byMaterial, spark: hasSpark ? spark : null };
}, [machines]);
const filtered = React.useMemo(() => {
let list = machines;
if (areaFilter === '__none__') list = list.filter(m => !m.areaId);
else if (areaFilter) list = list.filter(m => m.areaId === areaFilter);
if (filter === 'all') return list;
if (filter === 'legacy') return list.filter(m => m.kind === 'legacy');
if (filter === 'v2') return list.filter(m => m.kind === 'v2');
return list.filter(m => m.status === filter);
}, [filter, areaFilter, machines]);
// Sorter materialer etter antall (synker), dropp nuller
const matLabels = window.bottleHelpers.materialLabels;
const materialKpis = Object.entries(k.byMaterial)
.filter(([_, n]) => n > 0)
.sort((a, b) => b[1] - a[1]);
const kpis = [
{ label: t.kpi_today_rev, value: fmtKr(k.todayRev, lang), dir: 'neutral' },
{ label: t.kpi_month_rev, value: fmtKr(k.monthRev, lang), dir: 'neutral', spark: k.spark || undefined },
{ label: lang === 'no' ? 'Fulle totalt' : 'Full total',
value: fmtNum(k.fullTotal, lang), unit: lang === 'no' ? 'stk' : 'pcs', dir: 'neutral' },
...materialKpis.slice(0, 2).map(([mat, n]) => ({
label: (lang === 'no' ? 'Fulle ' : 'Full ') + (matLabels[mat]?.plural || mat).toLowerCase(),
value: fmtNum(n, lang), unit: lang === 'no' ? 'stk' : 'pcs', dir: 'neutral',
})),
{ label: t.kpi_empty, value: fmtNum(k.empty, lang), unit: lang === 'no' ? 'stk' : 'pcs', dir: 'neutral' },
{ label: t.kpi_vacant, value: fmtNum(k.vacant, lang), unit: lang === 'no' ? 'hyller' : 'shelves', dir: 'neutral' },
];
// (Onboarding-progress brukes ikke her — vises på Plattform-siden i stedet)
return (
{t.overview_title}
{t.overview_sub} · {t.live}
{/* Export laster ned ordre-CSV for valgt tenant. «Legg til automat»
er fjernet her — tenant_admin kan ikke opprette automater, og
superadmin gjør det via Innstillinger (unngår en død CTA). */}
{/* Empty state hvis ingen automater */}
{machines.length === 0 && (
{lang === 'no'
? 'Denne tenanten har ingen automater tildelt. Be superadmin om å tildele eller opprette en.'
: 'This tenant has no automats. Ask superadmin to assign or create one.'}
)}
{machines.length > 0 && (
{kpis.map((k, i) => (
{k.label}
{k.value}{k.unit && {k.unit}}
{/* «vs forrige» vises kun når en delta faktisk er beregnet
(2026-09-05) — før sto teksten alltid der med tomt tall. */}
{k.delta !== undefined && (
{bulkOpen && (
selectedIds.has(m.id))}
areas={areas}
onClose={() => setBulkOpen(false)}
onDone={() => {
// Bulk endrer priser/områder som er cachet ved innlasting —
// full reload er enklest og trygt for en sjelden operasjon
// (samme mønster som flaskekatalog-endringer).
location.reload();
}} />
)}
{/* Alarms */}