93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
import { useState } from "react";
|
|||
|
|
import { ApiError } from "../api/client";
|
||
|
|
import { packetsApi } from "../api/packets";
|
||
|
|
import type { PacketRead } from "../api/types";
|
||
|
|
import { errorText, fmtTime } from "../lib/format";
|
||
|
|
import type { Write } from "../warehouse/PullTable";
|
||
|
|
import { WeldRecord } from "./WeldRecord";
|
||
|
|
import type { Mutate } from "./tabs";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The stage checklist, grouped as the department's template, with the weld record beside it. Each tick is one PUT
|
||
|
|
* for its key; ticks of different keys never clash.
|
||
|
|
*/
|
||
|
|
export function ChecklistTab({
|
||
|
|
p,
|
||
|
|
write,
|
||
|
|
mutate,
|
||
|
|
reload,
|
||
|
|
onError,
|
||
|
|
}: {
|
||
|
|
p: PacketRead;
|
||
|
|
write: Write;
|
||
|
|
mutate: Mutate;
|
||
|
|
reload: () => Promise<void>;
|
||
|
|
onError: (text: string) => void;
|
||
|
|
}) {
|
||
|
|
const editable = !!p.stage && p.can.includes("edit_checklist");
|
||
|
|
const [pending, setPending] = useState<Record<string, boolean>>({});
|
||
|
|
const state = p.stage?.checklist ?? {};
|
||
|
|
const groups: Array<{ group: string; items: PacketRead["checklist_items"] }> = [];
|
||
|
|
for (const it of p.checklist_items) {
|
||
|
|
const g = groups.find((x) => x.group === it.group);
|
||
|
|
if (g) g.items.push(it);
|
||
|
|
else groups.push({ group: it.group, items: [it] });
|
||
|
|
}
|
||
|
|
|
||
|
|
const tick = async (key: string, done: boolean) => {
|
||
|
|
setPending((o) => ({ ...o, [key]: done }));
|
||
|
|
const r = await write((g) => packetsApi.setChecklist(p.id, key, done, g));
|
||
|
|
setPending((o) => {
|
||
|
|
const n = { ...o };
|
||
|
|
delete n[key];
|
||
|
|
return n;
|
||
|
|
});
|
||
|
|
if (!r.ok && r.err !== undefined) {
|
||
|
|
if (r.err instanceof ApiError && (r.err.status === 409 || r.err.status === 412)) await reload();
|
||
|
|
onError(errorText(r.err));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const weld = p.dept.weld !== false;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="cklay">
|
||
|
|
<div className="ckmain">
|
||
|
|
{groups.length ? null : <div className="empty">No checklist for this department.</div>}
|
||
|
|
{groups.map((g) => (
|
||
|
|
<section className="sec" key={g.group} aria-label={g.group}>
|
||
|
|
<div className="sec-h">
|
||
|
|
<h2>{g.group}</h2>
|
||
|
|
</div>
|
||
|
|
<div className="ck">
|
||
|
|
{g.items.map((i) => {
|
||
|
|
const c = state[i.key];
|
||
|
|
const done = pending[i.key] ?? !!c?.done;
|
||
|
|
const by = c?.done ? [c.stamp || c.by_name, fmtTime(c.at)].filter(Boolean).join(" · ") : "";
|
||
|
|
return (
|
||
|
|
<label key={i.key} className={`ck-item${done ? " done" : ""}`} aria-disabled={!editable}>
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={done}
|
||
|
|
disabled={!editable || pending[i.key] !== undefined}
|
||
|
|
onChange={(e) => void tick(i.key, e.target.checked)}
|
||
|
|
/>
|
||
|
|
<span className="t">{i.text}</span>
|
||
|
|
<span className="by">{by}</span>
|
||
|
|
</label>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
))}
|
||
|
|
{!editable && groups.length ? <div className="small muted">Checklist is completed at the stall.</div> : null}
|
||
|
|
</div>
|
||
|
|
{weld ? (
|
||
|
|
<div className="ckside">
|
||
|
|
<WeldRecord p={p} mutate={mutate} />
|
||
|
|
</div>
|
||
|
|
) : null}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|