Improve downtime qualification visibility
This commit is contained in:
@@ -192,6 +192,16 @@ export default function App() {
|
||||
|
||||
const selectedMachine = dashboard?.machines.find((machine) => machine.id === selectedMachineId) ?? null;
|
||||
const machines = dashboard?.machines ?? [];
|
||||
const selectedMachineOpenDowntimes =
|
||||
selectedMachineId === null ? [] : openDowntimes.filter((downtime) => downtime.machine_id === selectedMachineId);
|
||||
const downtimeOptions = [...openDowntimes].sort((left, right) => {
|
||||
const leftSelected = selectedMachineId !== null && left.machine_id === selectedMachineId;
|
||||
const rightSelected = selectedMachineId !== null && right.machine_id === selectedMachineId;
|
||||
if (leftSelected !== rightSelected) {
|
||||
return Number(rightSelected) - Number(leftSelected);
|
||||
}
|
||||
return new Date(left.start_time).getTime() - new Date(right.start_time).getTime();
|
||||
});
|
||||
const summaryMetrics = {
|
||||
production: machines.filter((machine) => machine.status === "production").length,
|
||||
openDowntimes: openDowntimes.length,
|
||||
@@ -403,6 +413,22 @@ export default function App() {
|
||||
return toasts[key] ? <div className="inline-toast">{toasts[key]}</div> : null;
|
||||
}
|
||||
|
||||
function downtimeMachineLabel(downtime: Downtime) {
|
||||
if (downtime.machine_code) {
|
||||
return downtime.machine_code;
|
||||
}
|
||||
const machine = machines.find((item) => item.id === downtime.machine_id);
|
||||
return machine?.code ?? `Machine ${downtime.machine_id ?? "-"}`;
|
||||
}
|
||||
|
||||
function downtimeOptionLabel(downtime: Downtime) {
|
||||
const parts = [`#${downtime.id}`, downtimeMachineLabel(downtime)];
|
||||
if (downtime.order_number) {
|
||||
parts.push(downtime.order_number);
|
||||
}
|
||||
return parts.join(" / ");
|
||||
}
|
||||
|
||||
function renderModule(): ReactNode {
|
||||
if (activeModule === "atelier") {
|
||||
return (
|
||||
@@ -631,23 +657,31 @@ export default function App() {
|
||||
|
||||
if (activeModule === "downtimes") {
|
||||
return (
|
||||
<Panel title="Qualification Arret" subtitle={`${openDowntimes.length} open downtime(s)`}>
|
||||
<Panel
|
||||
title="Qualification Arret"
|
||||
subtitle={
|
||||
selectedMachine
|
||||
? `${selectedMachineOpenDowntimes.length} open for ${selectedMachine.code} / ${openDowntimes.length} total`
|
||||
: `${openDowntimes.length} open downtime(s)`
|
||||
}
|
||||
>
|
||||
<div className="module-grid module-grid--form-table">
|
||||
<form
|
||||
className="form-grid module-form"
|
||||
key={selectedMachine?.id ?? "all-downtimes"}
|
||||
onSubmit={submitDowntimeQualification}
|
||||
onBlurCapture={(event) => handleFormBlur("downtime", event.currentTarget)}
|
||||
onInputCapture={(event) => validateForm(event.currentTarget, "downtime")}
|
||||
onChangeCapture={(event) => validateForm(event.currentTarget, "downtime")}
|
||||
noValidate
|
||||
>
|
||||
<select name="downtime_id" required defaultValue="">
|
||||
<select name="downtime_id" required defaultValue={selectedMachineOpenDowntimes[0]?.id ?? ""}>
|
||||
<option value="" disabled>
|
||||
Open downtime
|
||||
</option>
|
||||
{openDowntimes.map((downtime) => (
|
||||
{downtimeOptions.map((downtime) => (
|
||||
<option key={downtime.id} value={downtime.id}>
|
||||
#{downtime.id} machine {downtime.machine_id}
|
||||
{downtimeOptionLabel(downtime)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -673,17 +707,47 @@ export default function App() {
|
||||
{toast("downtime")}
|
||||
</form>
|
||||
|
||||
<ul className="timeline-list">
|
||||
{openDowntimes.map((item) => (
|
||||
<li key={item.id}>
|
||||
<div>
|
||||
<strong>Machine {item.machine_id}</strong>
|
||||
<span>Waiting qualification</span>
|
||||
</div>
|
||||
<time>{new Date(item.start_time).toLocaleString()}</time>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="downtime-board">
|
||||
{selectedMachine ? (
|
||||
<section className="downtime-focus">
|
||||
<span className="meta-label">Selected machine</span>
|
||||
<strong>{selectedMachine.code}</strong>
|
||||
{selectedMachineOpenDowntimes.length > 0 ? (
|
||||
<ul className="timeline-list timeline-list--compact">
|
||||
{selectedMachineOpenDowntimes.map((item) => (
|
||||
<li key={item.id}>
|
||||
<div>
|
||||
<strong>{downtimeOptionLabel(item)}</strong>
|
||||
<span>{item.reason_code ?? "Waiting qualification"}</span>
|
||||
</div>
|
||||
<time>{new Date(item.start_time).toLocaleString()}</time>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="empty-state">No open downtime for {selectedMachine.code}.</div>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section>
|
||||
<span className="meta-label">All open stops</span>
|
||||
<ul className="timeline-list">
|
||||
{downtimeOptions.map((item) => (
|
||||
<li key={item.id} className={item.machine_id === selectedMachineId ? "is-current-machine" : ""}>
|
||||
<div>
|
||||
<strong>{downtimeMachineLabel(item)}</strong>
|
||||
<span>
|
||||
{item.order_number ? `${item.order_number} / ` : ""}
|
||||
{item.reason_code ?? "Waiting qualification"}
|
||||
</span>
|
||||
</div>
|
||||
<time>{new Date(item.start_time).toLocaleString()}</time>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
{openDowntimes.length === 0 ? <div className="empty-state">No open downtime currently needs qualification.</div> : null}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
@@ -83,7 +83,9 @@ export type Cycle = {
|
||||
export type Downtime = {
|
||||
id: number;
|
||||
machine_id?: number;
|
||||
machine_code?: string | null;
|
||||
production_order_id: number | null;
|
||||
order_number?: string | null;
|
||||
start_time: string;
|
||||
end_time: string | null;
|
||||
duration_sec: number | null;
|
||||
|
||||
@@ -708,6 +708,11 @@ th {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.timeline-list li.is-current-machine {
|
||||
border-radius: 8px;
|
||||
background: var(--accent-light);
|
||||
}
|
||||
|
||||
.timeline-list li::before {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
@@ -753,6 +758,32 @@ th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timeline-list--compact {
|
||||
margin-top: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
|
||||
.downtime-board {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.downtime-focus {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-panel-alt);
|
||||
}
|
||||
|
||||
.downtime-focus > strong {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.oee-mini-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user