Each date-field event is now plotted on the lane corresponding to the Framework Stage the co-op was in on the field's stored date, resolved from the activity stream's stage transitions. Date_Opened is pinned to Stage 5 as the journey anchor. Events that pre-date any known transition fall back to the field's section rank so they still surface somewhere. Extracts the STAGE_RANK map (previously inline in ReportView) into lib/stageRank.ts alongside the new buildStageRankAtDate resolver factory.
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import type { ActivitySummary } from "@/types/form";
|
|
|
|
/**
|
|
* CiviCRM activity stage values → Framework Stage rank (1..5). "Inquiry"
|
|
* predates Stage 1 and is conventionally rank 0 (pre-engagement); callers
|
|
* that render stage lanes 1..5 should treat 0 as "no rank in scope".
|
|
*/
|
|
export const STAGE_RANK: Record<string, number> = {
|
|
Inquiry: 0,
|
|
Organizing: 1,
|
|
Feasibility: 2,
|
|
"Business feasibility": 3,
|
|
"Store Implementation": 4,
|
|
"Stabilize newly opened co-op": 5,
|
|
};
|
|
|
|
/**
|
|
* Build a resolver that returns the co-op's Framework Stage rank at a given
|
|
* point in time, derived from the activity stream. Only activities that
|
|
* carry a non-empty `stage` value are stage transitions; their stage holds
|
|
* from that activity's date forward until the next transition.
|
|
*
|
|
* Returns `null` when the date precedes any known transition (i.e. we have
|
|
* no evidence of which stage the co-op was in at that point) — callers can
|
|
* fall back to a field-section default in that case.
|
|
*/
|
|
export function buildStageRankAtDate(
|
|
activities: ActivitySummary[]
|
|
): (isoDate: string) => number | null {
|
|
const transitions = activities
|
|
.filter(
|
|
(a): a is ActivitySummary & { stage: string } =>
|
|
typeof a.stage === "string" && a.stage.length > 0
|
|
)
|
|
.map((a) => ({ t: new Date(a.date).getTime(), stage: a.stage }))
|
|
.filter((tr) => Number.isFinite(tr.t))
|
|
.sort((a, b) => a.t - b.t);
|
|
|
|
return (isoDate: string) => {
|
|
const t = new Date(isoDate).getTime();
|
|
if (!Number.isFinite(t)) return null;
|
|
let last: number | null = null;
|
|
for (const tr of transitions) {
|
|
if (tr.t > t) break;
|
|
const r = STAGE_RANK[tr.stage];
|
|
if (typeof r === "number") last = r;
|
|
}
|
|
return last;
|
|
};
|
|
}
|