shoc-frontend-new/src/app/v/[token]/dispatch/_components/signoff-section.tsx
Alexandre Brandizzi 45909e7aa6
Complete vendor operations roadmap frontend (#52)
* feat(vendors): complete operations roadmap frontend

* test(vendors): cover work order assignment flow

* fix(vendors): address review feedback

* fix(vendors): prevent stale preference saves

* fix(vendors): require truthful appointment bounds
2026-07-24 18:10:45 -03:00

135 lines
3.9 KiB
TypeScript

import { useState } from "react";
import { Text } from "@/components/ui/text";
import { SignaturePad } from "@/app/v/_components/signature-pad";
import { vendorPortalApi } from "@/domain/vendor-portal/api/vendor-portal-api";
import { formatVendorPortalDateTime } from "@/domain/vendor-portal/lib/status-helpers";
import type { VendorPortalSignoff } from "@/domain/vendor-portal/types/vendor-portal";
type SignoffSectionProps = {
token: string;
dispatchId: number;
existing: VendorPortalSignoff[];
signoffType: string;
nameLabel: string;
onAdded: () => void;
};
export function SignoffSection({
token,
dispatchId,
existing,
signoffType,
nameLabel,
onAdded,
}: SignoffSectionProps) {
const [name, setName] = useState("");
const [method, setMethod] = useState<"drawn" | "typed">("drawn");
const [typedSig, setTypedSig] = useState("");
const [drawnSig, setDrawnSig] = useState("");
const [submitting, setSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const existingSignoff = existing.find((signoff) => signoff.signoffType === signoffType);
if (existingSignoff) {
return (
<div>
<div className="vp-row">
<strong>Signed by</strong>
<span>{existingSignoff.name}</span>
</div>
<div className="vp-row">
<strong>Method</strong>
<span>{existingSignoff.signatureMethod}</span>
</div>
<div className="vp-row">
<strong>Signed at</strong>
<span>{formatVendorPortalDateTime(existingSignoff.signedAt)}</span>
</div>
</div>
);
}
const submit = async (event: React.FormEvent) => {
event.preventDefault();
const signature = method === "drawn" ? drawnSig : typedSig;
if (!name.trim() || !signature) {
setErrorMessage("Name and signature are required.");
return;
}
setErrorMessage("");
setSubmitting(true);
try {
await vendorPortalApi.addSignoff(token, dispatchId, {
name: name.trim(),
signature,
signatureMethod: method,
signoffType,
});
onAdded();
} catch {
setErrorMessage("Unable to submit the signoff. Please try again.");
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={(event) => void submit(event)}>
<div className="vp-row">
<strong>{nameLabel}</strong>
<input
className="vp-input"
value={name}
onChange={(event) => {
setName(event.target.value);
setErrorMessage("");
}}
/>
</div>
<div className="vp-row">
<strong>Method</strong>
<div>
<label style={{ marginRight: 12 }}>
<input type="radio" checked={method === "drawn"} onChange={() => setMethod("drawn")} />{" "}
Drawn
</label>
<label>
<input type="radio" checked={method === "typed"} onChange={() => setMethod("typed")} />{" "}
Typed
</label>
</div>
</div>
{method === "drawn" ? (
<div style={{ marginTop: 8 }}>
<SignaturePad
onChange={(signature) => {
setDrawnSig(signature);
setErrorMessage("");
}}
/>
</div>
) : (
<div className="vp-row">
<strong>Signature</strong>
<input
className="vp-input"
value={typedSig}
onChange={(event) => {
setTypedSig(event.target.value);
setErrorMessage("");
}}
/>
</div>
)}
<Text variant="error" when={Boolean(errorMessage)}>
{errorMessage}
</Text>
<div className="vp-actions">
<button type="submit" className="vp-btn vp-btn-success" disabled={submitting}>
{submitting ? "Signing…" : "Submit Signoff"}
</button>
</div>
</form>
);
}