/* Operatør-dashboard — mobil-optimalisert for daglige rutiner. * * Hver operatør ser: * - "Mine automater" — kun de hen har tilgang til * - For hver: status, tomme hyller (klar for henting), aktive alarmer * - Quick-actions: åpne hylle for refill, marker som rensekt-ferdig, kontakt support * * Vises som ny route /operator-dashboard tilgjengelig fra sidebaren. */ const OperatorDashboard = ({ lang, tenant, dataVersion, onOpenMachine }) => { const machines = (tenant && tenant.id !== '__all__') ? DATA.machines.filter(m => m.tenantId === tenant.id) : DATA.machines; const t = I18N[lang]; // Beregn "needs attention" — automater med tomme/feilede hyller const sorted = React.useMemo(() => { return [...machines].sort((a, b) => { // Sortering: alarm > offline > tomme hyller > vanlig const aScore = (a.status !== 'online' ? 100 : 0) + (a.emptyCount * 10) + (a.vacantCount * 2); const bScore = (b.status !== 'online' ? 100 : 0) + (b.emptyCount * 10) + (b.vacantCount * 2); return bScore - aScore; }); }, [machines, dataVersion]); return (

{lang === 'no' ? 'Operatør' : 'Operator'}

{sorted.length} {lang === 'no' ? 'automater å følge' : 'automats to monitor'}
{/* Sammendragslinje */}
{lang === 'no' ? 'Trenger oppmerksomhet' : 'Needs attention'}
{sorted.filter(m => m.status !== 'online' || m.emptyCount > 4).length}
{lang === 'no' ? 'Tomme totalt' : 'Empty total'}
{sorted.reduce((s, m) => s + m.emptyCount, 0)}
{lang === 'no' ? 'Online' : 'Online'}
{sorted.filter(m => m.status === 'online').length}/{sorted.length}
{/* Liste */}
{sorted.map(m => ( onOpenMachine(m.id)} /> ))} {sorted.length === 0 && (
{lang === 'no' ? 'Ingen automater i denne tenanten' : 'No machines in this tenant'}
)}
); }; const OperatorMachineCard = ({ machine: m, lang, onOpen }) => { const empties = (m.shelves || []).filter(s => s.state === 'empty'); const errors = (m.shelves || []).filter(s => s.state === 'error'); const empType = {}; empties.forEach(s => { const code = s.bottle || 'ukjent'; empType[code] = (empType[code] || 0) + 1; }); const urgency = m.status !== 'online' ? 'danger' : m.emptyCount > 4 ? 'warn' : 'ok'; return (
{m.location}
{m.id}
{m.lastSeen}
{/* Bottle-summary inline (kun materialer med stock) */}
{Object.entries(m.fullByMaterial || {}) .filter(([_, n]) => n > 0) .map(([mat, n]) => ( {n}× {window.bottleHelpers.materialLabels[mat]?.plural || mat} ))} {empties.length > 0 && ( {empties.length} {lang === 'no' ? 'tomme' : 'empty'} )} {errors.length > 0 && ( {errors.length} {lang === 'no' ? 'feil' : 'error'} )}
{/* Tomme per type — operatør vet hva som må fylles */} {Object.keys(empType).length > 0 && (
{lang === 'no' ? 'Trenger påfyll' : 'Needs refill'}: {Object.entries(empType).map(([code, n]) => `${n}× ${code}`).join(', ')}
)}
); }; Object.assign(window, { OperatorDashboard });