Skip to content

Commit 22730eb

Browse files
committed
Store full organization, relationship, and coachingSession objects in AppStateStore.
1 parent 3c930bb commit 22730eb

File tree

4 files changed

+181
-99
lines changed

4 files changed

+181
-99
lines changed
Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import React, { useState } from 'react';
2-
import { useApiData } from '@/hooks/use-api-data';
3-
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select';
4-
import { CoachingSession, isCoachingSession } from '@/types/coaching-session';
5-
import { DateTime } from 'ts-luxon';
1+
import React, { useState } from "react";
2+
import { useApiData } from "@/hooks/use-api-data";
3+
import {
4+
Select,
5+
SelectContent,
6+
SelectGroup,
7+
SelectItem,
8+
SelectLabel,
9+
SelectTrigger,
10+
SelectValue,
11+
} from "@/components/ui/select";
12+
import {
13+
CoachingSession,
14+
isCoachingSession,
15+
sortCoachingSessionArray,
16+
} from "@/types/coaching-session";
17+
import { DateTime } from "ts-luxon";
18+
import { SortOrder } from "@/types/general";
619

720
interface DynamicApiSelectProps<T> {
821
url: string;
9-
method?: 'GET' | 'POST';
22+
method?: "GET" | "POST";
1023
params?: Record<string, any>;
1124
onChange: (value: string) => void;
1225
placeholder?: string;
@@ -23,51 +36,82 @@ interface ApiResponse<T> {
2336

2437
export function DynamicApiSelect<T>({
2538
url,
26-
method = 'GET',
39+
method = "GET",
2740
params = {},
2841
onChange,
2942
placeholder = "Select an option",
3043
getOptionLabel,
3144
getOptionValue,
3245
elementId,
33-
groupByDate = false
46+
groupByDate = false,
3447
}: DynamicApiSelectProps<T>) {
35-
const { data: response, isLoading, error } = useApiData<ApiResponse<T>>(url, { method, params });
36-
const [value, setValue] = useState<string>('');
48+
const {
49+
data: response,
50+
isLoading,
51+
error,
52+
} = useApiData<ApiResponse<T>>(url, { method, params });
53+
const [value, setValue] = useState<string>("");
3754

3855
const handleValueChange = (newValue: string) => {
3956
setValue(newValue);
4057
onChange(newValue);
41-
}
58+
};
4259

4360
if (isLoading) return <p>Loading...</p>;
4461
if (error) return <p>Error: {error.message}</p>;
45-
if (!response || response.status_code !== 200) return <p>Error: Invalid response</p>;
62+
if (!response || response.status_code !== 200)
63+
return <p>Error: Invalid response</p>;
4664

4765
const items = response.data;
4866

49-
const renderSessions = (sessions: CoachingSession[], label: string, filterFn: (session: CoachingSession) => boolean) => {
67+
const renderSessions = (
68+
sessions: CoachingSession[],
69+
label: string,
70+
filterFn: (session: CoachingSession) => boolean,
71+
sortOrder: SortOrder
72+
) => {
5073
const filteredSessions = sessions.filter(filterFn);
51-
return filteredSessions.length > 0 && (
52-
<SelectGroup>
53-
<SelectLabel>{label}</SelectLabel>
54-
{filteredSessions.map(session => (
55-
<SelectItem value={session.id} key={session.id}>
56-
{DateTime.fromISO(session.date.toString()).toLocaleString(DateTime.DATETIME_FULL)}
57-
</SelectItem>
58-
))}
59-
</SelectGroup>
74+
const sortedSessions = sortCoachingSessionArray(
75+
filteredSessions,
76+
sortOrder
77+
);
78+
79+
return (
80+
sortedSessions.length > 0 && (
81+
<SelectGroup>
82+
<SelectLabel>{label}</SelectLabel>
83+
{sortedSessions.map((session) => (
84+
<SelectItem value={session.id} key={session.id}>
85+
{DateTime.fromISO(session.date).toLocaleString(
86+
DateTime.DATETIME_FULL
87+
)}
88+
</SelectItem>
89+
))}
90+
</SelectGroup>
91+
)
6092
);
6193
};
6294

6395
const renderCoachingSessions = (sessions: CoachingSession[]) => (
6496
<SelectContent>
6597
{sessions.length === 0 ? (
66-
<SelectItem disabled value="none">None found</SelectItem>
98+
<SelectItem disabled value="none">
99+
None found
100+
</SelectItem>
67101
) : (
68102
<>
69-
{renderSessions(sessions, 'Previous Sessions', session => DateTime.fromISO(session.date.toString()) < DateTime.now())}
70-
{renderSessions(sessions, 'Upcoming Sessions', session => DateTime.fromISO(session.date.toString()) >= DateTime.now())}
103+
{renderSessions(
104+
sessions,
105+
"Previous Sessions",
106+
(session) => DateTime.fromISO(session.date) < DateTime.now(),
107+
SortOrder.Descending
108+
)}
109+
{renderSessions(
110+
sessions,
111+
"Upcoming Sessions",
112+
(session) => DateTime.fromISO(session.date) >= DateTime.now(),
113+
SortOrder.Ascending
114+
)}
71115
</>
72116
)}
73117
</SelectContent>
@@ -76,7 +120,9 @@ export function DynamicApiSelect<T>({
76120
const renderOtherItems = (items: T[]) => (
77121
<SelectContent>
78122
{items.length === 0 ? (
79-
<SelectItem disabled value="none">None found</SelectItem>
123+
<SelectItem disabled value="none">
124+
None found
125+
</SelectItem>
80126
) : (
81127
items.map((item, index) => (
82128
<SelectItem key={index} value={getOptionValue(item)}>
@@ -87,7 +133,9 @@ export function DynamicApiSelect<T>({
87133
</SelectContent>
88134
);
89135

90-
const coachingSessions = groupByDate ? items.filter(isCoachingSession) as CoachingSession[] : [];
136+
const coachingSessions = groupByDate
137+
? (items.filter(isCoachingSession) as CoachingSession[])
138+
: [];
91139

92140
return (
93141
<Select value={value} onValueChange={handleValueChange}>
@@ -99,4 +147,4 @@ export function DynamicApiSelect<T>({
99147
: renderOtherItems(items)}
100148
</Select>
101149
);
102-
}
150+
}
Lines changed: 97 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,158 @@
1-
import React, { useState } from 'react';
2-
import { useAppStateStore } from '@/lib/providers/app-state-store-provider';
1+
import React, { useState } from "react";
2+
import { useAppStateStore } from "@/lib/providers/app-state-store-provider";
33
import { Id } from "@/types/general";
4-
import { DynamicApiSelect } from './dynamic-api-select';
4+
import { DynamicApiSelect } from "./dynamic-api-select";
55
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
6-
import { Organization } from '@/types/organization';
7-
import { CoachingRelationshipWithUserNames } from '@/types/coaching_relationship_with_user_names';
8-
import { CoachingSession } from '@/types/coaching-session';
9-
import { DateTime } from 'ts-luxon';
6+
import { Organization } from "@/types/organization";
7+
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names";
8+
import {
9+
CoachingSession,
10+
getCoachingSessionById,
11+
} from "@/types/coaching-session";
12+
import { DateTime } from "ts-luxon";
1013
import { Label } from "@/components/ui/label";
11-
import { Button } from '../button';
12-
import Link from 'next/link';
13-
14+
import { Button } from "../button";
15+
import Link from "next/link";
16+
import { fetchOrganization } from "@/lib/api/organizations";
17+
import { fetchCoachingRelationshipWithUserNames } from "@/lib/api/coaching-relationships";
18+
import { fetchCoachingSessions } from "@/lib/api/coaching-sessions";
1419

1520
export interface CoachingSessionCardProps {
1621
userId: Id;
1722
}
1823

19-
export function JoinCoachingSession({ userId: userId }: CoachingSessionCardProps) {
20-
const setOrganizationId = useAppStateStore(state => state.setOrganizationId);
21-
const setRelationshipId = useAppStateStore(state => state.setRelationshipId);
22-
const setCoachingSessionId = useAppStateStore(state => state.setCoachingSessionId);
23-
const [organizationId, setOrganization] = useState<string | null>(null);
24-
const [relationshipId, setRelationship] = useState<string | null>(null);
25-
const [sessionId, setSessions] = useState<string | null>(null);
24+
export function JoinCoachingSession({
25+
userId: userId,
26+
}: CoachingSessionCardProps) {
27+
const { organizationId, setOrganizationId } = useAppStateStore((state) => ({
28+
organizationId: state.organizationId,
29+
setOrganizationId: state.setOrganizationId,
30+
}));
31+
const { relationshipId, setRelationshipId } = useAppStateStore((state) => ({
32+
relationshipId: state.setRelationshipId,
33+
setRelationshipId: state.setRelationshipId,
34+
}));
35+
const { coachingSessionId, setCoachingSessionId } = useAppStateStore(
36+
(state) => ({
37+
coachingSessionId: state.coachingSessionId,
38+
setCoachingSessionId: state.setCoachingSessionId,
39+
})
40+
);
41+
const { organization, setOrganization } = useAppStateStore((state) => ({
42+
organization: state.organization,
43+
setOrganization: state.setOrganization,
44+
}));
45+
const { relationship, setRelationship } = useAppStateStore((state) => ({
46+
relationship: state.coachingRelationship,
47+
setRelationship: state.setCoachingRelationship,
48+
}));
49+
const { coachingSession, setCoachingSession } = useAppStateStore((state) => ({
50+
coachingSession: state.coachingSession,
51+
setCoachingSession: state.setCoachingSession,
52+
}));
2653
const FROM_DATE = DateTime.now().minus({ month: 1 }).toISODate();
2754
const TO_DATE = DateTime.now().plus({ month: 1 }).toISODate();
2855

29-
const handleOrganizationSelection = (value: string) => {
30-
setOrganization(value);
31-
setRelationship(null);
32-
setSessions(null);
33-
setOrganizationId(value);
34-
}
35-
36-
const handleRelationshipSelection = (value: string) => {
37-
setRelationship(value);
38-
setSessions(null);
39-
setRelationshipId(value);
40-
}
56+
const handleOrganizationSelection = (id: Id) => {
57+
fetchOrganization(id)
58+
.then((organization) => {
59+
setOrganizationId(organization.id);
60+
setOrganization(organization);
61+
})
62+
.catch((err) => {
63+
console.error("Failed to retrieve and set organization: " + err);
64+
});
65+
};
4166

42-
const handleSessionSelection = (value: string) => {
43-
setSessions(value);
44-
setCoachingSessionId(value);
45-
}
67+
const handleRelationshipSelection = (id: Id) => {
68+
fetchCoachingRelationshipWithUserNames(organization.id, id)
69+
.then((relationship) => {
70+
setRelationshipId(relationship.id);
71+
setRelationship(relationship);
72+
})
73+
.catch((err) => {
74+
console.error("Failed to retrieve and set relationship: " + err);
75+
});
76+
};
4677

78+
const handleSessionSelection = (id: Id) => {
79+
fetchCoachingSessions(relationship.id)
80+
.then((sessions) => {
81+
const session = getCoachingSessionById(id, sessions);
82+
setCoachingSessionId(session.id);
83+
setCoachingSession(session);
84+
})
85+
.catch((err) => {
86+
console.error("Failed to retrieve and set relationship: " + err);
87+
});
88+
};
4789

4890
return (
4991
<Card className="w-[300px]">
5092
<CardHeader>
5193
<CardTitle>Join a Coaching Session</CardTitle>
5294
</CardHeader>
53-
<CardContent className='grid gap-6'>
95+
<CardContent className="grid gap-6">
5496
<div className="grid gap-2">
55-
<Label htmlFor='organization-selector'>Organization</Label>
97+
<Label htmlFor="organization-selector">Organization</Label>
5698

5799
<DynamicApiSelect<Organization>
58100
url="/organizations"
59101
params={{ userId }}
60102
onChange={handleOrganizationSelection}
61103
placeholder="Select an organization"
62104
getOptionLabel={(org) => org.name}
63-
getOptionValue={(org) => org.id.toString()}
64-
elementId='organization-selector'
105+
getOptionValue={(org) => org.id} // FIXME: this doesn't seem to display the currently selected organization when the page loads and the org.id is set
106+
elementId="organization-selector"
65107
/>
66108
</div>
67-
{organizationId && (
109+
{organization.id.length > 0 && (
68110
<div className="grid gap-2">
69-
<Label htmlFor='relationship-selector'>Relationship</Label>
111+
<Label htmlFor="relationship-selector">Relationship</Label>
70112

71113
<DynamicApiSelect<CoachingRelationshipWithUserNames>
72114
url={`/organizations/${organizationId}/coaching_relationships`}
73115
params={{ organizationId }}
74116
onChange={handleRelationshipSelection}
75117
placeholder="Select coaching relationship"
76-
getOptionLabel={
77-
(relationship) =>
78-
`${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}`
118+
getOptionLabel={(relationship) =>
119+
`${relationship.coach_first_name} ${relationship.coach_last_name} -> ${relationship.coachee_first_name} ${relationship.coach_last_name}`
79120
}
80-
getOptionValue={(relationship) => relationship.id.toString()}
81-
elementId='relationship-selector'
121+
getOptionValue={(relationship) => relationship.id}
122+
elementId="relationship-selector"
82123
/>
83124
</div>
84125
)}
85-
{relationshipId && (
126+
{relationship.id.length > 0 && (
86127
<div className="grid gap-2">
87-
<Label htmlFor='session-selector'>Coaching Session</Label>
128+
<Label htmlFor="session-selector">Coaching Session</Label>
88129

89130
<DynamicApiSelect<CoachingSession>
90131
url="/coaching_sessions"
91132
params={{
92-
coaching_relationship_id: relationshipId,
133+
coaching_relationship_id: relationship.id,
93134
from_date: FROM_DATE,
94-
to_Date: TO_DATE
135+
to_Date: TO_DATE,
95136
}}
96137
onChange={handleSessionSelection}
97138
placeholder="Select coaching session"
98-
getOptionLabel={(session) => session.date.toString()}
99-
getOptionValue={(session) => session.id.toString()}
100-
elementId='session-selector'
139+
getOptionLabel={(session) => session.date}
140+
getOptionValue={(session) => session.id}
141+
elementId="session-selector"
101142
groupByDate={true}
102143
/>
103144
</div>
104145
)}
105-
{sessionId && (
106-
<div className='grid gap-2'>
107-
<Button variant='outline' className='w-full'>
108-
<Link href={`/coaching-sessions/${sessionId}`}>
146+
{coachingSession.id.length > 0 && (
147+
<div className="grid gap-2">
148+
<Button variant="outline" className="w-full">
149+
<Link href={`/coaching-sessions/${coachingSessionId}`}>
109150
Join Session
110151
</Link>
111152
</Button>
112153
</div>
113154
)}
114155
</CardContent>
115156
</Card>
116-
)
157+
);
117158
}

0 commit comments

Comments
 (0)