From bb9ef5ebdb0f8afe697be9315d964b32e2b833d7 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 25 Sep 2024 23:56:09 -0400 Subject: [PATCH 01/28] todo: update hook to post, update component --- package-lock.json | 14 + package.json | 1 + src/app/dashboard/page.tsx | 5 +- .../ui/dashboard/join-coaching-session.tsx | 53 ++++ .../ui/dashboard/select-coaching-session.tsx | 261 +++--------------- src/hooks/use-api-data.ts | 39 +++ src/site.config.ts | 26 +- 7 files changed, 156 insertions(+), 243 deletions(-) create mode 100644 src/components/ui/dashboard/join-coaching-session.tsx create mode 100644 src/hooks/use-api-data.ts diff --git a/package-lock.json b/package-lock.json index 04dd443..2ab744c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "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": "^4.5.2", @@ -6034,6 +6035,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "license": "MIT", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tailwind-merge": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz", diff --git a/package.json b/package.json index 69b79a2..986d5dd 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "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": "^4.5.2", diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 00ab6c4..503b1b9 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -8,7 +8,8 @@ import Image from "next/image"; import { cn } from "@/lib/utils"; -import { SelectCoachingSession } from "@/components/ui/dashboard/select-coaching-session"; +// 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 = { @@ -54,7 +55,7 @@ export default function DashboardPage() {
- +
diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx new file mode 100644 index 0000000..02393a9 --- /dev/null +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { Id } from "@/types/general"; +import { SelectCoachingSession } from './select-coaching-session'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +export interface CoachingSessionCardProps { + userId: Id; +} + +export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps) { + const [firstSelection, setFirstSelection] = useState(null) + const [secondSelection, setSecondSelection] = useState(null) + + const handleFirstSelection = (value: string) => { + setFirstSelection(value) + setSecondSelection(null) // Reset second selection when first changes + } + + const handleSecondSelection = (value: string) => { + setSecondSelection(value) + } + + return ( + + + Make Your Selection + + +
+ + {firstSelection && ( + + )} + {secondSelection && ( +

+ You selected: {firstSelection} and {secondSelection} +

+ )} +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/ui/dashboard/select-coaching-session.tsx b/src/components/ui/dashboard/select-coaching-session.tsx index 6b13dd3..d979517 100644 --- a/src/components/ui/dashboard/select-coaching-session.tsx +++ b/src/components/ui/dashboard/select-coaching-session.tsx @@ -1,238 +1,43 @@ -"use client"; +import React, { useState } from 'react'; +import { useApiData } from '@/hooks/use-api-data'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { fetchCoachingRelationshipsWithUserNames } from "@/lib/api/coaching-relationships"; -import { fetchCoachingSessions } from "@/lib/api/coaching-sessions"; -import { fetchOrganizationsByUserId } from "@/lib/api/organizations"; -import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; -import { CoachingSession } from "@/types/coaching-session"; -import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names"; -import { Id } from "@/types/general"; -import { Organization } from "@/types/organization"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { DateTime } from "ts-luxon"; - -export interface CoachingSessionProps { - /** The current logged in user's Id */ - userId: Id; +interface Option { + value: string, + label: string } -export function SelectCoachingSession({ - userId: userId, - ...props -}: CoachingSessionProps) { - const { organizationId, setOrganizationId } = useAppStateStore( - (state) => state - ); - const { relationshipId, setRelationshipId } = useAppStateStore( - (state) => state - ); - const { coachingSessionId, setCoachingSessionId } = useAppStateStore( - (state) => state - ); - - const [organizations, setOrganizations] = useState([]); - const [coachingRelationships, setCoachingRelationships] = useState< - CoachingRelationshipWithUserNames[] - >([]); - const [coachingSessions, setCoachingSessions] = useState( - [] - ); - - useEffect(() => { - async function loadOrganizations() { - if (!userId) return; - - await fetchOrganizationsByUserId(userId) - .then(([orgs]) => { - // Apparently it's normal for this to be triggered twice in modern - // React versions in strict + development modes - // https://stackoverflow.com/questions/60618844/react-hooks-useeffect-is-called-twice-even-if-an-empty-array-is-used-as-an-ar - console.debug("setOrganizations: " + JSON.stringify(orgs)); - setOrganizations(orgs); - }) - .catch(([err]) => { - console.error("Failed to fetch Organizations: " + err); - }); - } - loadOrganizations(); - }, [userId]); - - useEffect(() => { - async function loadCoachingRelationships() { - if (!organizationId) return; - - console.debug("organizationId: " + organizationId); +interface SelectCoachingSessionProps { + url: string, + params?: Record, + onChange: (value: string) => void, + placeholder?: string +} - await fetchCoachingRelationshipsWithUserNames(organizationId) - .then(([relationships]) => { - console.debug( - "setCoachingRelationships: " + JSON.stringify(relationships) - ); - setCoachingRelationships(relationships); - }) - .catch(([err]) => { - console.error("Failed to fetch coaching relationships: " + err); - }); - } - loadCoachingRelationships(); - }, [organizationId]); +export function SelectCoachingSession({ url, params = {}, onChange, placeholder = "Select an option" }: SelectCoachingSessionProps) { + const { data, isLoading, error } = useApiData(url, params); + const [value, setValue] = useState(''); - useEffect(() => { - async function loadCoachingSessions() { - if (!organizationId) return; + const handleValueChange = (newValue: string) => { + setValue(newValue); + onChange(newValue); + } - await fetchCoachingSessions(relationshipId) - .then(([coaching_sessions]) => { - console.debug( - "setCoachingSessions: " + JSON.stringify(coaching_sessions) - ); - setCoachingSessions(coaching_sessions); - }) - .catch(([err]) => { - console.error("Failed to fetch coaching sessions: " + err); - }); - } - loadCoachingSessions(); - }, [relationshipId]); + if (isLoading) return

Loading...

+ if (error) return

Error: {error.message}

return ( - - - Join a Coaching Session - - Select current organization, relationship and session - - - -
- - -
-
- - -
-
- - -
-
- - - -
+ ); } diff --git a/src/hooks/use-api-data.ts b/src/hooks/use-api-data.ts new file mode 100644 index 0000000..1f61b1e --- /dev/null +++ b/src/hooks/use-api-data.ts @@ -0,0 +1,39 @@ +import useSWR from 'swr'; +import { siteConfig } from '@/site.config'; + +interface FetcherOptions { + url: string + params?: Record +} + +const baseUrl = siteConfig.url; + +const fetcher = async ({ url, params }: FetcherOptions) => { + const queryString = new URLSearchParams(params).toString(); + const fullUrl = `${baseUrl}${url}${queryString ? `?${queryString}` : ''}`; + console.log(fullUrl); + const response = await fetch(fullUrl); + + if (!response.ok) { + throw new Error('An error occcured while fetching the data.'); + } + + return response.json(); +}; + +export function useApiData(url: string, params: Record = {}) { + const { data, error, isLoading } = useSWR( + { url, params }, + fetcher, + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + } + ); + + return { + data, + isLoading, + error + }; +}; \ No newline at end of file diff --git a/src/site.config.ts b/src/site.config.ts index be3c703..8a9599c 100644 --- a/src/site.config.ts +++ b/src/site.config.ts @@ -1,17 +1,17 @@ export const siteConfig = { - name: "Refactor Coaching & Mentoring", - url: "https://refactorcoach.com", - ogImage: "https://ui.shadcn.com/og.jpg", - locale: "us", - description: - "A platform for software engineers and tech leaders to level up their foundational skills.", - links: { - twitter: "https://twitter.com/shadcn", - github: "https://github.com/shadcn-ui/ui", - }, - } - - export type SiteConfig = typeof siteConfig + name: "Refactor Coaching & Mentoring", + url: "http://localhost:4000", + ogImage: "https://ui.shadcn.com/og.jpg", + locale: "us", + description: + "A platform for software engineers and tech leaders to level up their foundational skills.", + links: { + twitter: "https://twitter.com/shadcn", + github: "https://github.com/shadcn-ui/ui", + }, +} + +export type SiteConfig = typeof siteConfig import { MainNavItem, SidebarNavItem } from "./types/nav" From 0a28c00e50853e404884249ec9dc2ebb42be6876 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 1 Oct 2024 12:17:17 -0400 Subject: [PATCH 02/28] Get basic struction working --- .../ui/dashboard/dynamic-api-select.tsx | 57 ++++++++++ .../ui/dashboard/join-coaching-session.tsx | 105 +++++++++++------- .../ui/dashboard/select-coaching-session.tsx | 43 ------- src/hooks/use-api-data.ts | 56 +++++++--- .../coaching_relationship_with_user_names.ts | 90 +++++++-------- 5 files changed, 207 insertions(+), 144 deletions(-) create mode 100644 src/components/ui/dashboard/dynamic-api-select.tsx delete mode 100644 src/components/ui/dashboard/select-coaching-session.tsx diff --git a/src/components/ui/dashboard/dynamic-api-select.tsx b/src/components/ui/dashboard/dynamic-api-select.tsx new file mode 100644 index 0000000..fa13877 --- /dev/null +++ b/src/components/ui/dashboard/dynamic-api-select.tsx @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import { useApiData } from '@/hooks/use-api-data'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; + +interface DynamicApiSelectProps { + url: string; + method?: 'GET' | 'POST'; + params?: Record; + onChange: (value: string) => void; + placeholder?: string; + getOptionLabel: (item: T) => string; + getOptionValue: (item: T) => string; +} + +interface ApiResponse { + status_code: number; + data: T[]; +} + +export function DynamicApiSelect({ + url, + method = 'GET', + params = {}, + onChange, + placeholder = "Select an organization", + getOptionLabel, + getOptionValue +}: DynamicApiSelectProps) { + const { data: response, isLoading, error } = useApiData>(url, { method, params }); + const [value, setValue] = useState(''); + + const handleValueChange = (newValue: string) => { + setValue(newValue); + onChange(newValue); + } + + if (isLoading) return

Loading...

+ if (error) return

Error: {error.message}

+ if (!response || response.status_code !== 200) return

Error: Invalid response

+ + const items = response.data; + + return ( + + ); +} diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 02393a9..4e79293 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -1,53 +1,76 @@ import React, { useState } from 'react'; import { Id } from "@/types/general"; -import { SelectCoachingSession } from './select-coaching-session'; +import { DynamicApiSelect } from './dynamic-api-select'; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Organization } from '@/types/organization'; +import { CoachingRelationshipWithUserNames } from '@/types/coaching_relationship_with_user_names'; +import { CoachingSession } from '@/types/coaching-session'; +import { DateTime } from 'ts-luxon'; export interface CoachingSessionCardProps { - userId: Id; + userId: Id; } export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps) { - const [firstSelection, setFirstSelection] = useState(null) - const [secondSelection, setSecondSelection] = useState(null) + const [organization, setOrganizations] = useState(null); + const [relationship, setRelationships] = useState(null); + const [sessions, setSessions] = useState(null); - const handleFirstSelection = (value: string) => { - setFirstSelection(value) - setSecondSelection(null) // Reset second selection when first changes - } + const handleOrganizationSelection = (value: string) => { + setOrganizations(value) + setRelationships(null) // Reset second selection when first changes + } - const handleSecondSelection = (value: string) => { - setSecondSelection(value) - } + const handleRelationshipSelection = (value: string) => { + setRelationships(value); + setSessions(null); + } - return ( - - - Make Your Selection - - -
- - {firstSelection && ( - - )} - {secondSelection && ( -

- You selected: {firstSelection} and {secondSelection} -

- )} -
-
-
- ) + const handleSessionSelection = (value: string) => { + setSessions(value); + } + + return ( + + + Make Your Selection + + +
+ + url="/organizations" + params={{ userId }} + onChange={handleOrganizationSelection} + placeholder="Select an organization" + getOptionLabel={(org) => org.name} + getOptionValue={(org) => org.id.toString()} + /> + {organization && ( + + url={`/organizations/${organization}/coaching_relationships`} + params={{ organization }} + onChange={handleRelationshipSelection} + placeholder="Select coaching relationship" + getOptionLabel={(relationship) => relationship.coach_first_name} + getOptionValue={(relationship) => relationship.id.toString()} + /> + )} + {relationship && ( + + url="/coaching_sessions" + params={{ + coaching_relationship_id: relationship, + from_date: DateTime.now().minus({ month: 1 }).toISODate(), + to_Date: DateTime.now().plus({ month: 1 }).toISODate() + }} + onChange={handleRelationshipSelection} + placeholder="Select coaching session" + getOptionLabel={(session) => session.date.toString()} + getOptionValue={(session) => session.id.toString()} + /> + )} +
+
+
+ ) } \ No newline at end of file diff --git a/src/components/ui/dashboard/select-coaching-session.tsx b/src/components/ui/dashboard/select-coaching-session.tsx deleted file mode 100644 index d979517..0000000 --- a/src/components/ui/dashboard/select-coaching-session.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useState } from 'react'; -import { useApiData } from '@/hooks/use-api-data'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; - -interface Option { - value: string, - label: string -} - -interface SelectCoachingSessionProps { - url: string, - params?: Record, - onChange: (value: string) => void, - placeholder?: string -} - -export function SelectCoachingSession({ url, params = {}, onChange, placeholder = "Select an option" }: SelectCoachingSessionProps) { - const { data, isLoading, error } = useApiData(url, params); - const [value, setValue] = useState(''); - - const handleValueChange = (newValue: string) => { - setValue(newValue); - onChange(newValue); - } - - if (isLoading) return

Loading...

- if (error) return

Error: {error.message}

- - return ( - - ); -} diff --git a/src/hooks/use-api-data.ts b/src/hooks/use-api-data.ts index 1f61b1e..320658f 100644 --- a/src/hooks/use-api-data.ts +++ b/src/hooks/use-api-data.ts @@ -2,38 +2,64 @@ import useSWR from 'swr'; import { siteConfig } from '@/site.config'; interface FetcherOptions { - url: string - params?: Record + url: string, + method?: 'GET' | 'POST', + params?: Record, + // body?: Record } const baseUrl = siteConfig.url; -const fetcher = async ({ url, params }: FetcherOptions) => { - const queryString = new URLSearchParams(params).toString(); - const fullUrl = `${baseUrl}${url}${queryString ? `?${queryString}` : ''}`; +const fetcher = async ({ url, method = 'POST', params }: FetcherOptions) => { + const fullUrl = `${baseUrl}${url}`; console.log(fullUrl); - const response = await fetch(fullUrl); + const headers: HeadersInit = { + 'Content-Type': 'application/json', + 'X-Version': '0.0.1' + }; + + const fetchOptions: RequestInit = { + method, + headers, + credentials: 'include', + // ...(body != null && { body: JSON.stringify(body) }), + }; + console.log(JSON.stringify(fetchOptions)); + + const response = await fetch(fullUrl, fetchOptions); if (!response.ok) { - throw new Error('An error occcured while fetching the data.'); + const errorData = await response.json().catch(() => null); + throw new Error(errorData?.message || 'An error occurred while fetching the data.'); } - return response.json(); }; -export function useApiData(url: string, params: Record = {}) { - const { data, error, isLoading } = useSWR( - { url, params }, +export function useApiData( + url: string, + options: { + method?: 'GET' | 'POST' + params?: Record + body?: Record + } = {} +) { + const { method = 'POST', params = {}, body = {} || undefined } = options + + const { data, error, isLoading, mutate } = useSWR( + { url, method, params, body }, fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false, } - ); + ) + + console.log(data); return { data, isLoading, - error - }; -}; \ No newline at end of file + error, + mutate, + } +} \ No newline at end of file diff --git a/src/types/coaching_relationship_with_user_names.ts b/src/types/coaching_relationship_with_user_names.ts index cd5f619..f8489d3 100644 --- a/src/types/coaching_relationship_with_user_names.ts +++ b/src/types/coaching_relationship_with_user_names.ts @@ -7,60 +7,60 @@ export interface CoachingRelationshipWithUserNames { id: Id; coach_id: Id; coachee_id: Id; - coach_first_name: String; - coach_last_name: String; - coachee_first_name: String; - coachee_last_name: String; + coach_first_name: string; + coach_last_name: string; + coachee_first_name: string; + coachee_last_name: string; created_at: DateTime; updated_at: DateTime; } export function isCoachingRelationshipWithUserNames(value: unknown): value is CoachingRelationshipWithUserNames { - if (!value || typeof value !== "object") { - return false; - } - const object = value as Record; - - return ( - typeof object.id === "string" && - typeof object.coach_id === "string" && - typeof object.coachee_id === "string" && - typeof object.coach_first_name === "string" && - typeof object.coach_last_name === "string" && - typeof object.coachee_first_name === "string" && - typeof object.coachee_last_name === "string" && - typeof object.created_at === "string" && - typeof object.updated_at === "string" - ); + if (!value || typeof value !== "object") { + return false; } + const object = value as Record; + + return ( + typeof object.id === "string" && + typeof object.coach_id === "string" && + typeof object.coachee_id === "string" && + typeof object.coach_first_name === "string" && + typeof object.coach_last_name === "string" && + typeof object.coachee_first_name === "string" && + typeof object.coachee_last_name === "string" && + typeof object.created_at === "string" && + typeof object.updated_at === "string" + ); +} export function isCoachingRelationshipWithUserNamesArray(value: unknown): value is CoachingRelationshipWithUserNames[] { return Array.isArray(value) && value.every(isCoachingRelationshipWithUserNames); } export function defaultCoachingRelationshipWithUserNames(): CoachingRelationshipWithUserNames { - var now = DateTime.now(); - return { - id: "", - coach_id: "", - coachee_id: "", - coach_first_name: "", - coach_last_name: "", - coachee_first_name: "", - coachee_last_name: "", - created_at: now, - updated_at: now, - }; - } - - export function defaultCoachingRelationshipsWithUserNames(): CoachingRelationshipWithUserNames[] { - return [defaultCoachingRelationshipWithUserNames()]; - } - - export function coachingRelationshipWithUserNamesToString(relationship: CoachingRelationshipWithUserNames): string { - return JSON.stringify(relationship); - } - - export function coachingRelationshipsWithUserNamesToString(relationships: CoachingRelationshipWithUserNames[]): string { - return JSON.stringify(relationships); - } \ No newline at end of file + var now = DateTime.now(); + return { + id: "", + coach_id: "", + coachee_id: "", + coach_first_name: "", + coach_last_name: "", + coachee_first_name: "", + coachee_last_name: "", + created_at: now, + updated_at: now, + }; +} + +export function defaultCoachingRelationshipsWithUserNames(): CoachingRelationshipWithUserNames[] { + return [defaultCoachingRelationshipWithUserNames()]; +} + +export function coachingRelationshipWithUserNamesToString(relationship: CoachingRelationshipWithUserNames): string { + return JSON.stringify(relationship); +} + +export function coachingRelationshipsWithUserNamesToString(relationships: CoachingRelationshipWithUserNames[]): string { + return JSON.stringify(relationships); +} \ No newline at end of file From 6f5423fa3d82a43b02254f5604adb66ac1e6054d Mon Sep 17 00:00:00 2001 From: David Date: Tue, 1 Oct 2024 22:29:08 -0400 Subject: [PATCH 03/28] Clean up dynamic select component --- .../ui/dashboard/dynamic-api-select.tsx | 14 +++- .../ui/dashboard/join-coaching-session.tsx | 81 +++++++++++-------- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/components/ui/dashboard/dynamic-api-select.tsx b/src/components/ui/dashboard/dynamic-api-select.tsx index fa13877..3d02955 100644 --- a/src/components/ui/dashboard/dynamic-api-select.tsx +++ b/src/components/ui/dashboard/dynamic-api-select.tsx @@ -10,6 +10,8 @@ interface DynamicApiSelectProps { placeholder?: string; getOptionLabel: (item: T) => string; getOptionValue: (item: T) => string; + elementId: string; + groupByDate?: boolean; } interface ApiResponse { @@ -24,7 +26,9 @@ export function DynamicApiSelect({ onChange, placeholder = "Select an organization", getOptionLabel, - getOptionValue + getOptionValue, + elementId, + groupByDate }: DynamicApiSelectProps) { const { data: response, isLoading, error } = useApiData>(url, { method, params }); const [value, setValue] = useState(''); @@ -41,11 +45,13 @@ export function DynamicApiSelect({ const items = response.data; return ( - + - + {items.map((item, index) => ( {getOptionLabel(item)} diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 4e79293..c654834 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -1,75 +1,90 @@ -import React, { useState } from 'react'; +import React, { MouseEventHandler, useState } from 'react'; import { Id } from "@/types/general"; +import Link from 'next/link'; import { DynamicApiSelect } from './dynamic-api-select'; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Organization } from '@/types/organization'; import { CoachingRelationshipWithUserNames } from '@/types/coaching_relationship_with_user_names'; import { CoachingSession } from '@/types/coaching-session'; import { DateTime } from 'ts-luxon'; +import { Label } from "@/components/ui/label"; +import { Button } from '../button'; + export interface CoachingSessionCardProps { userId: Id; } export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps) { - const [organization, setOrganizations] = useState(null); - const [relationship, setRelationships] = useState(null); - const [sessions, setSessions] = useState(null); - - const handleOrganizationSelection = (value: string) => { - setOrganizations(value) - setRelationships(null) // Reset second selection when first changes - } - - const handleRelationshipSelection = (value: string) => { - setRelationships(value); - setSessions(null); - } - - const handleSessionSelection = (value: string) => { - setSessions(value); - } + const [organizationId, setOrganizationId] = useState(null); + const [relationshipId, setRelationshipId] = useState(null); + const [sessionId, setSessionId] = useState(null); return ( - Make Your Selection + Join a Coaching Session - -
+ +
+ + url="/organizations" params={{ userId }} - onChange={handleOrganizationSelection} + onChange={setOrganizationId} placeholder="Select an organization" getOptionLabel={(org) => org.name} getOptionValue={(org) => org.id.toString()} + elementId='organization-selector' /> - {organization && ( +
+ {organizationId && ( +
+ + - url={`/organizations/${organization}/coaching_relationships`} - params={{ organization }} - onChange={handleRelationshipSelection} + url={`/organizations/${organizationId}/coaching_relationships`} + params={{ organizationId }} + onChange={setRelationshipId} placeholder="Select coaching relationship" - getOptionLabel={(relationship) => relationship.coach_first_name} + getOptionLabel={ + (relationship) => + `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}` + } getOptionValue={(relationship) => relationship.id.toString()} + elementId='relationship-selector' /> - )} - {relationship && ( +
+ )} + {relationshipId && ( +
+ + url="/coaching_sessions" params={{ - coaching_relationship_id: relationship, + coaching_relationship_id: relationshipId, from_date: DateTime.now().minus({ month: 1 }).toISODate(), to_Date: DateTime.now().plus({ month: 1 }).toISODate() }} - onChange={handleRelationshipSelection} + onChange={setSessionId} placeholder="Select coaching session" getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} + elementId='session-selector' /> - )} -
+
+ )} + {sessionId && ( +
+ +
+ )}
) From 02278df49f9fe654ce7c296da885eef5d8b70773 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 1 Oct 2024 22:35:16 -0400 Subject: [PATCH 04/28] remove comments --- src/app/dashboard/page.tsx | 1 - src/hooks/use-api-data.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 503b1b9..3062dc3 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -8,7 +8,6 @@ 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"; diff --git a/src/hooks/use-api-data.ts b/src/hooks/use-api-data.ts index 320658f..03387da 100644 --- a/src/hooks/use-api-data.ts +++ b/src/hooks/use-api-data.ts @@ -23,7 +23,6 @@ const fetcher = async ({ url, method = 'POST', params }: FetcherOptions) => { method, headers, credentials: 'include', - // ...(body != null && { body: JSON.stringify(body) }), }; console.log(JSON.stringify(fetchOptions)); From f413887f3d61eb110c78fa6818e1a5373a980669 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 14:58:46 -0400 Subject: [PATCH 05/28] Fix session separation and date-time rendering --- .../ui/dashboard/dynamic-api-select.tsx | 75 ++++++++++++++----- .../ui/dashboard/join-coaching-session.tsx | 6 +- src/hooks/use-api-data.ts | 6 +- 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/components/ui/dashboard/dynamic-api-select.tsx b/src/components/ui/dashboard/dynamic-api-select.tsx index 3d02955..12884b8 100644 --- a/src/components/ui/dashboard/dynamic-api-select.tsx +++ b/src/components/ui/dashboard/dynamic-api-select.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import { useApiData } from '@/hooks/use-api-data'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { CoachingSession, isCoachingSession } from '@/types/coaching-session'; +import { DateTime } from 'ts-luxon'; interface DynamicApiSelectProps { url: string; @@ -24,11 +26,11 @@ export function DynamicApiSelect({ method = 'GET', params = {}, onChange, - placeholder = "Select an organization", + placeholder = "Select an option", getOptionLabel, getOptionValue, elementId, - groupByDate + groupByDate = false }: DynamicApiSelectProps) { const { data: response, isLoading, error } = useApiData>(url, { method, params }); const [value, setValue] = useState(''); @@ -38,26 +40,63 @@ export function DynamicApiSelect({ onChange(newValue); } - if (isLoading) return

