feat(po): admin-managed Terms & Conditions catalogue + PO dropdowns (#11) #106

Merged
shad0w merged 2 commits from feat/terms-conditions-admin into master 2026-06-23 22:14:06 +00:00
2 changed files with 24 additions and 20 deletions
Showing only changes of commit f4c8ec7585 - Show all commits

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)
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`)

View file

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