import { useEffect, useRef, useState, type FormEvent } from "react"; import { Link, useParams } from "react-router"; import { ApiError } from "../../api/client"; import { enterNcr, getItp, getUnitFolder, itpPdfUrl, itpPrintUrl, patchBlock, patchItp, patchLine, rejectLine, reopenItp, sectionNa, signBlock, signLine, signOff, unsignBlock, unsignLine, type HeaderField, type Itp, type ItpBlock, type ItpItem, type ItpLine, type LinePatch, type QcWho, } from "../../api/qc"; import type { Guarded } from "../../api/types"; import { useAuth } from "../../auth/useAuth"; import { Sheet } from "../../components/Sheet"; import { useGuard, type RunOptions } from "../../components/useGuard"; import { useChanged } from "../../lib/changes"; import { errorText, fmtTime } from "../../lib/format"; import { printPdf } from "../../lib/printPdf"; import { useLoad } from "../../lib/useLoad"; import { fmtQcDate } from "./folders"; import { blockScope, initials, isApproved, isNa, isOpenHold, isRejected, ITP_CUST_CODES, ITP_PAGE_ORDER, ITP_PAGES, lastReject, lineOf, progressText, sectionsOf, todayIso, } from "./itpView"; /** What a write came to. A 409 reloads the ITP and shows the server's text. */ type Outcome = "ok" | "cancel" | "conflict" | { error: string }; type LineSheetState = { kind: "line" | "reject" | "ncr"; key: string } | null; /** An input saved on blur when its value changed. */ function BlurInput({ label, value, disabled, maxLength, required = false, onSave, }: { label: string; value: string; disabled: boolean; maxLength: number; /** An empty value goes back to the saved one. */ required?: boolean; onSave: (next: string, prev: string) => Promise; }) { const [v, setV] = useState(value); const focused = useRef(false); useEffect(() => { if (!focused.current) setV(value); }, [value]); const blur = async () => { focused.current = false; const next = v.trim(); if (next === value || (required && !next)) { setV(value); return; } const ok = await onSave(next, value); if (!ok) setV(value); }; return ( ); } /** The line details sheet (the old openItpLine): N/A, the customer code and witness, the note. */ function LineSheet({ item, line, onSave, onCancel }: { item: ItpItem; line: ItpLine; onSave: (p: LinePatch) => Promise; onCancel: () => void }) { const approved = isApproved(line); const rej = isRejected(line); const [na, setNa] = useState(isNa(line)); const [cc, setCc] = useState(line.cust_code ?? ""); const [cby, setCby] = useState(line.cust_by ?? ""); const [cat, setCat] = useState(line.cust_at ? line.cust_at.slice(0, 10) : todayIso()); const [note, setNote] = useState(line.note ?? ""); const [busy, setBusy] = useState(false); const [err, setErr] = useState(null); const submit = async (e: FormEvent) => { e.preventDefault(); const p: LinePatch = {}; if (!approved && !rej && na !== isNa(line)) p.na = na; if (cc !== (line.cust_code ?? "")) p.cust_code = cc; const by = cby.trim(); if (by !== (line.cust_by ?? "")) p.cust_by = by; if (by && (p.cust_by !== undefined || cat !== (line.cust_at ?? "").slice(0, 10))) p.cust_at = cat || null; const n = note.trim(); if (n !== (line.note ?? "")) p.note = n; if (!Object.keys(p).length) { onCancel(); return; } setBusy(true); setErr(null); const o = await onSave(p); if (typeof o === "object") { setErr(o.error); setBusy(false); } else if (o === "cancel") setBusy(false); }; return (
{item.crit}
PESCO code {item.code || "-"} · customer code on the template {item.cust || "-"}