Loading...

- if (error) return

Error: {error.message}

- if (!response || response.status_code !== 200) return

Error: Invalid response

+ if (isLoading) return

Loading...

; + if (error) return

Error: {error.message}

; + if (!response || response.status_code !== 200) return

Error: Invalid response

; const items = response.data; - return ( - + + + + {groupByDate && coachingSessions.length > 0 + ? renderCoachingSessions(coachingSessions) + : renderOtherItems(items)} ); -} +} \ No newline at end of file diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index c654834..113d3fb 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -73,6 +73,7 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} elementId='session-selector' + groupByDate={true} /> )} @@ -88,4 +89,7 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps ) -} \ No newline at end of file +} + +// godot +//asesprint diff --git a/src/hooks/use-api-data.ts b/src/hooks/use-api-data.ts index 03387da..fdd4298 100644 --- a/src/hooks/use-api-data.ts +++ b/src/hooks/use-api-data.ts @@ -12,7 +12,6 @@ const baseUrl = siteConfig.url; const fetcher = async ({ url, method = 'POST', params }: FetcherOptions) => { const fullUrl = `${baseUrl}${url}`; - console.log(fullUrl); const headers: HeadersInit = { 'Content-Type': 'application/json', @@ -24,7 +23,6 @@ const fetcher = async ({ url, method = 'POST', params }: FetcherOptions) => { headers, credentials: 'include', }; - console.log(JSON.stringify(fetchOptions)); const response = await fetch(fullUrl, fetchOptions); if (!response.ok) { @@ -42,7 +40,7 @@ export function useApiData( body?: Record } = {} ) { - const { method = 'POST', params = {}, body = {} || undefined } = options + const { method = 'POST', params = {}, body = {} } = options const { data, error, isLoading, mutate } = useSWR( { url, method, params, body }, @@ -53,8 +51,6 @@ export function useApiData( } ) - console.log(data); - return { data, isLoading, From 2821e6287f92aa69c17419511a695407450d0c3b Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 15:45:28 -0400 Subject: [PATCH 06/28] Use zustand to share ids across contexts --- .../ui/dashboard/join-coaching-session.tsx | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 113d3fb..9351377 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -1,6 +1,6 @@ -import React, { MouseEventHandler, useState } from 'react'; +import React, { useState } from 'react'; +import { useAppStateStore } from '@/lib/providers/app-state-store-provider'; import { Id } from "@/types/general"; -import Link from 'next/link'; import { DynamicApiSelect } from './dynamic-api-select'; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Organization } from '@/types/organization'; @@ -9,6 +9,7 @@ import { CoachingSession } from '@/types/coaching-session'; import { DateTime } from 'ts-luxon'; import { Label } from "@/components/ui/label"; import { Button } from '../button'; +import Link from 'next/link'; export interface CoachingSessionCardProps { @@ -16,9 +17,33 @@ export interface CoachingSessionCardProps { } export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps) { - const [organizationId, setOrganizationId] = useState(null); - const [relationshipId, setRelationshipId] = useState(null); - const [sessionId, setSessionId] = useState(null); + const setOrganizationId = useAppStateStore(state => state.setOrganizationId); + const setRelationshipId = useAppStateStore(state => state.setRelationshipId); + const setCoachingSessionId = useAppStateStore(state => state.setCoachingSessionId); + const [organizationId, setOrganization] = useState(null); + const [relationshipId, setRelationship] = useState(null); + const [sessionId, setSessions] = useState(null); + const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); + const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); + + const handleOrganizationSelection = (value: string) => { + setOrganization(value); + setRelationship(null); + setSessions(null); + setOrganizationId(value); + } + + const handleRelationshipSelection = (value: string) => { + setRelationship(value); + setSessions(null); + setRelationshipId(value); + } + + const handleSessionSelection = (value: string) => { + setSessions(value); + setCoachingSessionId(value); + } + return ( @@ -32,7 +57,7 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps url="/organizations" params={{ userId }} - onChange={setOrganizationId} + onChange={handleOrganizationSelection} placeholder="Select an organization" getOptionLabel={(org) => org.name} getOptionValue={(org) => org.id.toString()} @@ -46,7 +71,7 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps url={`/organizations/${organizationId}/coaching_relationships`} params={{ organizationId }} - onChange={setRelationshipId} + onChange={handleRelationshipSelection} placeholder="Select coaching relationship" getOptionLabel={ (relationship) => @@ -65,10 +90,10 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps url="/coaching_sessions" params={{ coaching_relationship_id: relationshipId, - from_date: DateTime.now().minus({ month: 1 }).toISODate(), - to_Date: DateTime.now().plus({ month: 1 }).toISODate() + from_date: FROM_DATE, + to_Date: TO_DATE }} - onChange={setSessionId} + onChange={handleSessionSelection} placeholder="Select coaching session" getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} @@ -90,6 +115,3 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps ) } - -// godot -//asesprint From bdc92ec44933d667a3f8fcd18ebd53bd0006e0c3 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 8 Oct 2024 23:15:20 -0400 Subject: [PATCH 07/28] Format dynamic select component with Prettier --- .../ui/dashboard/dynamic-api-select.tsx | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/src/components/ui/dashboard/dynamic-api-select.tsx b/src/components/ui/dashboard/dynamic-api-select.tsx index 12884b8..f47596d 100644 --- a/src/components/ui/dashboard/dynamic-api-select.tsx +++ b/src/components/ui/dashboard/dynamic-api-select.tsx @@ -1,12 +1,20 @@ -import React, { useState } from 'react'; -import { useApiData } from '@/hooks/use-api-data'; -import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { CoachingSession, isCoachingSession } from '@/types/coaching-session'; -import { DateTime } from 'ts-luxon'; +import React, { useState } from "react"; +import { useApiData } from "@/hooks/use-api-data"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { CoachingSession, isCoachingSession } from "@/types/coaching-session"; +import { DateTime } from "ts-luxon"; interface DynamicApiSelectProps { url: string; - method?: 'GET' | 'POST'; + method?: "GET" | "POST"; params?: Record; onChange: (value: string) => void; placeholder?: string; @@ -23,51 +31,76 @@ interface ApiResponse { export function DynamicApiSelect({ url, - method = 'GET', + method = "GET", params = {}, onChange, placeholder = "Select an option", getOptionLabel, getOptionValue, elementId, - groupByDate = false + groupByDate = false, }: DynamicApiSelectProps) { - const { data: response, isLoading, error } = useApiData>(url, { method, params }); - const [value, setValue] = useState(''); + const { + data: response, + isLoading, + error, + } = useApiData>(url, { method, params }); + const [value, setValue] = useState(""); const handleValueChange = (newValue: string) => { setValue(newValue); onChange(newValue); - } + }; if (isLoading) return

