Stage 0: org-contact custom fields (Food_Co_op_Organizing)

Adds four fields from the Organization Contact's Food_Co_op_Organizing
custom group to the Stage 0 (always-visible) section:

  Date Incorporated                (date, editable)
  Name on Incorporation Certificate (text, editable)
  Certificate of Incorporation     (readonly; see note)
  Equity share                     (currency, editable)

These live on the Organization Contact record, not on the Check-in
activity, so they read/write through a different code path:

  - FieldConfig gains civiContactField, mutually exclusive with civiField
  - /api/data extends the org Contact.get select to include them and
    merges values into the prefill payload keyed by form-side name
  - /api/submit splits incoming values: contact-bound fields go through
    Contact.update (run first), activity-bound fields stay in the
    Activity.create call (run second)
  - FieldRenderer readonly branch now detects file-shaped values
    ({id, file_name}) and displays the filename rather than [object Object]

Certificate_of_Incorporation is wired readonly only: the form's
file-upload pipeline is not actually wired end-to-end (FileList drops
at JSON.stringify in onSubmit; no /api/upload endpoint exists). A
follow-up will close that gap.

Also adds scripts/inspect-org-custom-fields.mjs, a one-off introspection
script for dumping CustomField metadata when wiring a new group.
This commit is contained in:
Joel Brock
2026-06-04 17:37:36 -07:00
parent b120075ca2
commit 8ecf64c79b
6 changed files with 205 additions and 12 deletions
+19 -5
View File
@@ -66,11 +66,25 @@ export function FieldRenderer({
// ── Readonly display field ──────────────────────────────────────────────
if (field.type === "readonly") {
const opt = effectiveOptions.find((o) => o.value === readonlyValue);
const display =
readonlyValue == null || readonlyValue === ""
? "—"
: opt?.label ?? String(readonlyValue);
// File-shaped readonly value: {id, file_name} from a CiviCRM file field
// (e.g. Certificate of Incorporation). Render the filename rather than
// "[object Object]".
let display: string;
if (
readonlyValue != null &&
typeof readonlyValue === "object" &&
!Array.isArray(readonlyValue) &&
"file_name" in (readonlyValue as Record<string, unknown>)
) {
const fn = (readonlyValue as { file_name?: unknown }).file_name;
display = typeof fn === "string" && fn ? fn : "Attachment on file";
} else {
const opt = effectiveOptions.find((o) => o.value === readonlyValue);
display =
readonlyValue == null || readonlyValue === ""
? "—"
: opt?.label ?? String(readonlyValue);
}
return (
<div className="space-y-1">
<Label id={id} field={field} />