From d93ae683c768ad2bbb6b5b620c3597c2e709ff75 Mon Sep 17 00:00:00 2001 From: "arthur.bassi" Date: Tue, 18 Aug 2026 10:03:14 -0300 Subject: [PATCH] fix(work-orders): restore additionalContacts contract and name+phone extras (SH-218) Document the backend #66 producer, drop incomplete extras from persist, and show the same required-field errors on POC 2+. --- docs/work-orders/pending-be-contract.md | 30 ++++ .../_components/poc/poc-contact-fields.tsx | 167 +++++++++++------- .../mappers/additional-contacts-mapper.ts | 13 +- .../additional-contacts-mapper.test.ts | 20 +++ .../work-order-board-detail-mapper.test.ts | 8 + 5 files changed, 172 insertions(+), 66 deletions(-) create mode 100644 docs/work-orders/pending-be-contract.md diff --git a/docs/work-orders/pending-be-contract.md b/docs/work-orders/pending-be-contract.md new file mode 100644 index 00000000..08aff844 --- /dev/null +++ b/docs/work-orders/pending-be-contract.md @@ -0,0 +1,30 @@ +# Work-order `additionalContacts` contract (SH-218) + +Authoritative frontend contract for additional POC 2+ contacts. Implemented on +backend PR [shoc-backend#66](https://github.com/Sea-Haven-Industries/shoc-backend/pull/66). + +## Shape + +```ts +{ name: string; phone: string; notes?: string } +``` + +- Primary POC remains `pocName` / `pocPhone` / `pocNotes`. +- `additionalContacts` holds ordered POC 2+ only. +- Notes are optional. An empty list (or omitted field) is valid. +- Retained entries require **both** `name` and `phone`. Fully blank placeholder + rows are dropped and must not fail create. Name-only or phone-only entries are + rejected on POST create and PATCH. + +## Endpoints + +| Operation | Where | +| ------------------ | ------------------------------------------------------------- | +| POST create | `additionalContacts` array on the board create body | +| GET board / search | `additionalContacts` on each board row | +| GET detail | `info.additionalContacts` (same ordered list as board) | +| PATCH | `field: "additionalContacts"`, `value` is a JSON array string | + +Until #66 is merged to the deployed API, a missing field is still mapped as `[]` +on the frontend so the UI does not throw. After merge, create → reload → +board/slide-over must round-trip the ordered list through GET detail `info`. diff --git a/src/app/(protected)/workorders/_components/poc/poc-contact-fields.tsx b/src/app/(protected)/workorders/_components/poc/poc-contact-fields.tsx index da27b89e..0449fadd 100644 --- a/src/app/(protected)/workorders/_components/poc/poc-contact-fields.tsx +++ b/src/app/(protected)/workorders/_components/poc/poc-contact-fields.tsx @@ -6,6 +6,7 @@ import { DLG_INPUT_STYLE, } from "@/app/(protected)/workorders/_components/wizard/wizard-input-styles"; import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label"; +import { isIncompleteAdditionalContact } from "@/domain/work-orders/mappers/additional-contacts-mapper"; import { EMPTY_ADDITIONAL_CONTACT, type WorkOrderAdditionalContact, @@ -24,6 +25,14 @@ type PocContactFieldsProps = { showPrimaryErrors?: boolean; }; +function extraNameMissing(contact: WorkOrderAdditionalContact): boolean { + return isIncompleteAdditionalContact(contact) && !contact.name.trim(); +} + +function extraPhoneMissing(contact: WorkOrderAdditionalContact): boolean { + return isIncompleteAdditionalContact(contact) && !contact.phone.trim(); +} + function updateContactAt( contacts: WorkOrderAdditionalContact[], index: number, @@ -34,6 +43,87 @@ function updateContactAt( ); } +function AdditionalPocCard({ + contact, + index, + onChange, + onRemove, +}: { + contact: WorkOrderAdditionalContact; + index: number; + onChange: (patch: Partial) => void; + onRemove: () => void; +}) { + const nameMissing = extraNameMissing(contact); + const phoneMissing = extraPhoneMissing(contact); + + return ( +
+
+ + Point of contact {index + 2} + + +
+
+
+ onChange({ name: event.target.value })} + placeholder="POC name" + className={DLG_INPUT_CLS} + style={{ + ...DLG_INPUT_STYLE, + borderColor: nameMissing ? "var(--destructive)" : undefined, + }} + /> + + Contact name is required + +
+
+ onChange({ phone: event.target.value })} + placeholder="POC phone" + className={DLG_INPUT_CLS} + style={{ + ...DLG_INPUT_STYLE, + borderColor: phoneMissing ? "var(--destructive)" : undefined, + }} + /> + + Contact phone is required + +
+
+