Loading...

; if (error) return

Error: {error.message}

; - if (!response || response.status_code !== 200) return

Error: Invalid response

; + if (!response || response.status_code !== 200) + return

Error: Invalid response

; const items = response.data; - const renderSessions = (sessions: CoachingSession[], label: string, filterFn: (session: CoachingSession) => boolean) => { + const renderSessions = ( + sessions: CoachingSession[], + label: string, + filterFn: (session: CoachingSession) => boolean + ) => { const filteredSessions = sessions.filter(filterFn); - return filteredSessions.length > 0 && ( - - {label} - {filteredSessions.map(session => ( - - {DateTime.fromISO(session.date.toString()).toLocaleString(DateTime.DATETIME_FULL)} - - ))} - + return ( + filteredSessions.length > 0 && ( + + {label} + {filteredSessions.map((session) => ( + + {DateTime.fromISO(session.date.toString()).toLocaleString( + DateTime.DATETIME_FULL + )} + + ))} + + ) ); }; const renderCoachingSessions = (sessions: CoachingSession[]) => ( {sessions.length === 0 ? ( - None found + + None found + ) : ( <> - {renderSessions(sessions, 'Previous Sessions', session => DateTime.fromISO(session.date.toString()) < DateTime.now())} - {renderSessions(sessions, 'Upcoming Sessions', session => DateTime.fromISO(session.date.toString()) >= DateTime.now())} + {renderSessions( + sessions, + "Previous Sessions", + (session) => + DateTime.fromISO(session.date.toString()) < DateTime.now() + )} + {renderSessions( + sessions, + "Upcoming Sessions", + (session) => + DateTime.fromISO(session.date.toString()) >= DateTime.now() + )} )} @@ -76,7 +109,9 @@ export function DynamicApiSelect({ const renderOtherItems = (items: T[]) => ( {items.length === 0 ? ( - None found + + None found + ) : ( items.map((item, index) => ( @@ -87,7 +122,9 @@ export function DynamicApiSelect({ ); - const coachingSessions = groupByDate ? items.filter(isCoachingSession) as CoachingSession[] : []; + const coachingSessions = groupByDate + ? (items.filter(isCoachingSession) as CoachingSession[]) + : []; return ( ); -} \ No newline at end of file +} From 766bd8e5070f7891eae7939f9959c118d6d98ec8 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 11 Oct 2024 22:59:15 -0400 Subject: [PATCH 08/28] Fix css conflict on coaching-session page --- src/app/coaching-sessions/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 3fdfaf5..1ce7c2a 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -145,7 +145,7 @@ export default function CoachingSessionsPage() {
- + Notes Console From 4889b1e927dd36fb13b5b9ec5e5de2c2c55d8f12 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 12 Oct 2024 00:16:36 -0400 Subject: [PATCH 09/28] Use dynamic-api-selector on session page --- src/app/coaching-sessions/[id]/page.tsx | 58 ++++++++++++++++++------- src/site.config.ts | 2 +- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 1ce7c2a..0d0d403 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -9,8 +9,6 @@ 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, useState } from "react"; import { @@ -33,6 +31,9 @@ import { import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { LockClosedIcon, SymbolIcon } from "@radix-ui/react-icons"; +import { DynamicApiSelect } from "@/components/ui/dashboard/dynamic-api-select"; +import { DateTime } from "ts-luxon"; +import { CoachingSession } from "@/types/coaching-session"; // export const metadata: Metadata = { // title: "Coaching Session", @@ -44,19 +45,21 @@ export default function CoachingSessionsPage() { const [note, setNote] = useState(""); const [syncStatus, setSyncStatus] = useState(""); const { userId } = useAuthStore((state) => ({ userId: state.userId })); - const { coachingSession, coachingRelationship } = useAppStateStore( + const { coachingSessionId, relationshipId } = useAppStateStore( (state) => state ); + //@TODO: create a shared static function for this. + const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); + const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); + 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; } - await fetchNotesByCoachingSessionId(coachingSession.id) + await fetchNotesByCoachingSessionId(coachingSessionId) .then((notes) => { const note = notes[0]; if (notes.length > 0) { @@ -65,7 +68,7 @@ export default function CoachingSessionsPage() { setNote(note.body); setSyncStatus("Notes refreshed"); } else { - console.trace("No Notes associated with this coachingSession.id"); + console.trace("No Notes associated with this coachingSessionId"); } }) .catch((err) => { @@ -77,13 +80,13 @@ export default function CoachingSessionsPage() { useEffect(() => { fetchNote(); - }, [coachingSession.id, noteId]); + }, [coachingSessionId, noteId]); const handleInputChange = (value: string) => { setNote(value); - if (noteId && coachingSession.id && userId) { - updateNote(noteId, coachingSession.id, userId, value) + if (noteId && coachingSessionId && userId) { + updateNote(noteId, coachingSessionId, userId, value) .then((note) => { console.trace("Updated Note: " + noteToString(note)); setSyncStatus("All changes saved"); @@ -92,8 +95,8 @@ export default function CoachingSessionsPage() { setSyncStatus("Failed to save changes"); console.error("Failed to update Note: " + err); }); - } else if (!noteId && coachingSession.id && userId) { - createNote(coachingSession.id, userId, value) + } else if (!noteId && coachingSessionId && userId) { + createNote(coachingSessionId, userId, value) .then((note) => { console.trace("Newly created Note: " + noteToString(note)); setNoteId(note.id); @@ -105,7 +108,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." ); } }; @@ -118,6 +121,11 @@ export default function CoachingSessionsPage() { document.title = sessionTitle; }; + const handleSessionSelection = (value: string) => { + // console.log("Selected new session: " + value); + fetchNote(); + }; + return ( <>
@@ -128,7 +136,25 @@ export default function CoachingSessionsPage() { onRender={handleTitleRender} >
- + {/* */} + {relationshipId && ( +
+ + url="/coaching_sessions" + params={{ + coaching_relationship_id: relationshipId, + from_date: FROM_DATE, + to_Date: TO_DATE, + }} + onChange={handleSessionSelection} + placeholder="Select coaching session" + getOptionLabel={(session) => session.date.toString()} + getOptionValue={(session) => session.id.toString()} + elementId="session-selector" + groupByDate={true} + /> +
+ )} {/* Hidden for MVP */}
diff --git a/src/site.config.ts b/src/site.config.ts index 79d560e..4465a72 100644 --- a/src/site.config.ts +++ b/src/site.config.ts @@ -1,6 +1,6 @@ export const siteConfig = { name: "Refactor Coaching & Mentoring", - url: "https://refactorcoach.com", + url: "http://localhost:4000", ogImage: "https://ui.shadcn.com/og.jpg", locale: "us", titleStyle: SessionTitleStyle.CoachFirstCoacheeFirstDate, From 27239a95d0f66251727136ef0cd4b8d0f77806c9 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 15 Oct 2024 23:49:02 -0400 Subject: [PATCH 10/28] Add coaching relationship to state store on select --- .../ui/dashboard/join-coaching-session.tsx | 88 +++++++++++-------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 9351377..8dc828b 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -1,25 +1,37 @@ -import React, { useState } from 'react'; -import { useAppStateStore } from '@/lib/providers/app-state-store-provider'; +import React, { useState } from "react"; +import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; import { Id } from "@/types/general"; -import { DynamicApiSelect } from './dynamic-api-select'; +import { DynamicApiSelect } from "./dynamic-api-select"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Organization } from '@/types/organization'; -import { CoachingRelationshipWithUserNames } from '@/types/coaching_relationship_with_user_names'; -import { CoachingSession } from '@/types/coaching-session'; -import { DateTime } from 'ts-luxon'; +import { Organization } from "@/types/organization"; +import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names"; +import { CoachingSession } from "@/types/coaching-session"; +import { DateTime } from "ts-luxon"; import { Label } from "@/components/ui/label"; -import { Button } from '../button'; -import Link from 'next/link'; - +import { Button } from "../button"; +import Link from "next/link"; +import { fetchCoachingRelationshipWithUserNames } from "@/lib/api/coaching-relationships"; export interface CoachingSessionCardProps { userId: Id; } -export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps) { - const setOrganizationId = useAppStateStore(state => state.setOrganizationId); - const setRelationshipId = useAppStateStore(state => state.setRelationshipId); - const setCoachingSessionId = useAppStateStore(state => state.setCoachingSessionId); +export function JoinCoachingSession({ + userId: userId, +}: CoachingSessionCardProps) { + const setOrganizationId = useAppStateStore( + (state) => state.setOrganizationId + ); + const setRelationshipId = useAppStateStore( + (state) => state.setRelationshipId + ); + const setCoachingSessionId = useAppStateStore( + (state) => state.setCoachingSessionId + ); + + const setCoachingRelationship = useAppStateStore( + (state) => state.setCoachingRelationship + ); const [organizationId, setOrganization] = useState(null); const [relationshipId, setRelationship] = useState(null); const [sessionId, setSessions] = useState(null); @@ -31,28 +43,35 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps setRelationship(null); setSessions(null); setOrganizationId(value); - } + }; - const handleRelationshipSelection = (value: string) => { + const handleRelationshipSelection = (value: Id) => { setRelationship(value); setSessions(null); setRelationshipId(value); - } + if (organizationId) { + fetchCoachingRelationshipWithUserNames(organizationId, value).then( + (relationship) => { + setRelationshipId(relationship.id); + setCoachingRelationship(relationship); + } + ); + } + }; const handleSessionSelection = (value: string) => { setSessions(value); setCoachingSessionId(value); - } - + }; return ( Join a Coaching Session - +
- + url="/organizations" @@ -61,57 +80,54 @@ export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps placeholder="Select an organization" getOptionLabel={(org) => org.name} getOptionValue={(org) => org.id.toString()} - elementId='organization-selector' + elementId="organization-selector" />
{organizationId && (
- + url={`/organizations/${organizationId}/coaching_relationships`} params={{ organizationId }} onChange={handleRelationshipSelection} placeholder="Select coaching relationship" - getOptionLabel={ - (relationship) => - `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}` + getOptionLabel={(relationship) => + `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}` } getOptionValue={(relationship) => relationship.id.toString()} - elementId='relationship-selector' + elementId="relationship-selector" />
)} {relationshipId && (
- + url="/coaching_sessions" params={{ coaching_relationship_id: relationshipId, from_date: FROM_DATE, - to_Date: TO_DATE + to_Date: TO_DATE, }} onChange={handleSessionSelection} placeholder="Select coaching session" getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} - elementId='session-selector' + elementId="session-selector" groupByDate={true} />
)} {sessionId && ( -
-
)}
- ) + ); } From 9f5d37b98502c41a5935ba7233b6befe03558876 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 25 Oct 2024 14:46:03 -0400 Subject: [PATCH 11/28] Load full fetched objects into state --- .../ui/dashboard/join-coaching-session.tsx | 53 ++++++++++++------- src/lib/stores/app-state-store.ts | 12 +++-- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 8dc828b..18c290f 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -11,6 +11,8 @@ import { Label } from "@/components/ui/label"; import { Button } from "../button"; import Link from "next/link"; import { fetchCoachingRelationshipWithUserNames } from "@/lib/api/coaching-relationships"; +import { fetchOrganization } from "@/lib/api/organizations"; +import { fetchCoachingSessions } from "@/lib/api/coaching-sessions"; export interface CoachingSessionCardProps { userId: Id; @@ -19,37 +21,41 @@ export interface CoachingSessionCardProps { export function JoinCoachingSession({ userId: userId, }: CoachingSessionCardProps) { + const setOrganization = useAppStateStore((state) => state.setOrganization); const setOrganizationId = useAppStateStore( (state) => state.setOrganizationId ); - const setRelationshipId = useAppStateStore( - (state) => state.setRelationshipId - ); - const setCoachingSessionId = useAppStateStore( - (state) => state.setCoachingSessionId - ); + let organizationId = useAppStateStore((state) => state.organizationId); const setCoachingRelationship = useAppStateStore( (state) => state.setCoachingRelationship ); - const [organizationId, setOrganization] = useState(null); - const [relationshipId, setRelationship] = useState(null); - const [sessionId, setSessions] = useState(null); + const setRelationshipId = useAppStateStore( + (state) => state.setRelationshipId + ); + let relationshipId = useAppStateStore((state) => state.relationshipId); + + const setSession = useAppStateStore((state) => state.setCoachingSession); + const setSessionId = useAppStateStore((state) => state.setCoachingSessionId); + let sessionId = useAppStateStore((state) => state.setCoachingSessionId); + const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); - const handleOrganizationSelection = (value: string) => { - setOrganization(value); - setRelationship(null); - setSessions(null); + //@TODO: pass selected organization from organization array + const handleOrganizationSelection = (value: Id) => { setOrganizationId(value); + if (value) { + fetchOrganization(value).then(([organization]) => { + organizationId = setOrganization(organization); + }); + } }; + //@TODO: pass selected relationship from relationship array const handleRelationshipSelection = (value: Id) => { - setRelationship(value); - setSessions(null); setRelationshipId(value); - if (organizationId) { + if (value && organizationId) { fetchCoachingRelationshipWithUserNames(organizationId, value).then( (relationship) => { setRelationshipId(relationship.id); @@ -59,9 +65,18 @@ export function JoinCoachingSession({ } }; - const handleSessionSelection = (value: string) => { - setSessions(value); - setCoachingSessionId(value); + 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); + } + }); + } }; return ( diff --git a/src/lib/stores/app-state-store.ts b/src/lib/stores/app-state-store.ts index 8ed892e..bc59dab 100644 --- a/src/lib/stores/app-state-store.ts +++ b/src/lib/stores/app-state-store.ts @@ -21,14 +21,14 @@ interface AppState { } interface AppStateActions { - setOrganizationId: (organizationId: Id) => void; + setOrganizationId: (organizationId: Id) => [string]; setRelationshipId: (relationshipId: Id) => void; setCoachingSessionId: (coachingSessionId: Id) => void; - setOrganization: (organization: Organization) => void; - setCoachingSession: (coachingSession: CoachingSession) => void; + setOrganization: (organization: Organization) => Id; + setCoachingSession: (coachingSession: CoachingSession) => Id; setCoachingRelationship: ( coachingRelationship: CoachingRelationshipWithUserNames - ) => void; + ) => Id; reset(): void; } @@ -52,6 +52,7 @@ export const createAppStateStore = (initState: AppState = defaultInitState) => { setOrganizationId: (organizationId) => { set({ organizationId }); + return [organizationId]; }, setRelationshipId: (relationshipId) => { set({ relationshipId }); @@ -61,12 +62,15 @@ export const createAppStateStore = (initState: AppState = defaultInitState) => { }, setOrganization: (organization) => { set({ organization }); + return organization.id; }, setCoachingSession: (coachingSession) => { set({ coachingSession }); + return coachingSession.id; }, setCoachingRelationship: (coachingRelationship) => { set({ coachingRelationship }); + return coachingRelationship.id; }, reset(): void { set(defaultInitState); From d93108a1c63b7020b6a486684de6dd4ea2e936e2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 25 Oct 2024 17:41:54 -0400 Subject: [PATCH 12/28] Run npm i --- package-lock.json | 338 +++++++++++++++++----------------------------- 1 file changed, 125 insertions(+), 213 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82e7e9b..c4e23fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,9 +82,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", - "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -531,6 +531,8 @@ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -571,6 +573,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -591,6 +594,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -611,6 +615,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -631,6 +636,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -651,6 +657,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -671,6 +678,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -691,6 +699,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -711,6 +720,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -731,6 +741,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -751,6 +762,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -771,6 +783,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -791,6 +804,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3300,6 +3314,8 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -3347,13 +3363,12 @@ } }, "node_modules/cmdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz", - "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.1.tgz", + "integrity": "sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==", "license": "MIT", "dependencies": { - "@radix-ui/react-dialog": "1.0.5", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-dialog": "1.0.0" }, "peerDependencies": { "react": "^18.0.0", @@ -3361,348 +3376,234 @@ } }, "node_modules/cmdk/node_modules/@radix-ui/primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", - "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", + "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" } }, "node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", + "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-dialog": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", - "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz", + "integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.0", + "@radix-ui/react-focus-guards": "1.0.0", + "@radix-ui/react-focus-scope": "1.0.0", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-portal": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-slot": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" + "react-remove-scroll": "2.5.4" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", - "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz", + "integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-escape-keydown": "1.0.3" + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-escape-keydown": "1.0.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", - "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", + "integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", - "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz", + "integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1" + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-id": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", - "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", + "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" + "@radix-ui/react-use-layout-effect": "1.0.0" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-portal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", - "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz", + "integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-primitive": "1.0.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-presence": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", - "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", + "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz", + "integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" + "@radix-ui/react-slot": "1.0.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz", + "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" + "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", + "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", - "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz", + "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" + "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", - "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz", + "integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" + "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", + "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { - "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, "node_modules/cmdk/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz", + "integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.3", @@ -3999,6 +3900,8 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "license": "Apache-2.0", + "optional": true, + "peer": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -5297,7 +5200,9 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -6061,12 +5966,12 @@ "license": "ISC" }, "node_modules/lucide-react": { - "version": "0.453.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.453.0.tgz", - "integrity": "sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==", + "version": "0.314.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.314.0.tgz", + "integrity": "sha512-c2zOW7TOyKxPCaSs1og2lFdoI3SR4iii3yrZJU2Zpdc78nOw4fUmrcFNultaCiwZcr4CyiKdzjMdvKNlBBk+UQ==", "license": "ISC", "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, "node_modules/markdown-it": { @@ -6261,13 +6166,14 @@ } }, "node_modules/next-themes": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", - "integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", "license": "MIT", "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" + "next": "*", + "react": "*", + "react-dom": "*" } }, "node_modules/next/node_modules/postcss": { @@ -6302,7 +6208,9 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/node-releases": { "version": "2.0.18", @@ -7211,6 +7119,8 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 14.16.0" }, @@ -7423,6 +7333,8 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.2.tgz", "integrity": "sha512-9wXY8cGBlUmoUoT+vwOZOFCiS+naiWVjqlreN9ar9PudXbGwlMTFwCR5K9kB4dFumJ6ib98wZyAObJKsWf1nAA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", From 96a4d59064353e2b52b8daf61ed76a96a350298e Mon Sep 17 00:00:00 2001 From: David Date: Fri, 25 Oct 2024 17:45:07 -0400 Subject: [PATCH 13/28] Update coaching session with latest --- src/app/coaching-sessions/[id]/page.tsx | 97 ++++++++++++------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index fea151d..f3c7c5b 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -9,6 +9,8 @@ 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 { @@ -30,9 +32,10 @@ import { import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { LockClosedIcon, SymbolIcon } from "@radix-ui/react-icons"; -import { DynamicApiSelect } from "@/components/ui/dashboard/dynamic-api-select"; -import { DateTime } from "ts-luxon"; -import { CoachingSession } from "@/types/coaching-session"; +import { + CoachingNotes, + EditorRef, +} from "@/components/ui/coaching-sessions/coaching-notes"; // export const metadata: Metadata = { // title: "Coaching Session", @@ -43,17 +46,15 @@ export default function CoachingSessionsPage() { const [note, setNote] = useState(defaultNote()); const [syncStatus, setSyncStatus] = useState(""); const { userId } = useAuthStore((state) => ({ userId: state.userId })); - const { coachingSessionId, relationshipId } = useAppStateStore( - (state) => state - ); - - //@TODO: create a shared static function for this. - const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); - const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); + const { coachingSession } = useAppStateStore((state) => state); + const [isLoading, setIsLoading] = useState(false); + const editorRef = useRef(null); async function fetchNote() { - if (!coachingSessionId) { - console.error("Failed to fetch Note since coachingSessionId is not set."); + if (!coachingSession.id) { + console.error( + "Failed to fetch Note since coachingSession.id is not set." + ); return; } if (isLoading) { @@ -64,7 +65,7 @@ export default function CoachingSessionsPage() { setIsLoading(true); - await fetchNotesByCoachingSessionId(coachingSessionId) + await fetchNotesByCoachingSessionId(coachingSession.id) .then((notes) => { const note = notes[0]; if (notes.length > 0) { @@ -74,7 +75,10 @@ export default function CoachingSessionsPage() { setSyncStatus("Notes refreshed"); setEditorFocussed(); } else { - console.trace("No Notes associated with this coachingSessionId"); + console.trace( + "No Notes associated with this coachingSession.id: " + + coachingSession.id + ); } }) .catch((err) => { @@ -91,27 +95,41 @@ export default function CoachingSessionsPage() { useEffect(() => { fetchNote(); - }, [coachingSessionId, noteId]); + }, [coachingSession.id, isLoading]); + + const setEditorContent = (content: string) => { + editorRef.current?.setContent(`${content}`); + }; const setEditorFocussed = () => { editorRef.current?.setFocussed(); }; - if (noteId && coachingSessionId && userId) { - updateNote(noteId, coachingSessionId, userId, value) - .then((note) => { - console.trace("Updated Note: " + noteToString(note)); + const handleOnChange = (value: string) => { + console.debug("isLoading (before update/create): " + isLoading); + console.debug( + "coachingSession.id (before update/create): " + coachingSession.id + ); + 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) + .then((updatedNote) => { + setNote(updatedNote); + console.trace("Updated Note: " + noteToString(updatedNote)); setSyncStatus("All changes saved"); }) .catch((err) => { setSyncStatus("Failed to save changes"); console.error("Failed to update Note: " + err); }); - } else if (!noteId && coachingSessionId && userId) { - createNote(coachingSessionId, userId, value) - .then((note) => { - console.trace("Newly created Note: " + noteToString(note)); - setNoteId(note.id); + } else if (!isLoading && !note.id && coachingSession.id && userId) { + createNote(coachingSession.id, userId, value) + .then((createdNote) => { + setNote(createdNote); + console.trace("Newly created Note: " + noteToString(createdNote)); setSyncStatus("All changes saved"); }) .catch((err) => { @@ -120,7 +138,7 @@ export default function CoachingSessionsPage() { }); } else { console.error( - "Could not update or create a Note since coachingSessionId or userId are not set." + "Could not update or create a Note since coachingSession.id or userId are not set." ); } }; @@ -133,11 +151,6 @@ export default function CoachingSessionsPage() { document.title = sessionTitle; }; - const handleSessionSelection = (value: string) => { - // console.log("Selected new session: " + value); - fetchNote(); - }; - return (
@@ -148,25 +161,7 @@ export default function CoachingSessionsPage() { onRender={handleTitleRender} >
- {/* */} - {relationshipId && ( -
- - url="/coaching_sessions" - params={{ - coaching_relationship_id: relationshipId, - from_date: FROM_DATE, - to_Date: TO_DATE, - }} - onChange={handleSessionSelection} - placeholder="Select coaching session" - getOptionLabel={(session) => session.date.toString()} - getOptionValue={(session) => session.id.toString()} - elementId="session-selector" - groupByDate={true} - /> -
- )} + {/* Hidden for MVP */}
@@ -182,8 +177,8 @@ export default function CoachingSessionsPage() {
- - + + Notes Console From 1607fbdb378cd4d9a721ae152dde6fc7edb5eb5c Mon Sep 17 00:00:00 2001 From: David Date: Fri, 25 Oct 2024 21:07:39 -0400 Subject: [PATCH 14/28] Duplicate session selection component and logic - yuck --- src/app/coaching-sessions/[id]/page.tsx | 50 +++++++++++++++---- .../ui/dashboard/join-coaching-session.tsx | 1 + 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index f3c7c5b..632b400 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -8,9 +8,6 @@ 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 { @@ -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", @@ -151,6 +153,27 @@ 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 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); + } + }); + } + }; + return (
@@ -160,12 +183,21 @@ export default function CoachingSessionsPage() { style={siteConfig.titleStyle} onRender={handleTitleRender} > -
- - {/* Hidden for MVP */} -
- -
+
+ + url="/coaching_sessions" + params={{ + coaching_relationship_id: relationshipId, + from_date: FROM_DATE, + to_Date: TO_DATE, + }} + onChange={handleSessionSelection} + placeholder="Select coaching session" + getOptionLabel={(session) => session.date.toString()} + getOptionValue={(session) => session.id.toString()} + elementId="session-selector" + groupByDate={true} + />
diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 18c290f..9011a83 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -39,6 +39,7 @@ export function JoinCoachingSession({ const setSessionId = useAppStateStore((state) => state.setCoachingSessionId); let sessionId = useAppStateStore((state) => state.setCoachingSessionId); + //@TODO: abstract to state or utility function (apply to preset component) const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); From c4206d20605ae060d402b51d15b467a80881ffe9 Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Fri, 25 Oct 2024 20:24:52 -0500 Subject: [PATCH 15/28] Make sure a proper CoachingSession Id gets passed to the coaching session page. --- .../ui/dashboard/join-coaching-session.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 9011a83..6058009 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -35,9 +35,11 @@ export function JoinCoachingSession({ ); let relationshipId = useAppStateStore((state) => state.relationshipId); - const setSession = useAppStateStore((state) => state.setCoachingSession); + const { coachingSession, setCoachingSession } = useAppStateStore((state) => ({ + coachingSession: state.coachingSession, + setCoachingSession: state.setCoachingSession, + })); const setSessionId = useAppStateStore((state) => state.setCoachingSessionId); - let sessionId = useAppStateStore((state) => state.setCoachingSessionId); //@TODO: abstract to state or utility function (apply to preset component) const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); @@ -73,7 +75,7 @@ export function JoinCoachingSession({ (session) => session.id === selectedSession ); if (theSession) { - setSession(theSession); + setCoachingSession(theSession); setSessionId(theSession.id); } }); @@ -136,10 +138,12 @@ export function JoinCoachingSession({ />
)} - {sessionId && ( + {coachingSession.id && (
)} From fd0b4ea5a7b94f9a42b3e5096fb7d7d9ad8c76c8 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 29 Oct 2024 23:16:14 -0400 Subject: [PATCH 16/28] Fix session fetch loop and extra goal EP hit --- src/app/coaching-sessions/[id]/page.tsx | 39 ++++++++++--------- .../overarching-goal-container.tsx | 1 - 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 632b400..cd6e39b 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -3,7 +3,7 @@ // 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"; @@ -48,26 +48,25 @@ export default function CoachingSessionsPage() { const [note, setNote] = useState(defaultNote()); const [syncStatus, setSyncStatus] = useState(""); const { userId } = useAuthStore((state) => ({ userId: state.userId })); - const { coachingSession } = useAppStateStore((state) => state); + const coachingSessionId = useAppStateStore( + (state) => state.coachingSessionId + ); 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." - ); + 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) { @@ -78,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 ); @@ -97,7 +96,7 @@ export default function CoachingSessionsPage() { useEffect(() => { fetchNote(); - }, [coachingSession.id, isLoading]); + }, [coachingSessionId, isLoading]); const setEditorContent = (content: string) => { editorRef.current?.setContent(`${content}`); @@ -110,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)); @@ -127,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)); @@ -140,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." ); } }; @@ -159,6 +158,7 @@ export default function CoachingSessionsPage() { 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) { @@ -169,6 +169,7 @@ export default function CoachingSessionsPage() { if (theSession) { setSession(theSession); setSessionId(theSession.id); + router.push(`/coaching-sessions/${theSession.id}`); } }); } diff --git a/src/components/ui/coaching-sessions/overarching-goal-container.tsx b/src/components/ui/coaching-sessions/overarching-goal-container.tsx index 4d6f712..3c65833 100644 --- a/src/components/ui/coaching-sessions/overarching-goal-container.tsx +++ b/src/components/ui/coaching-sessions/overarching-goal-container.tsx @@ -148,7 +148,6 @@ const OverarchingGoalContainer: React.FC<{ ); }); } - fetchOverarchingGoal(); }, [coachingSession.id, goalId]); const handleGoalChange = async (newGoal: OverarchingGoal) => { From 68f4ed5027386af4b65e1e4a509d1e502d81f61b Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 18:29:55 -0400 Subject: [PATCH 17/28] Set overarching goal per session --- .../overarching-goal-container.tsx | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/components/ui/coaching-sessions/overarching-goal-container.tsx b/src/components/ui/coaching-sessions/overarching-goal-container.tsx index 3c65833..ec38579 100644 --- a/src/components/ui/coaching-sessions/overarching-goal-container.tsx +++ b/src/components/ui/coaching-sessions/overarching-goal-container.tsx @@ -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"; @@ -119,36 +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]); + }, [coachingSession.id, goalId, setGoalId, setGoal]); + + useEffect(() => { + fetchOverarchingGoal(); + }, [fetchOverarchingGoal]); const handleGoalChange = async (newGoal: OverarchingGoal) => { console.trace("handleGoalChange (goal to set/update): " + newGoal.title); From ad4c6a4b293bd4829a7b279914b4b094add5d79e Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 20:20:04 -0400 Subject: [PATCH 18/28] Clean up join coaching session component --- .../ui/dashboard/join-coaching-session.tsx | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 6058009..50a316e 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -21,25 +21,24 @@ export interface CoachingSessionCardProps { export function JoinCoachingSession({ userId: userId, }: CoachingSessionCardProps) { - const setOrganization = useAppStateStore((state) => state.setOrganization); - const setOrganizationId = useAppStateStore( - (state) => state.setOrganizationId - ); - let organizationId = useAppStateStore((state) => state.organizationId); - - const setCoachingRelationship = useAppStateStore( - (state) => state.setCoachingRelationship - ); - const setRelationshipId = useAppStateStore( - (state) => state.setRelationshipId - ); - let relationshipId = useAppStateStore((state) => state.relationshipId); - - const { coachingSession, setCoachingSession } = useAppStateStore((state) => ({ - coachingSession: state.coachingSession, + const { + setOrganization, + setOrganizationId, + setCoachingRelationship, + setRelationshipId, + setCoachingSession, + setCoachingSessionId, + } = useAppStateStore((state) => ({ + setOrganization: state.setOrganization, + setOrganizationId: state.setOrganizationId, + setCoachingRelationship: state.setCoachingRelationship, + setRelationshipId: state.setRelationshipId, setCoachingSession: state.setCoachingSession, + setCoachingSessionId: state.setCoachingSessionId, })); - const setSessionId = useAppStateStore((state) => state.setCoachingSessionId); + let organizationId = useAppStateStore((state) => state.organizationId); + let relationshipId = useAppStateStore((state) => state.relationshipId); + let coachingSessionId = useAppStateStore((state) => state.coachingSessionId); //@TODO: abstract to state or utility function (apply to preset component) const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); @@ -56,15 +55,16 @@ export function JoinCoachingSession({ }; //@TODO: pass selected relationship from relationship array - const handleRelationshipSelection = (value: Id) => { - setRelationshipId(value); - if (value && organizationId) { - fetchCoachingRelationshipWithUserNames(organizationId, value).then( - (relationship) => { - setRelationshipId(relationship.id); - setCoachingRelationship(relationship); - } - ); + const handleRelationshipSelection = (selectedRelationship: Id) => { + setRelationshipId(selectedRelationship); + if (selectedRelationship && organizationId) { + fetchCoachingRelationshipWithUserNames( + organizationId, + selectedRelationship + ).then((relationship) => { + setRelationshipId(relationship.id); + setCoachingRelationship(relationship); + }); } }; @@ -76,7 +76,7 @@ export function JoinCoachingSession({ ); if (theSession) { setCoachingSession(theSession); - setSessionId(theSession.id); + setCoachingSessionId(theSession.id); } }); } @@ -138,10 +138,10 @@ export function JoinCoachingSession({ />
)} - {coachingSession.id && ( + {coachingSessionId && (
From 2bdae0f9317899d291e21f1f17865a4cc01d46ca Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 21:08:07 -0400 Subject: [PATCH 19/28] Clean up join-coaching-session component further --- .../ui/dashboard/join-coaching-session.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 50a316e..87b8ec0 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -40,6 +40,15 @@ export function JoinCoachingSession({ let relationshipId = useAppStateStore((state) => state.relationshipId); let coachingSessionId = useAppStateStore((state) => state.coachingSessionId); + const [orgPlaceholder, setOrgPlaceholder] = useState( + "Select an organization" + ); + const [relPlaceHolder, setRelPlaceHolder] = useState( + "Select coaching relationship" + ); + const [sessionPlaceHolder, setSessionPlaceHolder] = + useState("Select a session"); + //@TODO: abstract to state or utility function (apply to preset component) const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); @@ -49,6 +58,7 @@ export function JoinCoachingSession({ setOrganizationId(value); if (value) { fetchOrganization(value).then(([organization]) => { + setOrgPlaceholder(organization.name); organizationId = setOrganization(organization); }); } @@ -62,6 +72,9 @@ export function JoinCoachingSession({ organizationId, selectedRelationship ).then((relationship) => { + setRelPlaceHolder( + `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coachee_last_name}` + ); setRelationshipId(relationship.id); setCoachingRelationship(relationship); }); @@ -75,6 +88,7 @@ export function JoinCoachingSession({ (session) => session.id === selectedSession ); if (theSession) { + setSessionPlaceHolder(theSession.date); setCoachingSession(theSession); setCoachingSessionId(theSession.id); } @@ -95,7 +109,7 @@ export function JoinCoachingSession({ url="/organizations" params={{ userId }} onChange={handleOrganizationSelection} - placeholder="Select an organization" + placeholder={orgPlaceholder} getOptionLabel={(org) => org.name} getOptionValue={(org) => org.id.toString()} elementId="organization-selector" @@ -109,7 +123,7 @@ export function JoinCoachingSession({ url={`/organizations/${organizationId}/coaching_relationships`} params={{ organizationId }} onChange={handleRelationshipSelection} - placeholder="Select coaching relationship" + placeholder={relPlaceHolder} getOptionLabel={(relationship) => `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}` } @@ -130,7 +144,7 @@ export function JoinCoachingSession({ to_Date: TO_DATE, }} onChange={handleSessionSelection} - placeholder="Select coaching session" + placeholder={sessionPlaceHolder} getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} elementId="session-selector" From 7f7011aae568bbeece412e663110571919e90414 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 21:10:03 -0400 Subject: [PATCH 20/28] Use coachee last name in relationship string --- src/components/ui/dashboard/join-coaching-session.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 87b8ec0..3025f1f 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -125,7 +125,7 @@ export function JoinCoachingSession({ onChange={handleRelationshipSelection} placeholder={relPlaceHolder} getOptionLabel={(relationship) => - `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}` + `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coachee_last_name}` } getOptionValue={(relationship) => relationship.id.toString()} elementId="relationship-selector" From 6e898e6ad87fedda47e4bd020c233ffa44b7b866 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 21:14:09 -0400 Subject: [PATCH 21/28] Sort session dates --- src/components/ui/dashboard/dynamic-api-select.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/ui/dashboard/dynamic-api-select.tsx b/src/components/ui/dashboard/dynamic-api-select.tsx index f47596d..6dc8a81 100644 --- a/src/components/ui/dashboard/dynamic-api-select.tsx +++ b/src/components/ui/dashboard/dynamic-api-select.tsx @@ -65,11 +65,18 @@ export function DynamicApiSelect({ filterFn: (session: CoachingSession) => boolean ) => { const filteredSessions = sessions.filter(filterFn); + + const sortedSessions = filteredSessions.sort( + (a, b) => + DateTime.fromISO(a.date.toString()).toMillis() - + DateTime.fromISO(b.date.toString()).toMillis() + ); + return ( - filteredSessions.length > 0 && ( + sortedSessions.length > 0 && ( {label} - {filteredSessions.map((session) => ( + {sortedSessions.map((session) => ( {DateTime.fromISO(session.date.toString()).toLocaleString( DateTime.DATETIME_FULL From 5efcf6129755b319216ad28cfc5388a9d0d20ef5 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 21:47:39 -0400 Subject: [PATCH 22/28] Persist placeholder with useEffect --- .../ui/dashboard/join-coaching-session.tsx | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 3025f1f..7c3c6e3 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; import { Id } from "@/types/general"; import { DynamicApiSelect } from "./dynamic-api-select"; @@ -18,9 +18,7 @@ export interface CoachingSessionCardProps { userId: Id; } -export function JoinCoachingSession({ - userId: userId, -}: CoachingSessionCardProps) { +export function JoinCoachingSession({ userId }: CoachingSessionCardProps) { const { setOrganization, setOrganizationId, @@ -28,6 +26,9 @@ export function JoinCoachingSession({ setRelationshipId, setCoachingSession, setCoachingSessionId, + organization, + coachingRelationship, + coachingSession, } = useAppStateStore((state) => ({ setOrganization: state.setOrganization, setOrganizationId: state.setOrganizationId, @@ -35,7 +36,11 @@ export function JoinCoachingSession({ setRelationshipId: state.setRelationshipId, setCoachingSession: state.setCoachingSession, setCoachingSessionId: state.setCoachingSessionId, + organization: state.organization, + coachingRelationship: state.coachingRelationship, + coachingSession: state.coachingSession, })); + let organizationId = useAppStateStore((state) => state.organizationId); let relationshipId = useAppStateStore((state) => state.relationshipId); let coachingSessionId = useAppStateStore((state) => state.coachingSessionId); @@ -43,28 +48,50 @@ export function JoinCoachingSession({ const [orgPlaceholder, setOrgPlaceholder] = useState( "Select an organization" ); - const [relPlaceHolder, setRelPlaceHolder] = useState( + const [relPlaceholder, setRelPlaceholder] = useState( "Select coaching relationship" ); - const [sessionPlaceHolder, setSessionPlaceHolder] = + const [sessionPlaceholder, setSessionPlaceholder] = useState("Select a session"); - //@TODO: abstract to state or utility function (apply to preset component) + useEffect(() => { + if (organization && organizationId) { + setOrgPlaceholder(organization.name); + } else { + setOrgPlaceholder("Select an organization"); + } + }, [organization, organizationId]); + + useEffect(() => { + if (coachingRelationship && relationshipId) { + setRelPlaceholder( + `${coachingRelationship.coach_first_name} ${coachingRelationship.coach_last_name} -> ${coachingRelationship.coachee_first_name} ${coachingRelationship.coachee_last_name}` + ); + } else { + setRelPlaceholder("Select coaching relationship"); + } + }, [coachingRelationship, relationshipId]); + + useEffect(() => { + if (coachingSession && coachingSessionId) { + setSessionPlaceholder(coachingSession.date); + } else { + setSessionPlaceholder("Select a session"); + } + }, [coachingSession, coachingSessionId]); + const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); - //@TODO: pass selected organization from organization array const handleOrganizationSelection = (value: Id) => { setOrganizationId(value); if (value) { fetchOrganization(value).then(([organization]) => { - setOrgPlaceholder(organization.name); - organizationId = setOrganization(organization); + setOrganization(organization); }); } }; - //@TODO: pass selected relationship from relationship array const handleRelationshipSelection = (selectedRelationship: Id) => { setRelationshipId(selectedRelationship); if (selectedRelationship && organizationId) { @@ -72,10 +99,6 @@ export function JoinCoachingSession({ organizationId, selectedRelationship ).then((relationship) => { - setRelPlaceHolder( - `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coachee_last_name}` - ); - setRelationshipId(relationship.id); setCoachingRelationship(relationship); }); } @@ -88,7 +111,6 @@ export function JoinCoachingSession({ (session) => session.id === selectedSession ); if (theSession) { - setSessionPlaceHolder(theSession.date); setCoachingSession(theSession); setCoachingSessionId(theSession.id); } @@ -104,7 +126,6 @@ export function JoinCoachingSession({
- url="/organizations" params={{ userId }} @@ -118,12 +139,11 @@ export function JoinCoachingSession({ {organizationId && (
- url={`/organizations/${organizationId}/coaching_relationships`} params={{ organizationId }} onChange={handleRelationshipSelection} - placeholder={relPlaceHolder} + placeholder={relPlaceholder} getOptionLabel={(relationship) => `${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coachee_last_name}` } @@ -135,7 +155,6 @@ export function JoinCoachingSession({ {relationshipId && (
- url="/coaching_sessions" params={{ @@ -144,7 +163,7 @@ export function JoinCoachingSession({ to_Date: TO_DATE, }} onChange={handleSessionSelection} - placeholder={sessionPlaceHolder} + placeholder={sessionPlaceholder} getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} elementId="session-selector" From f9e79dfdbfa7b6fbde39ef4fef7d25e17b6af879 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 21:58:49 -0400 Subject: [PATCH 23/28] Make sure the entire join session button is clickable --- src/components/ui/dashboard/join-coaching-session.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 7c3c6e3..2a31bb9 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -173,11 +173,11 @@ export function JoinCoachingSession({ userId }: CoachingSessionCardProps) { )} {coachingSessionId && (
- + +
)} From 1ad2df893a9755c5bb858185f66e34e751792cb2 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Oct 2024 22:37:59 -0400 Subject: [PATCH 24/28] Slight wording change on session page --- src/app/coaching-sessions/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index cd6e39b..de098a4 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -193,7 +193,7 @@ export default function CoachingSessionsPage() { to_Date: TO_DATE, }} onChange={handleSessionSelection} - placeholder="Select coaching session" + placeholder="Change coaching session" getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} elementId="session-selector" From b8afc27a3c6eb25d630a637c688b5bc2f17f7b92 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 31 Oct 2024 12:04:02 -0400 Subject: [PATCH 25/28] Update page.tsx Co-authored-by: Jim Hodapp --- src/app/coaching-sessions/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index de098a4..e13fc99 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -193,7 +193,7 @@ export default function CoachingSessionsPage() { to_Date: TO_DATE, }} onChange={handleSessionSelection} - placeholder="Change coaching session" + placeholder="Select a session" getOptionLabel={(session) => session.date.toString()} getOptionValue={(session) => session.id.toString()} elementId="session-selector" From dc32d21d8b2b4c7b2080e35f1b16703e9cd01af9 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 31 Oct 2024 12:47:43 -0400 Subject: [PATCH 26/28] Transform displayed date in state store --- src/lib/stores/app-state-store.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/stores/app-state-store.ts b/src/lib/stores/app-state-store.ts index bc59dab..9465832 100644 --- a/src/lib/stores/app-state-store.ts +++ b/src/lib/stores/app-state-store.ts @@ -10,6 +10,7 @@ import { Id } from "@/types/general"; import { defaultOrganization, Organization } from "@/types/organization"; import { create } from "zustand"; import { createJSONStorage, devtools, persist } from "zustand/middleware"; +import { DateTime } from "ts-luxon"; interface AppState { organizationId: Id; @@ -65,6 +66,9 @@ export const createAppStateStore = (initState: AppState = defaultInitState) => { return organization.id; }, setCoachingSession: (coachingSession) => { + coachingSession.date = DateTime.fromISO( + coachingSession.date.toString() + ).toLocaleString(DateTime.DATETIME_FULL); set({ coachingSession }); return coachingSession.id; }, From 6cc47347899567127c2cfdfb82052c3f151d8672 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 8 Nov 2024 01:33:36 -0500 Subject: [PATCH 27/28] Extract state logic from joinCoachingSession component --- .../ui/dashboard/join-coaching-session.tsx | 124 ++++----------- src/hooks/use-app-state.ts | 51 +++++++ src/hooks/use-selection-handlers.ts | 142 ++++++++++++++++++ src/hooks/use-selectors.ts | 14 ++ 4 files changed, 234 insertions(+), 97 deletions(-) create mode 100644 src/hooks/use-app-state.ts create mode 100644 src/hooks/use-selection-handlers.ts create mode 100644 src/hooks/use-selectors.ts diff --git a/src/components/ui/dashboard/join-coaching-session.tsx b/src/components/ui/dashboard/join-coaching-session.tsx index 2a31bb9..2813466 100644 --- a/src/components/ui/dashboard/join-coaching-session.tsx +++ b/src/components/ui/dashboard/join-coaching-session.tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from "react"; -import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; import { Id } from "@/types/general"; import { DynamicApiSelect } from "./dynamic-api-select"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -10,113 +9,44 @@ import { DateTime } from "ts-luxon"; import { Label } from "@/components/ui/label"; import { Button } from "../button"; import Link from "next/link"; -import { fetchCoachingRelationshipWithUserNames } from "@/lib/api/coaching-relationships"; -import { fetchOrganization } from "@/lib/api/organizations"; -import { fetchCoachingSessions } from "@/lib/api/coaching-sessions"; +import { useAppState } from "@/hooks/use-app-state"; +import { useSelectors } from "@/hooks/use-selectors"; +import { useSelectionHandlers } from "@/hooks/use-selection-handlers"; +import { + AppStateStore, + createAppStateStore, +} from "@/lib/stores/app-state-store"; +import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; export interface CoachingSessionCardProps { userId: Id; } export function JoinCoachingSession({ userId }: CoachingSessionCardProps) { - const { - setOrganization, - setOrganizationId, - setCoachingRelationship, - setRelationshipId, - setCoachingSession, - setCoachingSessionId, - organization, - coachingRelationship, - coachingSession, - } = useAppStateStore((state) => ({ - setOrganization: state.setOrganization, - setOrganizationId: state.setOrganizationId, - setCoachingRelationship: state.setCoachingRelationship, - setRelationshipId: state.setRelationshipId, - setCoachingSession: state.setCoachingSession, - setCoachingSessionId: state.setCoachingSessionId, - organization: state.organization, - coachingRelationship: state.coachingRelationship, - coachingSession: state.coachingSession, - })); - - let organizationId = useAppStateStore((state) => state.organizationId); - let relationshipId = useAppStateStore((state) => state.relationshipId); - let coachingSessionId = useAppStateStore((state) => state.coachingSessionId); - - const [orgPlaceholder, setOrgPlaceholder] = useState( - "Select an organization" - ); - const [relPlaceholder, setRelPlaceholder] = useState( - "Select coaching relationship" - ); - const [sessionPlaceholder, setSessionPlaceholder] = - useState("Select a session"); - - useEffect(() => { - if (organization && organizationId) { - setOrgPlaceholder(organization.name); - } else { - setOrgPlaceholder("Select an organization"); - } - }, [organization, organizationId]); - - useEffect(() => { - if (coachingRelationship && relationshipId) { - setRelPlaceholder( - `${coachingRelationship.coach_first_name} ${coachingRelationship.coach_last_name} -> ${coachingRelationship.coachee_first_name} ${coachingRelationship.coachee_last_name}` - ); - } else { - setRelPlaceholder("Select coaching relationship"); - } - }, [coachingRelationship, relationshipId]); - - useEffect(() => { - if (coachingSession && coachingSessionId) { - setSessionPlaceholder(coachingSession.date); - } else { - setSessionPlaceholder("Select a session"); - } - }, [coachingSession, coachingSessionId]); - const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate(); const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate(); - const handleOrganizationSelection = (value: Id) => { - setOrganizationId(value); - if (value) { - fetchOrganization(value).then(([organization]) => { - setOrganization(organization); - }); - } - }; + const { organizationId, relationshipId, coachingSessionId } = useAppState(); - const handleRelationshipSelection = (selectedRelationship: Id) => { - setRelationshipId(selectedRelationship); - if (selectedRelationship && organizationId) { - fetchCoachingRelationshipWithUserNames( - organizationId, - selectedRelationship - ).then((relationship) => { - setCoachingRelationship(relationship); - }); - } - }; + const selectors = useSelectors(); + const { + handleOrganizationSelection, + handleRelationshipSelection, + handleSessionSelection, + isLoading, + error, + isClient, + } = useSelectionHandlers(); - const handleSessionSelection = (selectedSession: Id) => { - if (selectedSession && relationshipId) { - fetchCoachingSessions(relationshipId).then(([sessions]) => { - const theSession = sessions.find( - (session) => session.id === selectedSession - ); - if (theSession) { - setCoachingSession(theSession); - setCoachingSessionId(theSession.id); - } - }); - } - }; + const orgPlaceholder = selectors.selectOrgPlaceholder( + useAppStateStore((state) => state) + ); + const relPlaceholder = selectors.selectRelPlaceholder( + useAppStateStore((state) => state) + ); + const sessionPlaceholder = selectors.selectSessionPlaceholder( + useAppStateStore((state) => state) + ); return ( diff --git a/src/hooks/use-app-state.ts b/src/hooks/use-app-state.ts new file mode 100644 index 0000000..8bcdf0c --- /dev/null +++ b/src/hooks/use-app-state.ts @@ -0,0 +1,51 @@ +import { useMemo } from "react"; +import { createAppStateStore as globalStore } from "@/lib/stores/app-state-store"; + +export const useAppState = () => { + const store = globalStore(); + + return useMemo( + () => ({ + organizationId: store.getState().organizationId, + relationshipId: store.getState().relationshipId, + coachingSessionId: store.getState().coachingSessionId, + + organization: store.getState().organization, + coachingRelationship: store.getState().coachingRelationship, + coachingSession: store.getState().coachingSession, + + setOrganizationId: (organizationId: string) => { + if (store.getState().organizationId !== organizationId) { + store.setState({ organizationId }); + } + }, + setRelationshipId: (relationshipId: string) => { + if (store.getState().relationshipId !== relationshipId) { + store.setState({ relationshipId }); + } + }, + setCoachingSessionId: (coachingSessionId: string) => { + if (store.getState().coachingSessionId !== coachingSessionId) { + store.setState({ coachingSessionId }); + } + }, + + setOrganization: (organization: any) => { + if (store.getState().organization !== organization) { + store.setState({ organization }); + } + }, + setCoachingRelationship: (coachingRelationship: any) => { + if (store.getState().coachingRelationship !== coachingRelationship) { + store.setState({ coachingRelationship }); + } + }, + setCoachingSession: (coachingSession: any) => { + if (store.getState().coachingSession !== coachingSession) { + store.setState({ coachingSession }); + } + }, + }), + [store] + ); +}; diff --git a/src/hooks/use-selection-handlers.ts b/src/hooks/use-selection-handlers.ts new file mode 100644 index 0000000..7a234fd --- /dev/null +++ b/src/hooks/use-selection-handlers.ts @@ -0,0 +1,142 @@ +import { useCallback, useState, useEffect } from "react"; +import { Id } from "@/types/general"; +import { fetchCoachingRelationshipWithUserNames } from "@/lib/api/coaching-relationships"; +import { fetchCoachingSessions } from "@/lib/api/coaching-sessions"; +import { fetchOrganization } from "@/lib/api/organizations"; +import { useAppState } from "./use-app-state"; +import { defaultCoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names"; +import { defaultCoachingSession } from "@/types/coaching-session"; +import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; + +export const useSelectionHandlers = () => { + const { + setOrganizationId, + setRelationshipId, + setCoachingSessionId, + setOrganization, + setCoachingRelationship, + setCoachingSession, + } = useAppState(); + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + const resetRelationshipAndSession = useCallback(() => { + setCoachingRelationship(defaultCoachingRelationshipWithUserNames()); + setRelationshipId(""); + setCoachingSession(defaultCoachingSession()); + setCoachingSessionId(""); + }, [ + setCoachingRelationship, + setRelationshipId, + setCoachingSession, + setCoachingSessionId, + ]); + + const resetSession = useCallback(() => { + setCoachingSession(defaultCoachingSession()); + setCoachingSessionId(""); + }, [setCoachingSession, setCoachingSessionId]); + + const handleOrganizationSelection = useCallback( + async (value: Id) => { + if (!isClient) return; // Prevent execution on server-side + setIsLoading(true); + setError(null); + try { + resetRelationshipAndSession(); + setOrganizationId(value); + if (value) { + const [organization] = await fetchOrganization(value); + if (!organization) throw new Error("Organization not found"); + setOrganization(organization); + } else { + setOrganization(null); + } + } catch (err) { + console.error("Error fetching organization:", err); + setError("Failed to fetch organization"); + } finally { + setIsLoading(false); + } + }, + [isClient, setOrganizationId, setOrganization, resetRelationshipAndSession] + ); + + const handleRelationshipSelection = useCallback( + async (selectedRelationship: Id) => { + if (!isClient) return; // Prevent execution on server-side + setIsLoading(true); + setError(null); + try { + resetSession(); + setRelationshipId(selectedRelationship); + if (selectedRelationship) { + const organizationId = useAppStateStore( + (state) => state.organizationId + ); + if (!organizationId) throw new Error("Organization ID is not set"); + const relationship = await fetchCoachingRelationshipWithUserNames( + organizationId, + selectedRelationship + ); + if (!relationship) throw new Error("Relationship not found"); + setCoachingRelationship(relationship); + } else { + setCoachingRelationship(defaultCoachingRelationshipWithUserNames()); + } + } catch (err) { + console.error("Error fetching relationship:", err); + setError("Failed to fetch relationship"); + } finally { + setIsLoading(false); + } + }, + [isClient, setRelationshipId, setCoachingRelationship, resetSession] + ); + + const handleSessionSelection = useCallback( + async (selectedSession: Id) => { + if (!isClient) return; // Prevent execution on server-side + setIsLoading(true); + setError(null); + try { + if (selectedSession) { + const relationshipId = useAppStateStore( + (state) => state.relationshipId + ); + if (!relationshipId) throw new Error("Relationship ID is not set"); + const [sessions] = await fetchCoachingSessions(relationshipId); + const theSession = sessions.find( + (session) => session.id === selectedSession + ); + if (!theSession) throw new Error("Selected session not found"); + setCoachingSession(theSession); + setCoachingSessionId(theSession.id); + } else { + resetSession(); + } + } catch (err) { + console.error("Error fetching session:", err); + setError("Failed to fetch session"); + } finally { + setIsLoading(false); + } + }, + [isClient, setCoachingSession, setCoachingSessionId, resetSession] + ); + + return { + handleOrganizationSelection, + handleRelationshipSelection, + handleSessionSelection, + isLoading, + error, + isClient, + }; +}; diff --git a/src/hooks/use-selectors.ts b/src/hooks/use-selectors.ts new file mode 100644 index 0000000..d10ec93 --- /dev/null +++ b/src/hooks/use-selectors.ts @@ -0,0 +1,14 @@ +import { AppStateStore } from "@/lib/stores/app-state-store"; + +export const useSelectors = () => ({ + selectOrgPlaceholder: (state: AppStateStore) => + state.organization?.name || "Select an organization", + + selectRelPlaceholder: (state: AppStateStore) => + state.coachingRelationship + ? `${state.coachingRelationship.coach_first_name} ${state.coachingRelationship.coach_last_name} -> ${state.coachingRelationship.coachee_first_name} ${state.coachingRelationship.coachee_last_name}` + : "Select coaching relationship", + + selectSessionPlaceholder: (state: AppStateStore) => + state.coachingSession?.date || "Select a session", +}); From 4354aa022fec45427bb4bf62af287327b136eed7 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 12 Nov 2024 17:22:03 -0500 Subject: [PATCH 28/28] Clean up selection hook --- src/hooks/use-selection-handlers.ts | 176 +++++++++++++--------------- 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/src/hooks/use-selection-handlers.ts b/src/hooks/use-selection-handlers.ts index 7a234fd..4712ea7 100644 --- a/src/hooks/use-selection-handlers.ts +++ b/src/hooks/use-selection-handlers.ts @@ -26,115 +26,107 @@ export const useSelectionHandlers = () => { setIsClient(true); }, []); - const resetRelationshipAndSession = useCallback(() => { - setCoachingRelationship(defaultCoachingRelationshipWithUserNames()); - setRelationshipId(""); - setCoachingSession(defaultCoachingSession()); - setCoachingSessionId(""); - }, [ - setCoachingRelationship, - setRelationshipId, - setCoachingSession, - setCoachingSessionId, - ]); - - const resetSession = useCallback(() => { - setCoachingSession(defaultCoachingSession()); - setCoachingSessionId(""); - }, [setCoachingSession, setCoachingSessionId]); - - const handleOrganizationSelection = useCallback( - async (value: Id) => { - if (!isClient) return; // Prevent execution on server-side - setIsLoading(true); - setError(null); - try { - resetRelationshipAndSession(); - setOrganizationId(value); - if (value) { - const [organization] = await fetchOrganization(value); - if (!organization) throw new Error("Organization not found"); - setOrganization(organization); - } else { - setOrganization(null); - } - } catch (err) { - console.error("Error fetching organization:", err); - setError("Failed to fetch organization"); - } finally { - setIsLoading(false); + const resetState = useCallback( + (level: "all" | "relationship" | "session") => { + if (level === "all" || level === "relationship") { + setCoachingRelationship(defaultCoachingRelationshipWithUserNames()); + setRelationshipId(""); } - }, - [isClient, setOrganizationId, setOrganization, resetRelationshipAndSession] - ); - - const handleRelationshipSelection = useCallback( - async (selectedRelationship: Id) => { - if (!isClient) return; // Prevent execution on server-side - setIsLoading(true); - setError(null); - try { - resetSession(); - setRelationshipId(selectedRelationship); - if (selectedRelationship) { - const organizationId = useAppStateStore( - (state) => state.organizationId - ); - if (!organizationId) throw new Error("Organization ID is not set"); - const relationship = await fetchCoachingRelationshipWithUserNames( - organizationId, - selectedRelationship - ); - if (!relationship) throw new Error("Relationship not found"); - setCoachingRelationship(relationship); - } else { - setCoachingRelationship(defaultCoachingRelationshipWithUserNames()); - } - } catch (err) { - console.error("Error fetching relationship:", err); - setError("Failed to fetch relationship"); - } finally { - setIsLoading(false); + if (level === "all" || level === "relationship" || level === "session") { + setCoachingSession(defaultCoachingSession()); + setCoachingSessionId(""); } }, - [isClient, setRelationshipId, setCoachingRelationship, resetSession] + [ + setCoachingRelationship, + setRelationshipId, + setCoachingSession, + setCoachingSessionId, + ] ); - const handleSessionSelection = useCallback( - async (selectedSession: Id) => { - if (!isClient) return; // Prevent execution on server-side + const handleSelection = useCallback( + async (level: "organization" | "relationship" | "session", value: Id) => { + if (!isClient) return; setIsLoading(true); setError(null); + try { - if (selectedSession) { - const relationshipId = useAppStateStore( - (state) => state.relationshipId - ); - if (!relationshipId) throw new Error("Relationship ID is not set"); - const [sessions] = await fetchCoachingSessions(relationshipId); - const theSession = sessions.find( - (session) => session.id === selectedSession - ); - if (!theSession) throw new Error("Selected session not found"); - setCoachingSession(theSession); - setCoachingSessionId(theSession.id); - } else { - resetSession(); + switch (level) { + case "organization": + resetState("all"); + setOrganizationId(value); + if (value) { + const [organization] = await fetchOrganization(value); + if (!organization) throw new Error("Organization not found"); + setOrganization(organization); + } else { + setOrganization(null); + } + break; + + case "relationship": + resetState("relationship"); + setRelationshipId(value); + if (value) { + const organizationId = useAppStateStore( + (state) => state.organizationId + ); + if (!organizationId) + throw new Error("Organization ID is not set"); + const relationship = await fetchCoachingRelationshipWithUserNames( + organizationId, + value + ); + if (!relationship) throw new Error("Relationship not found"); + setCoachingRelationship(relationship); + } + break; + + case "session": + if (value) { + const relationshipId = useAppStateStore( + (state) => state.relationshipId + ); + if (!relationshipId) + throw new Error("Relationship ID is not set"); + const [sessions] = await fetchCoachingSessions(relationshipId); + const theSession = sessions.find( + (session) => session.id === value + ); + if (!theSession) throw new Error("Selected session not found"); + setCoachingSession(theSession); + setCoachingSessionId(theSession.id); + } else { + resetState("session"); + } + break; } } catch (err) { - console.error("Error fetching session:", err); - setError("Failed to fetch session"); + console.error(`Error fetching ${level}:`, err); + setError(`Failed to fetch ${level}`); } finally { setIsLoading(false); } }, - [isClient, setCoachingSession, setCoachingSessionId, resetSession] + [ + isClient, + setOrganizationId, + setOrganization, + setRelationshipId, + setCoachingRelationship, + setCoachingSession, + setCoachingSessionId, + resetState, + ] ); return { - handleOrganizationSelection, - handleRelationshipSelection, - handleSessionSelection, + handleOrganizationSelection: (value: Id) => + handleSelection("organization", value), + handleRelationshipSelection: (value: Id) => + handleSelection("relationship", value), + handleSessionSelection: (value: Id) => handleSelection("session", value), isLoading, error, isClient,