"use client"; import { useEffect, useId, useMemo, useState } from "react"; import type { StageSectionConfig, SelectOption } from "@/types/form"; import type { UseFormRegister, FieldValues, FieldErrors, Control, } 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; control: Control; 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 this stage is ahead of the org's current stage. Locked sections * still render so the user can see what's coming, but the fields inside * are wrapped in a disabled fieldset and never written on submit. */ locked: boolean; /** Whether the section starts open. */ defaultOpen: boolean; /** Option groups fetched from CiviCRM, keyed by option_group_id. */ options: Record; } /** * 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, control, errors, formValues, isCurrent, locked, defaultOpen, options, }: StageSectionProps) { const [open, setOpen] = useState(defaultOpen); const headingId = useId(); const panelId = useId(); // Listen for programmatic expand requests (fired by EngagementForm when a // submit fails on a required field inside a collapsed section). useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent<{ sectionId: string }>).detail; if (detail?.sectionId === section.id) setOpen(true); }; document.addEventListener("coop-checkin:expand-section", handler); return () => document.removeEventListener("coop-checkin:expand-section", handler); }, [section.id]); 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, ); const cardClass = isCurrent ? "border-2 border-leaf-600 shadow-[0_1px_0_0_rgba(0,0,0,0.04),0_8px_24px_-12px_rgba(60,80,40,0.18)] bg-white/95" : locked ? "border border-dashed border-rule bg-paper-2/30 shadow-none" : "border border-rule bg-white/95 shadow-sm"; const buttonClass = isCurrent ? "bg-leaf-50/60" : locked ? "hover:bg-paper-2/50" : "hover:bg-paper-2/60"; 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, locked, }: { rank: number; isCurrent: boolean; locked: boolean; }) { const iconClass = isCurrent ? "text-leaf-700 opacity-100" : locked ? "text-ink-mute opacity-30" : "text-leaf-600 opacity-50"; const badgeClass = isCurrent ? "bg-leaf-700 text-paper" : locked ? "bg-paper text-ink-mute border border-dashed border-rule" : "bg-paper text-ink-soft border border-rule"; return ( {rank} ); } function LockGlyph() { return ( ); } function LockedBanner() { return (

A look ahead.{" "} These fields will become editable when your co-op reaches this stage. They're visible now so you can preview the framework you'll be working through.

); } function Chevron({ open }: { open: boolean }) { return ( ); }