This commit is contained in:
mdares
2026-01-11 22:07:01 +00:00
parent d0ab254dd7
commit f231d87ae3
2 changed files with 78 additions and 18 deletions

View File

@@ -90,6 +90,13 @@ type TimelineSeg = {
state: TimelineState;
};
type ActiveStoppage = {
state: "microstop" | "macrostop";
startedAt: string;
durationSec: number;
theoreticalCycleTime: number;
};
type UploadState = {
status: "idle" | "parsing" | "uploading" | "success" | "error";
message?: string;
@@ -246,9 +253,12 @@ export default function MachineDetailClient() {
const [error, setError] = useState<string | null>(null);
const [cycles, setCycles] = useState<CycleRow[]>([]);
const [thresholds, setThresholds] = useState<Thresholds | null>(null);
const [activeStoppage, setActiveStoppage] = useState<ActiveStoppage | null>(null);
const [open, setOpen] = useState<null | "events" | "deviation" | "impact">(null);
const fileInputRef = useRef<HTMLInputElement | null>(null);
const [uploadState, setUploadState] = useState<UploadState>({ status: "idle" });
const [nowMs, setNowMs] = useState(() => Date.now());
const BUCKET = {
normal: {
@@ -308,6 +318,7 @@ export default function MachineDetailClient() {
setEvents(json.events ?? []);
setCycles(json.cycles ?? []);
setThresholds(json.thresholds ?? null);
setActiveStoppage(json.activeStoppage ?? null);
setError(null);
setLoading(false);
} catch {
@@ -325,6 +336,11 @@ export default function MachineDetailClient() {
};
}, [machineId, t]);
useEffect(() => {
const timer = setInterval(() => setNowMs(Date.now()), 1000);
return () => clearInterval(timer);
}, []);
async function parseWorkOrdersFile(file: File) {
const name = file.name.toLowerCase();
if (name.endsWith(".csv")) {
@@ -430,6 +446,20 @@ export default function MachineDetailClient() {
return `${v}`;
}
function formatDurationShort(totalSec?: number | null) {
if (totalSec === null || totalSec === undefined || Number.isNaN(totalSec)) {
return t("common.na");
}
const sec = Math.max(0, Math.floor(totalSec));
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = sec % 60;
if (h > 0) return `${h}h ${m}m`;
if (m > 0) return `${m}m ${s}s`;
return `${s}s`;
}
function timeAgo(ts?: string) {
if (!ts) return t("common.never");
const diff = Math.floor((Date.now() - new Date(ts).getTime()) / 1000);
@@ -815,6 +845,8 @@ export default function MachineDetailClient() {
});
}
return { windowSec, segments: segs, start, end };
}, [cycles, cycleTarget, thresholds]);

View File

@@ -312,6 +312,34 @@ const rawCycles = await prisma.machineCycle.findMany({
sku: true,
},
});
const latestCycle = rawCycles[0] ?? null;
let activeStoppage: {
state: "microstop" | "macrostop";
startedAt: string;
durationSec: number;
theoreticalCycleTime: number;
} | null = null;
if (latestCycle?.ts && effectiveCycleTime && effectiveCycleTime > 0) {
const elapsedSec = (Date.now() - latestCycle.ts.getTime()) / 1000;
const microThresholdSec = effectiveCycleTime * microMultiplier;
const macroThresholdSec = effectiveCycleTime * macroMultiplier;
if (elapsedSec >= microThresholdSec) {
const isMacro = elapsedSec >= macroThresholdSec;
const state = isMacro ? "macrostop" : "microstop";
const thresholdSec = isMacro ? macroThresholdSec : microThresholdSec;
const startedAtMs = latestCycle.ts.getTime() + thresholdSec * 1000;
activeStoppage = {
state,
startedAt: new Date(startedAtMs).toISOString(),
durationSec: Math.max(0, Math.floor(elapsedSec - thresholdSec)),
theoreticalCycleTime: effectiveCycleTime,
};
}
}
// chart-friendly: oldest -> newest + numeric timestamps
const cycles = rawCycles
@@ -340,14 +368,14 @@ const cycles = rawCycles
latestHeartbeat: machine.heartbeats[0] ?? null,
latestKpi: machine.kpiSnapshots[0] ?? null,
effectiveCycleTime,
},
thresholds: {
stoppageMultiplier: microMultiplier,
macroStoppageMultiplier: macroMultiplier,
},
activeStoppage,
events,
cycles
cycles,
});
}