diff --git a/src/components/ui/coaching-session-selector.tsx b/src/components/ui/coaching-session-selector.tsx
index 2ca2569..8a2140f 100644
--- a/src/components/ui/coaching-session-selector.tsx
+++ b/src/components/ui/coaching-session-selector.tsx
@@ -12,9 +12,11 @@ import {
} from "@/components/ui/select";
import { getDateTimeFromString, Id } from "@/types/general";
import { useCoachingSessions } from "@/lib/api/coaching-sessions";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
import { DateTime } from "ts-luxon";
import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider";
+import { fetchOverarchingGoalsByCoachingSessionId } from "@/lib/api/overarching-goals";
+import { OverarchingGoal } from "@/types/overarching-goal";
interface CoachingSessionsSelectorProps extends PopoverProps {
/// The CoachingRelationship Id for which to get a list of associated CoachingSessions
@@ -30,25 +32,47 @@ function CoachingSessionsSelectItems({
}: {
relationshipId: Id;
}) {
- const { coachingSessions, isLoading, isError } =
- useCoachingSessions(relationshipId);
+ const {
+ coachingSessions,
+ isLoading: isLoadingSessions,
+ isError: isErrorSessions,
+ } = useCoachingSessions(relationshipId);
+
const { setCurrentCoachingSessions } = useCoachingSessionStateStore(
(state) => state
);
+ const [goals, setGoals] = useState<(OverarchingGoal[] | undefined)[]>([]);
+ const [isLoadingGoals, setIsLoadingGoals] = useState(false);
- console.debug(`coachingSessions: ${JSON.stringify(coachingSessions)}`);
-
- // Be sure to cache the list of current coaching sessions in the CoachingSessionStateStore
useEffect(() => {
if (!coachingSessions.length) return;
- console.debug(
- `coachingSessions (useEffect): ${JSON.stringify(coachingSessions)}`
- );
setCurrentCoachingSessions(coachingSessions);
}, [coachingSessions]);
- if (isLoading) return
Loading...
;
- if (isError) return Error loading coaching sessions
;
+ useEffect(() => {
+ const fetchGoals = async () => {
+ setIsLoadingGoals(true);
+ try {
+ const sessionIds = coachingSessions?.map((session) => session.id) || [];
+ const goalsPromises = sessionIds.map((id) =>
+ fetchOverarchingGoalsByCoachingSessionId(id)
+ );
+ const fetchedGoals = await Promise.all(goalsPromises);
+ setGoals(fetchedGoals);
+ } catch (error) {
+ console.error("Error fetching goals:", error);
+ } finally {
+ setIsLoadingGoals(false);
+ }
+ };
+
+ if (coachingSessions?.length) {
+ fetchGoals();
+ }
+ }, [coachingSessions]);
+
+ if (isLoadingSessions || isLoadingGoals) return Loading...
;
+ if (isErrorSessions) return Error loading coaching sessions
;
if (!coachingSessions?.length) return No coaching sessions found
;
return (
@@ -62,13 +86,25 @@ function CoachingSessionsSelectItems({
.filter(
(session) => getDateTimeFromString(session.date) < DateTime.now()
)
- .map((session) => (
-
- {getDateTimeFromString(session.date).toLocaleString(
- DateTime.DATETIME_FULL
- )}
-
- ))}
+ .map((session) => {
+ const sessionIndex = coachingSessions.findIndex(
+ (s) => s.id === session.id
+ );
+ return (
+
+
+
+ {goals[sessionIndex]?.[0]?.title || "No goal set"}
+
+
+ {getDateTimeFromString(session.date).toLocaleString(
+ DateTime.DATETIME_FULL
+ )}
+
+
+
+ );
+ })}
)}
{coachingSessions.some(
@@ -80,13 +116,25 @@ function CoachingSessionsSelectItems({
.filter(
(session) => getDateTimeFromString(session.date) >= DateTime.now()
)
- .map((session) => (
-
- {getDateTimeFromString(session.date).toLocaleString(
- DateTime.DATETIME_FULL
- )}
-
- ))}
+ .map((session) => {
+ const sessionIndex = coachingSessions.findIndex(
+ (s) => s.id === session.id
+ );
+ return (
+
+
+
+ {goals[sessionIndex]?.[0]?.title || "No goal set"}
+
+
+ {getDateTimeFromString(session.date).toLocaleString(
+ DateTime.DATETIME_FULL
+ )}
+
+
+
+ );
+ })}
)}
>
@@ -105,23 +153,51 @@ export default function CoachingSessionSelector({
getCurrentCoachingSession,
} = useCoachingSessionStateStore((state) => state);
+ const [currentGoal, setCurrentGoal] = useState();
+ const [isLoadingGoal, setIsLoadingGoal] = useState(false);
+
+ const currentSession = currentCoachingSessionId
+ ? getCurrentCoachingSession(currentCoachingSessionId)
+ : null;
+
+ useEffect(() => {
+ const fetchGoal = async () => {
+ if (!currentCoachingSessionId) return;
+
+ setIsLoadingGoal(true);
+ try {
+ const goals = await fetchOverarchingGoalsByCoachingSessionId(
+ currentCoachingSessionId
+ );
+ setCurrentGoal(goals[0]);
+ } catch (error) {
+ console.error("Error fetching goal:", error);
+ } finally {
+ setIsLoadingGoal(false);
+ }
+ };
+
+ fetchGoal();
+ }, [currentCoachingSessionId]);
+
const handleSetCoachingSession = (coachingSessionId: Id) => {
setCurrentCoachingSessionId(coachingSessionId);
if (onSelect) {
- onSelect(relationshipId);
+ onSelect(coachingSessionId);
}
};
- const currentSession = currentCoachingSessionId
- ? getCurrentCoachingSession(currentCoachingSessionId)
- : null;
-
const displayValue = currentSession ? (
- <>
- {getDateTimeFromString(currentSession.date).toLocaleString(
- DateTime.DATETIME_FULL
- )}
- >
+
+
+ {currentGoal?.title || "No goal set"}
+
+
+ {getDateTimeFromString(currentSession.date).toLocaleString(
+ DateTime.DATETIME_FULL
+ )}
+
+
) : undefined;
return (
diff --git a/src/lib/api/overarching-goals.ts b/src/lib/api/overarching-goals.ts
index 471fa69..737f69d 100644
--- a/src/lib/api/overarching-goals.ts
+++ b/src/lib/api/overarching-goals.ts
@@ -8,8 +8,102 @@ import {
parseOverarchingGoal,
} from "@/types/overarching-goal";
import { ItemStatus, Id } from "@/types/general";
-import { AxiosError, AxiosResponse } from "axios";
+import axios, { AxiosError, AxiosResponse } from "axios";
import { siteConfig } from "@/site.config";
+import useSWR, { useSWRConfig } from "swr";
+
+interface ApiResponseOverarchingGoals {
+ status_code: number;
+ data: OverarchingGoal[];
+}
+
+// Fetch all OverarchingGoals associated with a particular User
+const fetcherOverarchingGoals = async (
+ url: string,
+ coachingSessionId: Id
+): Promise =>
+ axios
+ .get(url, {
+ params: {
+ coaching_session_id: coachingSessionId,
+ },
+ withCredentials: true,
+ timeout: 5000,
+ headers: {
+ "X-Version": siteConfig.env.backendApiVersion,
+ },
+ })
+ .then((res) => res.data.data);
+
+/// A hook to retrieve all OverarchingGoals associated with coachingSessionId
+export function useOverarchingGoals(coachingSessionId: Id) {
+ const { data, error, isLoading } = useSWR(
+ [
+ `${siteConfig.env.backendServiceURL}/overarching_goals`,
+ coachingSessionId,
+ ],
+ ([url, _token]) => fetcherOverarchingGoals(url, coachingSessionId)
+ );
+ const swrConfig = useSWRConfig();
+ console.debug(`swrConfig: ${JSON.stringify(swrConfig)}`);
+
+ console.debug(`overarchingGoals data: ${JSON.stringify(data)}`);
+
+ return {
+ overarchingGoals: Array.isArray(data) ? data : [],
+ isLoading,
+ isError: error,
+ };
+}
+
+/// A hook to retrieve a single OverarchingGoal by a coachingSessionId
+export function useOverarchingGoalByCoachingSessionId(coachingSessionId: Id) {
+ const { overarchingGoals, isLoading, isError } =
+ useOverarchingGoals(coachingSessionId);
+
+ return {
+ overarchingGoal: overarchingGoals.length
+ ? overarchingGoals[0]
+ : defaultOverarchingGoal(),
+ isLoading,
+ isError: isError,
+ };
+}
+
+interface ApiResponseOverarchingGoal {
+ status_code: number;
+ data: OverarchingGoal;
+}
+
+// Fetcher for retrieving a single OverarchingGoal by its Id
+const fetcherOverarchingGoal = async (url: string): Promise =>
+ axios
+ .get(url, {
+ withCredentials: true,
+ timeout: 5000,
+ headers: {
+ "X-Version": siteConfig.env.backendApiVersion,
+ },
+ })
+ .then((res) => res.data.data);
+
+/// A hook to retrieve a single OverarchingGoal by its Id
+export function useOverarchingGoal(overarchingGoalId: Id) {
+ const { data, error, isLoading } = useSWR(
+ `${siteConfig.env.backendServiceURL}/overarching_goals/${overarchingGoalId}`,
+ fetcherOverarchingGoal
+ );
+ const swrConfig = useSWRConfig();
+ console.debug(`swrConfig: ${JSON.stringify(swrConfig)}`);
+
+ console.debug(`overarchingGoal data: ${JSON.stringify(data)}`);
+
+ return {
+ overarchingGoal: data || defaultOverarchingGoal(),
+ isLoading,
+ isError: error,
+ };
+}
export const fetchOverarchingGoalsByCoachingSessionId = async (
coachingSessionId: Id
diff --git a/src/lib/providers/overarching-goal-state-store-provider.tsx b/src/lib/providers/overarching-goal-state-store-provider.tsx
new file mode 100644
index 0000000..50dab8b
--- /dev/null
+++ b/src/lib/providers/overarching-goal-state-store-provider.tsx
@@ -0,0 +1,47 @@
+// The purpose of this provider is to provide compatibility with
+// Next.js re-rendering and component caching
+"use client";
+
+import { type ReactNode, createContext, useRef, useContext } from "react";
+import { type StoreApi, useStore } from "zustand";
+
+import {
+ type OverarchingGoalStateStore,
+ createOverarchingGoalStateStore,
+} from "@/lib/stores/overarching-goal-state-store";
+
+export const OverarchingGoalStateStoreContext =
+ createContext | null>(null);
+
+export interface OverarchingGoalStateStoreProviderProps {
+ children: ReactNode;
+}
+
+export const OverarchingGoalStateStoreProvider = ({
+ children,
+}: OverarchingGoalStateStoreProviderProps) => {
+ const storeRef = useRef>(undefined);
+ if (!storeRef.current) {
+ storeRef.current = createOverarchingGoalStateStore();
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useOverarchingGoalStateStore = (
+ selector: (store: OverarchingGoalStateStore) => T
+): T => {
+ const oagStateStoreContext = useContext(OverarchingGoalStateStoreContext);
+
+ if (!oagStateStoreContext) {
+ throw new Error(
+ `useOverarchingGoalStateStore must be used within OverarchingGoalStateStoreProvider`
+ );
+ }
+
+ return useStore(oagStateStoreContext, selector);
+};
diff --git a/src/lib/providers/root-layout-providers.tsx b/src/lib/providers/root-layout-providers.tsx
index 49f2063..96e7f0c 100644
--- a/src/lib/providers/root-layout-providers.tsx
+++ b/src/lib/providers/root-layout-providers.tsx
@@ -6,6 +6,7 @@ import { SWRConfig } from "swr";
import { OrganizationStateStoreProvider } from "./organization-state-store-provider";
import { CoachingRelationshipStateStoreProvider } from "./coaching-relationship-state-store-provider";
import { CoachingSessionStateStoreProvider } from "./coaching-session-state-store-provider";
+import { OverarchingGoalStateStoreProvider } from "./overarching-goal-state-store-provider";
export function RootLayoutProviders({
children,
@@ -24,15 +25,17 @@ export function RootLayoutProviders({
- new Map(),
- }}
- >
- {children}
-
+
+ new Map(),
+ }}
+ >
+ {children}
+
+
diff --git a/src/lib/stores/coaching-session-state-store.ts b/src/lib/stores/coaching-session-state-store.ts
index 92508f5..2e1a2ab 100644
--- a/src/lib/stores/coaching-session-state-store.ts
+++ b/src/lib/stores/coaching-session-state-store.ts
@@ -16,6 +16,7 @@ interface CoachingSessionState {
interface CoachingSessionsStateActions {
getCurrentCoachingSession: (coachingSessionId: Id) => CoachingSession;
+ getCurrentCoachingSessionId: () => Id;
setCurrentCoachingSessionId: (newCoachingSessionId: Id) => void;
setCurrentCoachingSession: (newCoachingSession: CoachingSession) => void;
setCurrentCoachingSessions: (newCoachingSessions: CoachingSession[]) => void;
@@ -51,11 +52,17 @@ export const createCoachingSessionStateStore = (
)
: defaultCoachingSession();
},
+ getCurrentCoachingSessionId: () => {
+ return get().currentCoachingSessionId;
+ },
setCurrentCoachingSessionId: (newCoachingSessionId) => {
set({ currentCoachingSessionId: newCoachingSessionId });
},
setCurrentCoachingSession: (newCoachingSession) => {
- set({ currentCoachingSession: newCoachingSession });
+ set({
+ currentCoachingSession: newCoachingSession,
+ currentCoachingSessionId: newCoachingSession.id,
+ });
},
setCurrentCoachingSessions: (
newCoachingSessions: CoachingSession[]
diff --git a/src/lib/stores/overarching-goal-state-store.ts b/src/lib/stores/overarching-goal-state-store.ts
new file mode 100644
index 0000000..2eea2a7
--- /dev/null
+++ b/src/lib/stores/overarching-goal-state-store.ts
@@ -0,0 +1,75 @@
+import { Id } from "@/types/general";
+import {
+ defaultOverarchingGoal,
+ defaultOverarchingGoals,
+ getOverarchingGoalById,
+ OverarchingGoal,
+} from "@/types/overarching-goal";
+import { create } from "zustand";
+import { createJSONStorage, devtools, persist } from "zustand/middleware";
+
+interface OverarchingGoalState {
+ currentOverarchingGoalId: Id;
+ currentOverarchingGoal: OverarchingGoal;
+ currentOverarchingGoals: OverarchingGoal[];
+}
+
+interface OverarchingGoalStateActions {
+ getCurrentOverarchingGoal: (overarchingGoalId: Id) => OverarchingGoal;
+ setCurrentOverarchingGoalId: (newOverarchingGoalId: Id) => void;
+ setCurrentOverarchingGoal: (newOverarchingGoal: OverarchingGoal) => void;
+ setCurrentOverarchingGoals: (newOverarchingGoals: OverarchingGoal[]) => void;
+ resetOverarchingGoalState(): void;
+}
+
+export type OverarchingGoalStateStore = OverarchingGoalState &
+ OverarchingGoalStateActions;
+
+export const defaultInitState: OverarchingGoalState = {
+ currentOverarchingGoalId: "",
+ currentOverarchingGoal: defaultOverarchingGoal(),
+ currentOverarchingGoals: defaultOverarchingGoals(),
+};
+
+export const createOverarchingGoalStateStore = (
+ initState: OverarchingGoalState = defaultInitState
+) => {
+ const oagStateStore = create()(
+ devtools(
+ persist(
+ (set, get) => ({
+ ...initState,
+
+ // Expects the array of OverarchingGoals to be fetched and set
+ getCurrentOverarchingGoal: (
+ overarchingGoalId: Id
+ ): OverarchingGoal => {
+ return get().currentOverarchingGoals
+ ? getOverarchingGoalById(
+ overarchingGoalId,
+ get().currentOverarchingGoals
+ )
+ : defaultOverarchingGoal();
+ },
+ setCurrentOverarchingGoalId: (newOverarchingGoalId) => {
+ set({ currentOverarchingGoalId: newOverarchingGoalId });
+ },
+ setCurrentOverarchingGoal: (newOverarchingGoal) => {
+ set({ currentOverarchingGoal: newOverarchingGoal });
+ },
+ setCurrentOverarchingGoals(newOverarchingGoals: OverarchingGoal[]) {
+ set({ currentOverarchingGoals: newOverarchingGoals });
+ },
+ resetOverarchingGoalState(): void {
+ set(defaultInitState);
+ },
+ }),
+ {
+ name: "overarching-goal-state-store",
+ storage: createJSONStorage(() => sessionStorage),
+ }
+ )
+ )
+ );
+ return oagStateStore;
+};