Initial commit with Dockerfile and demo code
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Line KPI tiles.
|
||||
*
|
||||
* OEE gets a hero figure; its three factors sit beside it as their own tiles with
|
||||
* magnitude meters. Deliberately tiles rather than a chart: these are five
|
||||
* single headline numbers, and a bar chart of five unrelated measures would be
|
||||
* harder to read, not easier.
|
||||
*/
|
||||
|
||||
import { useStore } from '../store.js';
|
||||
|
||||
function Meter({ value, color }) {
|
||||
return (
|
||||
<div className="meter">
|
||||
<span style={{ width: `${Math.max(0, Math.min(100, value))}%`, background: color }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Colour the OEE factors by how bad the loss is, always beside a number. */
|
||||
function factorColor(v) {
|
||||
if (v >= 0.95) return 'var(--good)';
|
||||
if (v >= 0.85) return 'var(--series-1)';
|
||||
if (v >= 0.70) return 'var(--warning)';
|
||||
return 'var(--critical)';
|
||||
}
|
||||
|
||||
export function KpiRow() {
|
||||
const kpi = useStore((s) => (s.frame ? s.frame.kpi : null));
|
||||
const windowSec = useStore((s) => (s.meta ? s.meta.kpiWindowSec : 1200));
|
||||
|
||||
if (!kpi) {
|
||||
return <div className="card"><div className="empty">Waiting for telemetry…</div></div>;
|
||||
}
|
||||
|
||||
const factors = [
|
||||
{ label: 'Availability', v: kpi.availability, foot: 'Stoppage losses' },
|
||||
{ label: 'Performance', v: kpi.performance, foot: 'Micro-stops, slow cycles' },
|
||||
{ label: 'Quality', v: kpi.quality, foot: 'Rejects at INS-04' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="kpi-row">
|
||||
<div className="tile hero">
|
||||
<span className="label">OEE · rolling {Math.round(windowSec / 60)} min</span>
|
||||
<span className="value" style={{ color: factorColor(kpi.oee) }}>
|
||||
{(kpi.oee * 100).toFixed(1)}<span className="unit">%</span>
|
||||
</span>
|
||||
<Meter value={kpi.oee * 100} color={factorColor(kpi.oee)} />
|
||||
<span className="foot">Availability × Performance × Quality</span>
|
||||
</div>
|
||||
|
||||
{factors.map((f) => (
|
||||
<div className="tile" key={f.label}>
|
||||
<span className="label">{f.label}</span>
|
||||
<span className="value">{(f.v * 100).toFixed(1)}<span className="unit">%</span></span>
|
||||
<Meter value={f.v * 100} color={factorColor(f.v)} />
|
||||
<span className="foot">{f.foot}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="tile">
|
||||
<span className="label">Output</span>
|
||||
<span className="value">{kpi.throughputPerHour.toFixed(0)}<span className="unit">/h</span></span>
|
||||
<span className="foot">
|
||||
{kpi.good.toLocaleString()} good · {kpi.rejected.toLocaleString()} rejected
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="tile">
|
||||
<span className="label">Energy</span>
|
||||
<span className="value">{kpi.energyKw.toFixed(0)}<span className="unit">kW</span></span>
|
||||
<span className="foot">
|
||||
{kpi.energyPerUnit > 0 ? `${kpi.energyPerUnit.toFixed(3)} kWh/unit` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user