From a8f99653914db05d5ed5d408db8a9697d8457f30 Mon Sep 17 00:00:00 2001
From: Pim Hutting
Date: Tue, 20 Jan 2026 12:31:34 +0100
Subject: [PATCH] chore: added recursive goals to monitor page
---
src/pages/MonitoringPage/MonitoringPage.tsx | 9 ++--
.../MonitoringPageComponents.tsx | 10 ++--
src/utils/programStore.ts | 48 +++++++++++++++++++
3 files changed, 61 insertions(+), 6 deletions(-)
diff --git a/src/pages/MonitoringPage/MonitoringPage.tsx b/src/pages/MonitoringPage/MonitoringPage.tsx
index 9252448..cd7f95e 100644
--- a/src/pages/MonitoringPage/MonitoringPage.tsx
+++ b/src/pages/MonitoringPage/MonitoringPage.tsx
@@ -306,14 +306,17 @@ function PhaseDashboard({
setActiveIds: React.Dispatch>>,
goalIndex: number
}) {
- const getGoals = useProgramStore((s) => s.getGoalsInPhase);
+ const getGoalsWithDepth = useProgramStore((s) => s.getGoalsWithDepth);
const getTriggers = useProgramStore((s) => s.getTriggersInPhase);
const getNorms = useProgramStore((s) => s.getNormsInPhase);
// Prepare data view models
- const goals = (getGoals(phaseId) as GoalNode[]).map(g => ({
+ const goals = getGoalsWithDepth(phaseId).map((g) => ({
...g,
- achieved: activeIds[g.id] ?? false,
+ id: g.id as string,
+ name: g.name as string,
+ achieved: activeIds[g.id as string] ?? false,
+ level: g.level, // Pass this new property to the UI
}));
const triggers = (getTriggers(phaseId) as TriggerNode[]).map(t => ({
diff --git a/src/pages/MonitoringPage/MonitoringPageComponents.tsx b/src/pages/MonitoringPage/MonitoringPageComponents.tsx
index 6efeab5..d1d2854 100644
--- a/src/pages/MonitoringPage/MonitoringPageComponents.tsx
+++ b/src/pages/MonitoringPage/MonitoringPageComponents.tsx
@@ -91,13 +91,14 @@ export const DirectSpeechInput: React.FC = () => {
};
// --- interface for goals/triggers/norms/conditional norms ---
-type StatusItem = {
+export type StatusItem = {
id?: string | number;
achieved?: boolean;
description?: string;
label?: string;
norm?: string;
name?: string;
+ level?: number;
};
interface StatusListProps {
@@ -129,7 +130,7 @@ export const StatusList: React.FC = ({
const isCurrentGoal = type === 'goal' && idx === currentGoalIndex;
const canOverride = (showIndicator && !isActive) || (type === 'cond_norm' && isActive);
-
+ const indentation = (item.level || 0) * 20;
const handleOverrideClick = () => {
if (!canOverride) return;
@@ -147,7 +148,10 @@ export const StatusList: React.FC = ({
};
return (
-
+
{showIndicator && (
[] };
+export type GoalWithDepth = Record & { level: number };
+
/**
* the type definition of the programStore
*/
@@ -18,6 +20,7 @@ export type ProgramState = {
getPhaseNames: () => string[];
getNormsInPhase: (currentPhaseId: string) => Record[];
getGoalsInPhase: (currentPhaseId: string) => Record[];
+ getGoalsWithDepth: (currentPhaseId: string) => GoalWithDepth[];
getTriggersInPhase: (currentPhaseId: string) => Record[];
// if more specific utility functions are needed they can be added here:
}
@@ -70,6 +73,51 @@ const useProgramStore = create((set, get) => ({
}
throw new Error(`phase with id:"${currentPhaseId}" not found`)
},
+
+ getGoalsWithDepth: (currentPhaseId: string) => {
+ const program = get().currentProgram;
+ const phase = program.phases.find(val => val["id"] === currentPhaseId);
+
+ if (!phase) {
+ throw new Error(`phase with id:"${currentPhaseId}" not found`);
+ }
+
+ const rootGoals = phase["goals"] as Record[];
+ const flatList: GoalWithDepth[] = [];
+
+ // Helper: Define this ONCE, outside the loop
+ const isGoal = (item: Record) => {
+ return item["plan"] !== undefined && item["plan"] !== null;
+ };
+
+ // Recursive helper function
+ const traverse = (goals: Record[], depth: number) => {
+ goals.forEach((goal) => {
+ // 1. Add the current goal to the list
+ flatList.push({ ...goal, level: depth });
+
+ // 2. Check for children
+ const plan = goal["plan"] as Record | undefined;
+
+ if (plan && Array.isArray(plan["steps"])) {
+ const steps = plan["steps"] as Record[];
+
+ // 3. FILTER: Only recurse on steps that are actually goals
+ // If we just passed 'steps', we might accidentally add Actions/Speeches to the goal list
+ const childGoals = steps.filter(isGoal);
+
+ if (childGoals.length > 0) {
+ traverse(childGoals, depth + 1);
+ }
+ }
+ });
+ };
+
+ // Start traversal
+ traverse(rootGoals, 0);
+
+ return flatList;
+ },
/**
* gets the triggers for the provided phase
*/