Commit Graph
6 Commits
Author SHA1 Message Date
Joel Brock d88f1229ae Report: stair-step actual line, drop sparklines, move chart into Stage 0
- Actual member line now uses the same carry-forward step pattern as
  the goal line — between measurements the chart holds the prior value
  instead of interpolating diagonally, and the final value extends flat
  to the right edge. Eliminates the apparent dips that arose when
  diagonal interpolation crossed missing periods or low intermediate
  values.
- Per-field Sparkline (and its isNumericField / formatScalarText
  helpers) removed entirely. Expanding 'earlier entries' now just shows
  the chronological list. Curated multi-metric charts (like the
  Membership chart) are the path forward for trend visualization.
- Membership chart relocated from the top-of-report band into the
  Stage 0 ('Check-in (organizing)') section, rendered inline after
  whichever of Members__current_ / Member_Goal_for_current_Stage
  appears last in the section's fields-with-history list. Naturally
  scopes the chart to wherever those questions live (no double-render
  if config later moves them). Chart props refactored to take field
  + history pairs directly instead of walking the full sections array.
2026-05-19 16:45:53 -07:00
Joel Brock ae4e47025c Report: dedicated Membership chart (goal vs actual over time)
New MembershipChart card sits between the DateTimeline and the section
accordions, rendering whenever Members__current_ or
Member_Goal_for_current_Stage has any historical data.

- Actual member count: smooth leaf-700 polyline with a faint leaf-500
  area fill underneath, dots at every measurement, an emphasized dot
  on the most recent point with the value labeled inline.
- Goal: dashed clay-600 step line — each goal value is treated as a
  target that holds until the next update, then extends flat to the
  right edge of the chart. Dots at each update; the most-recent goal
  value labeled at the right.
- Y-axis: niceYTicks picks 3–5 round-number ticks (snapped to
  1/2/2.5/5/10 × 10^N) spanning [min(0, dataMin), dataMax]; faint
  gridlines + tabular-num labels on the left. Anchoring at 0 keeps
  growth-from-small-base readable.
- X-axis: reuses generateAxisTicks for adaptive month/year stepping,
  matching the timeline above. Today gets a dashed clay vertical
  guide when in range.
- Header: title + subtitle + an inline 'NNN of MMM target · X to go'
  callout in tabular-nums, color-coded (clay-700 if behind goal,
  leaf-700 if above).
- Legend at the bottom with line+dot chips for both series.
2026-05-19 16:37:01 -07:00
Joel Brock 00e10a992f Timeline: hover/focus tooltips, stage 0 lane, adaptive date axis
Three improvements to the report's DateTimeline:

1. Tooltips on every dot. Each dot is now a focusable span (tabIndex,
   role=img, full aria-label). A small ink-tinted card appears above
   the dot on mouse hover or keyboard focus, showing field label,
   formatted date, stage rank, and an 'Opened' marker for Date_Opened.
   Anchor flips to left/center/right based on the dot's position so
   tooltips don't overflow the row at the edges.

2. Plot every date field, including stage 0. The previous version
   skipped Stage 0 dates (Internal_Startup_Assessment_Date,
   Date_Closed_Folded). Now there are six swim lanes (0-5) instead
   of five. Stage 0 gets bg-leaf-200 so the gradient extends one
   step lighter.

3. Adaptive month/year x-axis under the lanes. generateAxisTicks
   picks a 'nice' interval based on the visible span: 1mo / 2mo /
   3mo / 6mo / 1yr / 2yr. January-bordered ticks include the year
   so the reader has anchors. Today gets its own labeled clay tick
   when it falls in range.
2026-05-19 16:32:50 -07:00
Joel Brock 814b560363 Report: sparkline charts on numeric history + stage-grouped date timeline
Two visual additions to the read-only activity report.

Sparkline: when the user expands earlier-entries on a numeric field
(number/currency/percent) with two or more numeric points, the
expansion now leads with a 240x56 inline SVG trend chart — chronological
polyline, faint area fill, small dots at every measurement, a slightly
larger emphasized dot on the most recent point. Min and max captions
sit beneath in tabular-nums, formatted in the field's native style
(currency uses Intl, percent appends %, etc.). Non-numeric fields are
unchanged.

DateTimeline: a new card between the context header and the section
accordions. Walks every date-type field in stage sections 1-5 (Stage 0
omitted as it isn't a stage in the journey sense), pulls each field's
most-recent entered date, and lays the events out in five horizontal
swim lanes — one per stage rank, labeled at the left. Time axis
spans from the earliest event to max(latest event, Date_Opened).
Stage 5's Date_Opened is rendered as a larger clay-700 dot with a
heavier ring so it reads as the journey's anchor at the right end.
A faint clay-300 dashed vertical line marks 'today' if it falls
within the range. Color scale across stages is leaf-300 / leaf-500
/ leaf-600 / leaf-700 / clay-700 — a sprout-to-fruit gradient that
matches the existing palette. Empty stage rows still draw their lane
line at half opacity so the structure stays readable. SR-only event
list provides screen-reader access to all plotted dates with their
labels.

Stub payload enriched with four cross-stage date entries so the
timeline has content in dev preview.
2026-05-19 16:16:57 -07:00
Joel Brock 9209d6dc02 Prefill file fields with their file_name joined from CiviCRM
CiviCRM APIv4 file custom fields return a bare file id by default; an
extra '.file_name' join is required to get the human-readable filename.
Both the form prefill walk (lib/prefill.ts) and the report walk
(app/api/report/route.ts) now request '<civiField>.file_name' for every
file-type field alongside the primary value, and wrap the prefill into
a { id, file_name } object so downstream UI has both. Falls back to
file_name undefined when the join returns null (eg orphaned id).

The form's FilePriorIndicator already accepts the object shape, so it
now shows the filename inline. The report's FormattedValue gets a
matching case: renders the file_name string if present, falls back to
'Attachment #<id>' when only the id came through.
2026-05-19 15:51:35 -07:00
Joel Brock ba88eb0165 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.
2026-05-13 12:33:26 -07:00