feat(po): make TermsField a combobox (type-or-pick)
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 30s

Per review: the five named PO T&C slots now allow a one-off custom clause as
well as picking a catalogued one. TermsField becomes a native <input list> +
<datalist> combobox (still plain FormData, no form/page changes). Any current/
custom value is preserved as the input value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hardik 2026-06-24 03:42:38 +05:30
parent a99b2ed5df
commit f4c8ec7585
2 changed files with 24 additions and 20 deletions

View file

@ -106,7 +106,7 @@ The three PO forms (`new-po-form`, `edit-po-form`, `manager-edit-po-form`) rende
### Terms & Conditions catalogue (issue #11) ### Terms & Conditions catalogue (issue #11)
Same admin-list-feeds-PO-dropdown pattern as Delivery Locations. `TermsCondition` (`category: TermsCategory` enum + `text` + `isActive`) is an admin-managed clause library, managed at `/admin/terms` (gated by **`manage_terms`** — Manager + SuperUser + Admin; CRUD mirrors `/admin/delivery-locations`). The migration **seeds** the prior `TC_DEFAULTS` wording as the starting clauses. The five **named** PO T&C slots (Delivery / Dispatch / Inspection / Transit Insurance / Payment Terms — the `tc*` columns, mapped via `lib/terms.ts` `TC_FIELD_CATEGORY`) become a shared `<TermsField>` native `<select>` populated from the active clauses of that category (`lib/terms-data.ts` `getActiveTermsByCategory`). **"Others" stays free text**, and the fixed boilerplate lines (`TC_FIXED_LINE` / `TC_FIXED_LINE_2`) are not catalogued. The `tc*` columns stay **free-text snapshots** (export/import unchanged); a current value not among the active clauses is preserved as a "(current)" option. No "work order" type — POs only (per the issue's steer). Same admin-list-feeds-PO-dropdown pattern as Delivery Locations. `TermsCondition` (`category: TermsCategory` enum + `text` + `isActive`) is an admin-managed clause library, managed at `/admin/terms` (gated by **`manage_terms`** — Manager + SuperUser + Admin; CRUD mirrors `/admin/delivery-locations`). The migration **seeds** the prior `TC_DEFAULTS` wording as the starting clauses. The five **named** PO T&C slots (Delivery / Dispatch / Inspection / Transit Insurance / Payment Terms — the `tc*` columns, mapped via `lib/terms.ts` `TC_FIELD_CATEGORY`) become a shared `<TermsField>` **combobox** (native `<input list>` + `<datalist>`) — type a one-off clause or pick a catalogued one — suggesting the active clauses of that category (`lib/terms-data.ts` `getActiveTermsByCategory`). **"Others" stays free text**, and the fixed boilerplate lines (`TC_FIXED_LINE` / `TC_FIXED_LINE_2`) are not catalogued. The `tc*` columns stay **free-text snapshots** (export/import unchanged); since the slot is a free-text combobox, any current/custom value is preserved as-is. No "work order" type — POs only (per the issue's steer).
### PO Numbering (`lib/po-number.ts`) ### PO Numbering (`lib/po-number.ts`)

View file

@ -1,14 +1,14 @@
"use client"; "use client";
/** /**
* A single PO Terms & Conditions slot (issue #11) a native * A single PO Terms & Conditions slot (issue #11) a combobox: type a one-off
* <select name={field}> sourced from the admin-managed clauses for its category. * clause OR pick a catalogued one. Implemented as a native <input list> +
* Plain HTML so it works with the forms' native FormData submission. * <datalist> so it stays free-text (custom wording per PO) while suggesting the
* admin-managed clauses for this category, and submits via plain FormData.
* *
* `options` are the active clause texts (also the stored value). `current` is the * `options` are the active clause texts (suggestions). `current` is the PO's
* PO's existing/default value for this slot; if it isn't one of the active * existing/default value for this slot; it's just the input's initial value, so
* options (a removed clause, or an older custom value) it is preserved as a * a value not in the catalogue is preserved as-is.
* leading "(current)" option so an edit never silently drops it.
*/ */
export function TermsField({ export function TermsField({
field, field,
@ -21,18 +21,22 @@ export function TermsField({
current?: string | null; current?: string | null;
className?: string; className?: string;
}) { }) {
const cur = (current ?? "").trim(); const listId = `terms-list-${field}`;
const currentMissing = cur.length > 0 && !options.includes(cur);
return ( return (
<select name={field} defaultValue={cur} className={className}> <>
<option value=""></option> <input
{currentMissing && <option value={cur}>{cur} (current)</option>} name={field}
{options.map((o) => ( list={listId}
<option key={o} value={o}> defaultValue={current ?? ""}
{o} autoComplete="off"
</option> placeholder="Type a clause or pick one…"
))} className={className}
</select> />
<datalist id={listId}>
{options.map((o) => (
<option key={o} value={o} />
))}
</datalist>
</>
); );
} }