"use client"; import { useId, useMemo, useState } from "react"; import type { StageSectionConfig, SelectOption } from "@/types/form"; import type { UseFormRegister, FieldValues, FieldErrors } from "react-hook-form"; import { FieldRenderer } from "./fields/FieldRenderer"; import { MatrixGroup } from "./MatrixGroup"; import { StageIcon } from "./StageIcon"; import { evaluate } from "@/lib/conditional"; interface StageSectionProps { section: StageSectionConfig; register: UseFormRegister; errors: FieldErrors; /** Live form values, used to evaluate per-field visibility rules. */ formValues: Record; /** Whether this is the section that matches the current org stage. */ isCurrent: boolean; /** Whether the section starts open. */ defaultOpen: boolean; /** Option groups fetched from CiviCRM, keyed by option_group_id. */ options: Record; /** Optional last-checked-in timestamp for this stage (ISO date). */ lastTouched?: string; } /** * One accordion card per stage section. Header carries: * - A hand-drawn stage icon (left) * - The stage label (display serif) * - A "Current stage" pill in terracotta if applicable * - A field-count summary on the right * - A chevron that rotates on open * * The card border thickens and tints leaf-green when current; otherwise it * sits quietly on the cream paper. */ export function StageSection({ section, register, errors, formValues, isCurrent, defaultOpen, options, lastTouched, }: StageSectionProps) { const [open, setOpen] = useState(defaultOpen); const headingId = useId(); const panelId = useId(); const matrixFieldNames = useMemo(() => { const names = new Set(); for (const m of section.matrixGroups ?? []) { for (const row of m.rows) for (const f of row.fields) names.add(f); } return names; }, [section.matrixGroups]); const visibleFields = section.fields.filter( (f) => evaluate(f.visibleWhen, formValues) && !matrixFieldNames.has(f.name), ); const fieldCount = visibleFields.length + (section.matrixGroups ?? []).reduce( (n, g) => n + g.rows.reduce((m, r) => m + r.fields.length, 0), 0, ); return (

); } /** * Stage rank mark: the number sits in a small circle, with the hand-drawn * icon offset behind it. When current, the ring is leaf-green and the * number is white-on-green; otherwise it's quiet. */ function StageRankMark({ rank, isCurrent }: { rank: number; isCurrent: boolean }) { return ( {rank} ); } function Chevron({ open }: { open: boolean }) { return ( ); } function formatRelative(iso: string): string { const then = new Date(iso).getTime(); if (Number.isNaN(then)) return iso; const days = Math.round((Date.now() - then) / (1000 * 60 * 60 * 24)); if (days <= 0) return "today"; if (days === 1) return "yesterday"; if (days < 30) return `${days} days ago`; if (days < 365) return `${Math.round(days / 30)} months ago`; return `${Math.round(days / 365)} years ago`; }