Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Join session milestone point #43

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bb9ef5e
todo: update hook to post, update component
qafui Sep 26, 2024
0a28c00
Get basic struction working
qafui Oct 1, 2024
6f5423f
Clean up dynamic select component
qafui Oct 2, 2024
02278df
remove comments
qafui Oct 2, 2024
f413887
Fix session separation and date-time rendering
qafui Oct 3, 2024
d4ab474
Merge branch 'main' into select-session
qafui Oct 3, 2024
2821e62
Use zustand to share ids across contexts
qafui Oct 3, 2024
bdc92ec
Format dynamic select component with Prettier
qafui Oct 9, 2024
c6bd7ff
Merge and resolve conflicts
qafui Oct 12, 2024
766bd8e
Fix css conflict on coaching-session page
qafui Oct 12, 2024
4889b1e
Use dynamic-api-selector on session page
qafui Oct 12, 2024
27239a9
Add coaching relationship to state store on select
qafui Oct 16, 2024
9f5d37b
Load full fetched objects into state
qafui Oct 25, 2024
bc2ff8e
Merge branch 'main' into join-session-milestone-point
qafui Oct 25, 2024
d93108a
Run npm i
qafui Oct 25, 2024
96a4d59
Update coaching session with latest
qafui Oct 25, 2024
1607fbd
Duplicate session selection component and logic - yuck
qafui Oct 26, 2024
c4206d2
Make sure a proper CoachingSession Id gets passed to the coaching ses…
jhodapp Oct 26, 2024
fd0b4ea
Fix session fetch loop and extra goal EP hit
qafui Oct 30, 2024
68f4ed5
Set overarching goal per session
qafui Oct 30, 2024
ad4c6a4
Clean up join coaching session component
qafui Oct 31, 2024
2bdae0f
Clean up join-coaching-session component further
qafui Oct 31, 2024
7f7011a
Use coachee last name in relationship string
qafui Oct 31, 2024
6e898e6
Sort session dates
qafui Oct 31, 2024
5efcf61
Persist placeholder with useEffect
qafui Oct 31, 2024
f9e79df
Make sure the entire join session button is clickable
qafui Oct 31, 2024
1ad2df8
Slight wording change on session page
qafui Oct 31, 2024
b8afc27
Update page.tsx
qafui Oct 31, 2024
dc32d21
Transform displayed date in state store
qafui Oct 31, 2024
6cc4734
Extract state logic from joinCoachingSession component
qafui Nov 8, 2024
4354aa0
Clean up selection hook
qafui Nov 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
371 changes: 148 additions & 223 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@
"@tiptap/starter-kit": "^2.8.0",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"lucide-react": "^0.453.0",
"next": "^14.2.15",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"sass": "^1.80.2",
"tailwind-merge": "^2.5.4",
"clsx": "^2.1.0",
"cmdk": "^0.2.1",
"lucide-react": "^0.314.0",
"next": "^14.2.3",
"next-themes": "^0.2.1",
"react": "^18",
"react-day-picker": "^8.10.0",
"react-dom": "^18",
"swr": "^2.2.5",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"ts-luxon": "^5.0.7-beta.0",
"zustand": "^4.5.5"
Expand Down
89 changes: 61 additions & 28 deletions src/app/coaching-sessions/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
// import { Metadata } from "next";

import * as React from "react";

import { useRouter } from "next/navigation";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";

