/* 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 (
{/* 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 });