Add /report — read-only activity history view

Mirrors the form's IA and Field Almanac aesthetic; same auth (cid/cs
checksum) so the org owner who can fill the form can also view its
history.

New routes:
- GET /api/report — verifies checksum, resolves the org via the
  Primary Contact relationship, fires Contact.get + Activity.get +
  OptionValue.get in parallel. For every form field with a civiField,
  walks all the org's Check-in (organizing) activities and collects
  every non-empty value into a sorted-DESC history list. Returns
  ReportPayload (orgName, currentStage, activities, fieldHistory,
  options). Has stub-mode payload for env-less local dev.
- /report — page entry; same layout shell (SiteHeader + SiteFooter,
  3xl page width). Eyebrow "Activity report - Co-op organizing".

ReportView component:
- ReportContextHeader: large org name, progress dots + uppercase
  "Current stage" eyebrow + the Civi option *label* on its own line
  at display-font xl/2xl leaf-800 (matches the form's header). Below
  it a 3-up stat band: total check-ins, fields tracked, date span.
- One accordion card per stage section, in stage-rank order. Only
  sections that have at least one field-with-entries render — past,
  current, or "future-with-data" all welcome; truly empty stages stay
  hidden so the page is calm.
- Same journey rail (md+) and mobile stem (md-) with past =
  check-filled-leaf, current = filled-leaf-with-ring, future = dashed
  hollow ring; solid leaf line vs dashed muted between markers.
- Within each card: divide-y rows. Field label and help on the left,
  most-recent value on the right in display-font lg leaf-800, dated
  beneath with an "{N} earlier entries" disclosure that expands a
  small vertical timeline (date on left, value on right).
- FormattedValue handles currency (Intl), percent, number (tabular
  nums), date (long, timezone-safe for YYYY-MM-DD), boolean (Yes/No),
  select/readonly (resolved via option group), multiselect (handles
  array or delimited string), file (filename), text-like (as-is).
- Loading / empty / error states match the form's treatments.

types/form.ts: new FieldHistoryEntry, ActivitySummary, ReportPayload.
The fieldHistory map keys by FieldConfig.name and only includes fields
that have at least one non-empty entry.
This commit is contained in:
Joel Brock
2026-05-13 12:33:26 -07:00
parent 04e69ca04c
commit ba88eb0165
4 changed files with 978 additions and 0 deletions

View File

@@ -197,3 +197,49 @@ export interface SubmitPayload {
cs: string;
values: Record<string, unknown>;
}
/**
* One historical entry for a field. The report walks all Check-in
* (organizing) activities for an org and collects every non-empty value
* each field has ever held, alongside the activity it came from.
*/
export interface FieldHistoryEntry {
activityId: number;
/** ISO timestamp of activity_date_time. */
date: string;
value: unknown;
}
/**
* A trimmed view of one activity, used to head the report and to label
* staff stage-change activities distinctly from form-driven submissions.
*/
export interface ActivitySummary {
id: number;
date: string;
/** Stage value carried on this activity (non-empty → staff transition). */
stage?: string | null;
subject?: string | null;
}
/**
* The shape returned by /api/report. Activities ordered DESC; each
* fieldHistory entry list is also DESC.
*/
export interface ReportPayload {
orgName: string;
/** Current Framework Stage as text value (e.g. "Organizing"). */
currentStage: string;
/**
* Activity summary list, ordered DESC by activity_date_time.
* Includes both staff stage-change and form-driven submission activities.
*/
activities: ActivitySummary[];
/**
* Per-field history of non-empty values, keyed by FieldConfig.name. Each
* list is sorted DESC by activity_date_time. Fields that have never been
* filled in are absent from this map.
*/
fieldHistory: Record<string, FieldHistoryEntry[]>;
options?: Record<number, SelectOption[]>;
}