import { PresetActions } from "@/components/ui/preset-actions";
import { PresetSelector } from "@/components/ui/preset-selector";
import { current, future, past } from "@/data/presets";
import { useAppStateStore } from "@/lib/providers/app-state-store-provider";
import { useEffect, useRef, useState } from "react";
import {
Expand All @@ -36,6 +33,11 @@ import {
CoachingNotes,
EditorRef,
} from "@/components/ui/coaching-sessions/coaching-notes";
import { DynamicApiSelect } from "@/components/ui/dashboard/dynamic-api-select";
import { CoachingSession } from "@/types/coaching-session";
import { fetchCoachingSessions } from "@/lib/api/coaching-sessions";
import { Id } from "@/types/general";
import { DateTime } from "ts-luxon";

// export const metadata: Metadata = {
// title: "Coaching Session",
Expand All @@ -46,26 +48,25 @@ export default function CoachingSessionsPage() {
const [note, setNote] = useState<Note>(defaultNote());
const [syncStatus, setSyncStatus] = useState<string>("");
const { userId } = useAuthStore((state) => ({ userId: state.userId }));
const { coachingSession } = useAppStateStore((state) => state);
const coachingSessionId = useAppStateStore(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ok if it fixes the fetch loop (not sure if it was the cause or not). We'll convert to de-duplicating storing these Ids separately from their object instances after we land this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the cause but a small optimization after observing that we weren't using any other properties of the object in the component.

I could be wrong but the way I understand it, this is a case of whether we want to pass around the string value or the reference to the object (while operating on its mutable property).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm open to your thoughts here on what's best long term for convenience and efficiency.

(state) => state.coachingSessionId
);
const [isLoading, setIsLoading] = useState(false);
const editorRef = useRef<EditorRef>(null);

async function fetchNote() {
if (!coachingSession.id) {
console.error(
"Failed to fetch Note since coachingSession.id is not set."
);
if (!coachingSessionId) {
console.error("Failed to fetch Note since coachingSessionId is not set.");
return;
}
if (isLoading) {
console.debug(
"Not issuing a new Note fetch because a previous fetch is still in progress."
);
setIsLoading(true);
}

setIsLoading(true);

await fetchNotesByCoachingSessionId(coachingSession.id)
await fetchNotesByCoachingSessionId(coachingSessionId)
.then((notes) => {
const note = notes[0];
if (notes.length > 0) {
Expand All @@ -76,15 +77,15 @@ export default function CoachingSessionsPage() {
setEditorFocussed();
} else {
console.trace(
"No Notes associated with this coachingSession.id: " +
coachingSession.id
"No Notes associated with this coachingSessionId: " +
coachingSessionId
);
}
})
.catch((err) => {
console.error(
"Failed to fetch Note for current coaching session id: " +
coachingSession.id +
coachingSessionId +
". Error: " +
err
);
Expand All @@ -95,7 +96,7 @@ export default function CoachingSessionsPage() {

useEffect(() => {
fetchNote();
}, [coachingSession.id, isLoading]);
}, [coachingSessionId, isLoading]);

const setEditorContent = (content: string) => {
editorRef.current?.setContent(`${content}`);
Expand All @@ -108,14 +109,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));
Expand All @@ -125,8 +126,8 @@ export default function CoachingSessionsPage() {
setSyncStatus("Failed to save changes");
console.error("Failed to update Note: " + err);
});
} else if (!isLoading && !note.id && coachingSession.id && userId) {
createNote(coachingSession.id, userId, value)
} else if (!isLoading && !note.id && coachingSessionId && userId) {
createNote(coachingSessionId, userId, value)
.then((createdNote) => {
setNote(createdNote);
console.trace("Newly created Note: " + noteToString(createdNote));
Expand All @@ -138,7 +139,7 @@ export default function CoachingSessionsPage() {
});
} else {
console.error(
"Could not update or create a Note since coachingSession.id or userId are not set."
"Could not update or create a Note since coachingSessionId or userId are not set."
);
}
};
Expand All @@ -151,6 +152,29 @@ export default function CoachingSessionsPage() {
document.title = sessionTitle;
};

const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate();
const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate();

const setSession = useAppStateStore((state) => state.setCoachingSession);
const setSessionId = useAppStateStore((state) => state.setCoachingSessionId);
const relationshipId = useAppStateStore((state) => state.relationshipId);
const router = useRouter();

const handleSessionSelection = (selectedSession: Id) => {
if (selectedSession && relationshipId) {
fetchCoachingSessions(relationshipId).then(([sessions]) => {
const theSession = sessions.find(
(session) => session.id === selectedSession
);
if (theSession) {
setSession(theSession);
setSessionId(theSession.id);
router.push(`/coaching-sessions/${theSession.id}`);
}
});
}
};

return (
<div className="max-w-screen-2xl">
<div className="flex-col h-full md:flex ">
Expand All @@ -160,12 +184,21 @@ export default function CoachingSessionsPage() {
style={siteConfig.titleStyle}
onRender={handleTitleRender}
qafui marked this conversation as resolved.
Show resolved Hide resolved
></CoachingSessionTitle>
<div className="ml-auto flex w-full space-x-2 sm:justify-end">
<PresetSelector current={current} future={future} past={past} />
{/* Hidden for MVP */}
<div className="hidden">
<PresetActions />
</div>
<div className="ml-auto flex w-64 space-x-2 sm:justify-end">
<DynamicApiSelect<CoachingSession>
url="/coaching_sessions"
params={{
coaching_relationship_id: relationshipId,
from_date: FROM_DATE,
to_Date: TO_DATE,
}}
onChange={handleSessionSelection}
placeholder="Select a session"
getOptionLabel={(session) => session.date.toString()}
getOptionValue={(session) => session.id.toString()}
elementId="session-selector"
groupByDate={true}
/>
</div>
</div>
</div>
Expand Down
30 changes: 24 additions & 6 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Image from "next/image";

import { cn } from "@/lib/utils";

import { SelectCoachingSession } from "@/components/ui/dashboard/select-coaching-session";
import { JoinCoachingSession } from "@/components/ui/dashboard/join-coaching-session";
import { useAuthStore } from "@/lib/providers/auth-store-provider";

// export const metadata: Metadata = {
Expand All @@ -35,10 +35,28 @@ export default function DashboardPage() {
const { userId } = useAuthStore((state) => state);

return (
<div className="items-start justify-center gap-6 rounded-lg p-8 md:grid lg:grid-cols-2 xl:grid-cols-3">
<DashboardContainer>
<SelectCoachingSession userId={userId} />
</DashboardContainer>
</div>
<>
<div className="md:hidden">
<Image
src="/examples/cards-light.png"
width={1280}
height={1214}
alt="Cards"
className="block dark:hidden"
/>
<Image
src="/examples/cards-dark.png"
width={1280}
height={1214}
alt="Cards"
className="hidden dark:block"
/>
</div>
<div className="hidden items-start justify-center gap-6 rounded-lg p-8 md:grid lg:grid-cols-2 xl:grid-cols-3">
<DashboardContainer>
<JoinCoachingSession userId={userId} />
</DashboardContainer>
</div>
</>
);
}
59 changes: 31 additions & 28 deletions src/components/ui/coaching-sessions/overarching-goal-container.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useEffect, useState } from "react";
import { useEffect, useState, useCallback } from "react";
import { ActionsList } from "@/components/ui/coaching-sessions/actions-list";
import { ItemStatus, Id } from "@/types/general";
import { Action } from "@/types/action";
Expand Down Expand Up @@ -119,37 +119,40 @@ const OverarchingGoalContainer: React.FC<{
});
};

useEffect(() => {
async function fetchOverarchingGoal() {
if (!coachingSession.id) {
console.error(
"Failed to fetch Overarching Goal since coachingSession.id is not set."
const fetchOverarchingGoal = useCallback(async () => {
if (!coachingSession.id) {
console.error(
"Failed to fetch Overarching Goal since coachingSession.id is not set."
);
return;
}

try {
const goals = await fetchOverarchingGoalsByCoachingSessionId(
coachingSession.id
);
if (goals.length > 0) {
const goal = goals[0];
console.trace("Overarching Goal: " + overarchingGoalToString(goal));
if (goal.id !== goalId) {
setGoalId(goal.id);
setGoal(goal);
}
} else {
console.trace(
"No Overarching Goals associated with this coachingSession.id"
);
return;
}

await fetchOverarchingGoalsByCoachingSessionId(coachingSession.id)
.then((goals) => {
const goal = goals[0];
if (goals.length > 0) {
console.trace("Overarching Goal: " + overarchingGoalToString(goal));
setGoalId(goal.id);
setGoal(goal);
} else {
console.trace(
"No Overarching Goals associated with this coachingSession.id"
);
}
})
.catch((err) => {
console.error(
"Failed to fetch Overarching Goal for current coaching session: " +
err
);
});
} catch (err) {
console.error(
"Failed to fetch Overarching Goal for current coaching session: " + err
);
}
}, [coachingSession.id, goalId, setGoalId, setGoal]);

useEffect(() => {
fetchOverarchingGoal();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@qafui this is necessary, right now if you go to a coaching session page, if you've set an OverarchingGoal, it isn't getting loaded when the page loads.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I should have re-tested that bit. I noticed it was re-rendering in the console and thought to fix it with memoization but the thought somehow trailed off. I was too tired 😸

}, [coachingSession.id, goalId]);
}, [fetchOverarchingGoal]);

const handleGoalChange = async (newGoal: OverarchingGoal) => {
console.trace("handleGoalChange (goal to set/update): " + newGoal.title);
Expand Down
Loading
Loading