From 01e734ed12f64ef287c109cbaf1e6254231d006c Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Wed, 27 Nov 2024 17:04:46 -0600 Subject: [PATCH 1/2] Add a new component to select a coaching session from the coaching session page. Also get rid of useEffect render loops. --- src/app/coaching-sessions/[id]/page.tsx | 126 +++++++++++------- .../ui/coaching-session-selector.tsx | 109 +++++++++++++++ .../overarching-goal-container.tsx | 39 +++--- 3 files changed, 201 insertions(+), 73 deletions(-) create mode 100644 src/components/ui/coaching-session-selector.tsx diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 3428f42..8a3911a 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -36,6 +36,10 @@ import { CoachingNotes, EditorRef, } from "@/components/ui/coaching-sessions/coaching-notes"; +import { CoachingSessionSelector } from "@/components/ui/coaching-session-selector"; +import { CoachingSession } from "@/types/coaching-session"; +import { useRouter } from "next/navigation"; +import { fetchCoachingSessions } from "@/lib/api/coaching-sessions"; // export const metadata: Metadata = { // title: "Coaching Session", @@ -43,59 +47,69 @@ import { // }; export default function CoachingSessionsPage() { + const router = useRouter(); const [note, setNote] = useState(defaultNote()); const [syncStatus, setSyncStatus] = useState(""); const { userId } = useAuthStore((state) => ({ userId: state.userId })); const { coachingSession } = useAppStateStore((state) => state); + const { coachingSessionId, setCoachingSessionId } = useAppStateStore( + (state) => state + ); + const [coachingSessions, setCoachingSessions] = React.useState< + CoachingSession[] + >([]); + const { relationshipId } = useAppStateStore((state) => state); const [isLoading, setIsLoading] = useState(false); const editorRef = useRef(null); - async function fetchNote() { - if (!coachingSession.id) { - console.error( - "Failed to fetch Note since coachingSession.id is not set." - ); - return; - } - if (isLoading) { - console.debug( - "Not issuing a new Note fetch because a previous fetch is still in progress." - ); - } + const fetchNoteData = async () => { + if (isLoading) return; setIsLoading(true); + try { + const notes = await fetchNotesByCoachingSessionId(coachingSessionId); + if (notes.length > 0) { + setEditorContent(notes[0].body); + setNote(notes[0]); + setSyncStatus("Notes refreshed"); + setEditorFocussed(); + } + } catch (err) { + console.error(`Failed to fetch Note: ${err}`); + } finally { + setIsLoading(false); + } + }; - await fetchNotesByCoachingSessionId(coachingSession.id) - .then((notes) => { - const note = notes[0]; - if (notes.length > 0) { - console.trace("Fetched note: " + noteToString(note)); - setEditorContent(note.body); - setNote(note); - setSyncStatus("Notes refreshed"); - setEditorFocussed(); - } else { - console.trace( - "No Notes associated with this coachingSession.id: " + - coachingSession.id - ); - } - }) - .catch((err) => { - console.error( - "Failed to fetch Note for current coaching session id: " + - coachingSession.id + - ". Error: " + - err - ); - }); + useEffect(() => { + if (!coachingSessionId) return; - setIsLoading(false); - } + fetchNoteData(); + }, [coachingSessionId]); // Remove isLoading from dependencies useEffect(() => { - fetchNote(); - }, [coachingSession.id, isLoading]); + if (!relationshipId) return; + + const loadCoachingSessions = async () => { + if (isLoading) return; + + setIsLoading(true); + + try { + const [coachingSessions] = await fetchCoachingSessions(relationshipId); + console.debug( + "setCoachingSessions: " + JSON.stringify(coachingSessions) + ); + setCoachingSessions(coachingSessions); + } catch (err) { + console.error("Failed to fetch coaching sessions: " + err); + } finally { + setIsLoading(false); + } + }; + + loadCoachingSessions(); + }, [relationshipId]); const setEditorContent = (content: string) => { editorRef.current?.setContent(`${content}`); @@ -108,14 +122,14 @@ export default function CoachingSessionsPage() { const handleOnChange = (value: string) => { console.debug("isLoading (before update/create): " + isLoading); console.debug( - "coachingSession.id (before update/create): " + coachingSession.id + "coachingSessionId (before update/create): " + coachingSessionId ); console.debug("userId (before update/create): " + userId); console.debug("value (before update/create): " + value); console.debug("--------------------------------"); - if (!isLoading && note.id && coachingSession.id && userId) { - updateNote(note.id, coachingSession.id, userId, value) + if (!isLoading && note.id && coachingSessionId && userId) { + updateNote(note.id, coachingSessionId, userId, value) .then((updatedNote) => { setNote(updatedNote); console.trace("Updated Note: " + noteToString(updatedNote)); @@ -126,7 +140,7 @@ export default function CoachingSessionsPage() { console.error("Failed to update Note: " + err); }); } else if (!isLoading && !note.id && coachingSession.id && userId) { - createNote(coachingSession.id, userId, value) + createNote(coachingSessionId, userId, value) .then((createdNote) => { setNote(createdNote); console.trace("Newly created Note: " + noteToString(createdNote)); @@ -151,6 +165,12 @@ export default function CoachingSessionsPage() { document.title = sessionTitle; }; + const handleCoachingSessionSelect = (coachingSessionId: string) => { + setCoachingSessionId(coachingSessionId); + console.debug("coachingSessionId selected: " + coachingSessionId); + router.push(`/coaching-sessions/${coachingSessionId}`); + }; + return (
@@ -160,12 +180,12 @@ export default function CoachingSessionsPage() { style={siteConfig.titleStyle} onRender={handleTitleRender} > -
- - {/* Hidden for MVP */} -
- -
+
+
@@ -224,7 +244,11 @@ export default function CoachingSessionsPage() {
-
diff --git a/src/components/ui/coaching-session-selector.tsx b/src/components/ui/coaching-session-selector.tsx new file mode 100644 index 0000000..78358d4 --- /dev/null +++ b/src/components/ui/coaching-session-selector.tsx @@ -0,0 +1,109 @@ +"use client"; + +import * as React from "react"; +import { PopoverProps } from "@radix-ui/react-popover"; + +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +import "@/styles/code-block.scss"; +import { + CoachingSession, + coachingSessionToString, + getCoachingSessionById, +} from "@/types/coaching-session"; +import { getDateTimeFromString, Id } from "@/types/general"; +import { DateTime } from "ts-luxon"; +import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; + +interface CoachingSessionSelectorProps extends PopoverProps { + sessions: CoachingSession[]; + placeholder: string; + onSelect: (session: Id) => void; +} + +export function CoachingSessionSelector({ + sessions, + placeholder, + onSelect, + ...props +}: CoachingSessionSelectorProps) { + const { coachingSessionId, setCoachingSessionId } = useAppStateStore( + (state) => state + ); + const { setCoachingSession } = useAppStateStore((state) => state); + + const handleSetCoachingSession = (coachingSessionId: string) => { + setCoachingSessionId(coachingSessionId); + const coachingSession = getCoachingSessionById(coachingSessionId, sessions); + console.debug( + "coachingSession: " + coachingSessionToString(coachingSession) + ); + setCoachingSession(coachingSession); + onSelect(coachingSessionId); + }; + + return ( + + ); +} diff --git a/src/components/ui/coaching-sessions/overarching-goal-container.tsx b/src/components/ui/coaching-sessions/overarching-goal-container.tsx index 4d6f712..52bb00c 100644 --- a/src/components/ui/coaching-sessions/overarching-goal-container.tsx +++ b/src/components/ui/coaching-sessions/overarching-goal-container.tsx @@ -35,16 +35,11 @@ const OverarchingGoalContainer: React.FC<{ const [isOpen, setIsOpen] = useState(false); const [goal, setGoal] = useState(defaultOverarchingGoal()); const [goalId, setGoalId] = useState(""); - const { coachingSession, coachingRelationship } = useAppStateStore( - (state) => ({ - coachingSession: state.coachingSession, - coachingRelationship: state.coachingRelationship, - }) - ); + const { coachingSessionId } = useAppStateStore((state) => state); const handleAgreementAdded = (body: string): Promise => { // Calls the backend endpoint that creates and stores a full Agreement entity - return createAgreement(coachingSession.id, userId, body) + return createAgreement(coachingSessionId, userId, body) .then((agreement) => { return agreement; }) @@ -55,7 +50,7 @@ const OverarchingGoalContainer: React.FC<{ }; const handleAgreementEdited = (id: Id, body: string): Promise => { - return updateAgreement(id, coachingSession.id, userId, body) + return updateAgreement(id, coachingSessionId, userId, body) .then((agreement) => { return agreement; }) @@ -82,7 +77,7 @@ const OverarchingGoalContainer: React.FC<{ dueBy: DateTime ): Promise => { // Calls the backend endpoint that creates and stores a full Action entity - return createAction(coachingSession.id, body, status, dueBy) + return createAction(coachingSessionId, body, status, dueBy) .then((action) => { return action; }) @@ -98,7 +93,7 @@ const OverarchingGoalContainer: React.FC<{ status: ItemStatus, dueBy: DateTime ): Promise => { - return updateAction(id, coachingSession.id, body, status, dueBy) + return updateAction(id, coachingSessionId, body, status, dueBy) .then((action) => { return action; }) @@ -121,14 +116,14 @@ const OverarchingGoalContainer: React.FC<{ useEffect(() => { async function fetchOverarchingGoal() { - if (!coachingSession.id) { + if (!coachingSessionId) { console.error( - "Failed to fetch Overarching Goal since coachingSession.id is not set." + "Failed to fetch Overarching Goal since coachingSessionId is not set." ); return; } - await fetchOverarchingGoalsByCoachingSessionId(coachingSession.id) + await fetchOverarchingGoalsByCoachingSessionId(coachingSessionId) .then((goals) => { const goal = goals[0]; if (goals.length > 0) { @@ -137,7 +132,7 @@ const OverarchingGoalContainer: React.FC<{ setGoal(goal); } else { console.trace( - "No Overarching Goals associated with this coachingSession.id" + "No Overarching Goals associated with this coachingSessionId" ); } }) @@ -149,16 +144,16 @@ const OverarchingGoalContainer: React.FC<{ }); } fetchOverarchingGoal(); - }, [coachingSession.id, goalId]); + }, [coachingSessionId]); const handleGoalChange = async (newGoal: OverarchingGoal) => { console.trace("handleGoalChange (goal to set/update): " + newGoal.title); - if (goalId && coachingSession.id) { + if (goalId && coachingSessionId) { console.debug("Update existing Overarching Goal with id: " + goalId); updateOverarchingGoal( goalId, - coachingSession.id, + coachingSessionId, newGoal.title, newGoal.body, newGoal.status @@ -172,9 +167,9 @@ const OverarchingGoalContainer: React.FC<{ .catch((err) => { console.error("Failed to update Overarching Goal: " + err); }); - } else if (!goalId && coachingSession.id) { + } else if (!goalId && coachingSessionId) { createOverarchingGoal( - coachingSession.id, + coachingSessionId, newGoal.title, newGoal.body, newGoal.status @@ -192,7 +187,7 @@ const OverarchingGoalContainer: React.FC<{ }); } else { console.error( - "Could not update or create a Overarching Goal since coachingSession.id or userId are not set." + "Could not update or create a Overarching Goal since coachingSessionId or userId are not set." ); } }; @@ -226,7 +221,7 @@ const OverarchingGoalContainer: React.FC<{
Date: Wed, 27 Nov 2024 17:25:03 -0600 Subject: [PATCH 2/2] Ensure the entire Join Session button can be clicked to join a coaching session from the dashboard page --- .../ui/dashboard/select-coaching-session.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/ui/dashboard/select-coaching-session.tsx b/src/components/ui/dashboard/select-coaching-session.tsx index f22345d..110e671 100644 --- a/src/components/ui/dashboard/select-coaching-session.tsx +++ b/src/components/ui/dashboard/select-coaching-session.tsx @@ -288,13 +288,20 @@ export function SelectCoachingSession({
- + {coachingSessionId ? ( + + + + ) : ( + + )} );