Report: drop Stage 0 lane, flip 5→1 axis, fix Actual label clipping

DateTimeline now skips section rank 0 (intake) and renders stage lanes
top-to-bottom 5→1, matching how the framework actually numbers stages.

MembershipChart's latest-Actual label was placed at the circle's
x + 6, which overflowed the SVG when the most-recent point landed near
the right padding. Anchor it to xOf(maxT) - 4 with textAnchor="end",
mirroring the Goal label so both endpoint labels stay inside the chart.
This commit is contained in:
Joel Brock
2026-06-11 13:39:42 -07:00
parent 2dc8ec4e6d
commit 8dfbbea0ba
2 changed files with 15 additions and 11 deletions
+10 -9
View File
@@ -5,13 +5,14 @@ import { formatShortDate } from "./FieldHistory";
/** /**
* Date-grouped timeline strip at the top of the report. Walks every * Date-grouped timeline strip at the top of the report. Walks every
* date-type field across all stage sections (05), pulls each field's * date-type field across stage sections 15, pulls each field's
* most-recent entered date, and plots events into six horizontal swim * most-recent entered date, and plots events into five horizontal swim
* lanes — one per stage. Stage 5's `Date_Opened` gets visual emphasis * lanes — one per stage, Stage 5 on top down to Stage 1 at the bottom.
* as the journey's anchor at the right end of the time axis. A faint * Stage 5's `Date_Opened` gets visual emphasis as the journey's anchor
* "Today" tick anchors the reader if today falls within the range, and * at the right end of the time axis. A faint "Today" tick anchors the
* an adaptive month/year axis runs beneath the lanes. Each dot exposes * reader if today falls within the range, and an adaptive month/year
* a tooltip on hover or keyboard focus. * axis runs beneath the lanes. Each dot exposes a tooltip on hover or
* keyboard focus.
*/ */
export function DateTimeline({ export function DateTimeline({
sections, sections,
@@ -28,7 +29,7 @@ export function DateTimeline({
}; };
const events: Event[] = []; const events: Event[] = [];
for (const section of sections) { for (const section of sections) {
if (section.rank < 0 || section.rank > 5) continue; if (section.rank < 1 || section.rank > 5) continue;
for (const f of section.fields) { for (const f of section.fields) {
if (f.type !== "date") continue; if (f.type !== "date") continue;
const history = fieldHistory[f.name]; const history = fieldHistory[f.name];
@@ -80,7 +81,7 @@ export function DateTimeline({
</p> </p>
</div> </div>
<ol className="mt-4 space-y-2"> <ol className="mt-4 space-y-2">
{[0, 1, 2, 3, 4, 5].map((rank) => { {[5, 4, 3, 2, 1].map((rank) => {
const rowEvents = events.filter((e) => e.rank === rank); const rowEvents = events.filter((e) => e.rank === rank);
const isEmpty = rowEvents.length === 0; const isEmpty = rowEvents.length === 0;
return ( return (
+5 -2
View File
@@ -281,11 +281,14 @@ export function MembershipChart({
/> />
))} ))}
{/* Most-recent point value labels */} {/* Most-recent point value labels. Both anchor to the chart's right
edge so the carry-forward endpoint label stays inside the viewBox
instead of overflowing past the right padding. */}
{actualCoords.length > 0 && ( {actualCoords.length > 0 && (
<text <text
x={actualCoords[actualCoords.length - 1].x + 6} x={xOf(maxT) - 4}
y={actualCoords[actualCoords.length - 1].y - 6} y={actualCoords[actualCoords.length - 1].y - 6}
textAnchor="end"
className="fill-leaf-800 text-[10px] font-medium tabular-nums" className="fill-leaf-800 text-[10px] font-medium tabular-nums"
> >
{numberFmt.format(actualCoords[actualCoords.length - 1].v)} {numberFmt.format(actualCoords[actualCoords.length - 1].v)}