Skip to content

Commit 703fe78

Browse files
committed
split choosing relationship and coaching sessions
1 parent a0f6206 commit 703fe78

File tree

9 files changed

+287
-149
lines changed

9 files changed

+287
-149
lines changed

.env

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
# will have no effect on the values passed into the containers as those are
44
# all set in the backend's .env file.
55

6-
# NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL="http"
7-
# NEXT_PUBLIC_BACKEND_SERVICE_PORT=4000
8-
# NEXT_PUBLIC_BACKEND_SERVICE_HOST="localhost"
9-
# NEXT_PUBLIC_BACKEND_API_VERSION="0.0.1"
6+
NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL="http"
7+
NEXT_PUBLIC_BACKEND_SERVICE_PORT=4000
8+
NEXT_PUBLIC_BACKEND_SERVICE_HOST="localhost"
9+
NEXT_PUBLIC_BACKEND_API_VERSION="0.0.1"
1010

11-
# FRONTEND_SERVICE_INTERFACE=0.0.0.0
12-
# FRONTEND_SERVICE_PORT=3000
11+
FRONTEND_SERVICE_INTERFACE=0.0.0.0
12+
FRONTEND_SERVICE_PORT=3000

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/dashboard/page.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
1-
import { Metadata } from "next";
2-
3-
import * as React from "react";
4-
5-
import { cn } from "@/lib/utils";
6-
7-
import SelectCoachingSession from "@/components/ui/dashboard/select-coaching-session";
1+
import type { Metadata } from "next"
2+
import type * as React from "react"
3+
import { cn } from "@/lib/utils"
4+
import SelectCoachingRelationship from "@/components/ui/dashboard/select-coaching-relationship"
5+
import CoachingSessionList from "@/components/ui/dashboard/coaching-session-list"
86

97
export const metadata: Metadata = {
108
title: "Dashboard",
119
description: "Coaching dashboard",
12-
};
10+
}
1311

14-
function DashboardContainer({
15-
className,
16-
...props
17-
}: React.HTMLAttributes<HTMLDivElement>) {
12+
function DashboardContainer({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
1813
return (
1914
<div
2015
className={cn(
21-
"flex items-center justify-center [&>div]:w-full",
22-
className
16+
// Base styles
17+
"p-4",
18+
// Mobile: stack vertically
19+
"flex flex-col gap-6",
20+
// Tablet and up (640px+): side by side
21+
"sm:grid sm:grid-cols-2",
22+
// Ensure full width for children
23+
"[&>*]:w-full",
24+
className,
2325
)}
2426
{...props}
2527
/>
26-
);
28+
)
2729
}
2830

2931
export default function DashboardPage() {
3032
return (
31-
<div className="items-start justify-center gap-6 rounded-lg p-8 md:grid lg:grid-cols-2 xl:grid-cols-3">
32-
<DashboardContainer>
33-
<SelectCoachingSession />
34-
</DashboardContainer>
35-
</div>
36-
);
33+
<DashboardContainer>
34+
<SelectCoachingRelationship />
35+
<CoachingSessionList />
36+
</DashboardContainer>
37+
)
3738
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use client"
2+
3+
import React from 'react';
4+
import { format } from 'date-fns';
5+
import { Card, CardHeader } from '@/components/ui/card';
6+
import { Button } from '@/components/ui/button';
7+
import Link from 'next/link';
8+
import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider";
9+
import { useOverarchingGoalByCoachingSessionId } from "@/lib/api/overarching-goals";
10+
import { Id } from '@/types/general';
11+
12+
interface CoachingSessionProps {
13+
coachingSession: {
14+
id: Id;
15+
date: string;
16+
};
17+
}
18+
19+
const CoachingSession: React.FC<CoachingSessionProps> = ({ coachingSession }) => {
20+
const { setCurrentCoachingSessionId } = useCoachingSessionStateStore((state) => state);
21+
22+
return (
23+
<Card>
24+
<CardHeader className="p-4">
25+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
26+
<div className="space-y-1">
27+
<OverarchingGoal coachingSessionId={coachingSession.id} />
28+
<div className="text-sm text-muted-foreground">
29+
{format(new Date(coachingSession.date), "MMMM d, yyyy h:mm a")}
30+
</div>
31+
</div>
32+
<Link href={`/coaching-sessions/${coachingSession.id}`} passHref>
33+
<Button
34+
size="sm"
35+
className="w-full sm:w-auto mt-2 sm:mt-0"
36+
onClick={() => setCurrentCoachingSessionId(coachingSession.id)}
37+
>
38+
Join Session
39+
</Button>
40+
</Link>
41+
</div>
42+
</CardHeader>
43+
</Card>
44+
);
45+
};
46+
47+
interface OverarchingGoalProps {
48+
coachingSessionId: Id;
49+
}
50+
51+
const OverarchingGoal: React.FC<OverarchingGoalProps> = ({ coachingSessionId }) => {
52+
const {
53+
overarchingGoal,
54+
isLoading: isLoadingOverarchingGoal,
55+
isError: isErrorOverarchingGoal,
56+
} = useOverarchingGoalByCoachingSessionId(coachingSessionId);
57+
58+
let titleText: string;
59+
60+
if (isLoadingOverarchingGoal) {
61+
titleText = "Loading...";
62+
} else if (isErrorOverarchingGoal) {
63+
titleText = "Error loading Overarching Goal";
64+
} else {
65+
titleText = overarchingGoal?.title || "No goal set";
66+
}
67+
68+
return <div>{titleText}</div>;
69+
};
70+
71+
export default CoachingSession;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"use client"
2+
3+
import { useState, useEffect, useCallback } from "react"
4+
import { Button } from "@/components/ui/button"
5+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
6+
import { ArrowUpDown, CalendarPlus } from "lucide-react"
7+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
8+
import { Input } from "@/components/ui/input"
9+
import { Label } from "@/components/ui/label"
10+
import CoachingSession from "@/components/ui/coaching-session"
11+
import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"
12+
import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider";
13+
import { createCoachingSession, useCoachingSessions } from "@/lib/api/coaching-sessions";
14+
15+
16+
export default function CoachingSessionList() {
17+
const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore((state) => state)
18+
const {
19+
coachingSessions,
20+
isLoading: isLoadingCoachingSessions,
21+
isError: isErrorCoachingSessions,
22+
} = useCoachingSessions(currentCoachingRelationshipId);
23+
const { setCurrentCoachingSessions } = useCoachingSessionStateStore(
24+
(state) => state
25+
);
26+
27+
const [sortByDate, setSortByDate] = useState(true)
28+
const [isDialogOpen, setIsDialogOpen] = useState(false)
29+
const [newSessionDate, setNewSessionDate] = useState("")
30+
31+
useEffect(() => {
32+
if (!coachingSessions.length) return;
33+
setCurrentCoachingSessions(coachingSessions);
34+
}, [coachingSessions]);
35+
36+
const handleCreateSession = (e: React.FormEvent) => {
37+
e.preventDefault()
38+
createCoachingSession(currentCoachingRelationshipId, newSessionDate).then((createdCoachingSession) => {
39+
setIsDialogOpen(false)
40+
setNewSessionDate("")
41+
setCurrentCoachingSessions([...coachingSessions, createdCoachingSession])
42+
}).catch((err) => {
43+
console.error("Failed to create new Coaching Session: " + err);
44+
throw err;
45+
});
46+
}
47+
48+
const sortedSessions = [...coachingSessions].sort((a, b) => {
49+
return new Date(b.date).getTime() - new Date(a.date).getTime()
50+
})
51+
52+
if (!currentCoachingRelationshipId) return <div>Choose a Coaching Relationship to View Coaching Sessions</div>
53+
if (isLoadingCoachingSessions) return <div>Loading coaching sessions...</div>
54+
if (isErrorCoachingSessions) return <div>Error loading coaching sessions</div>
55+
56+
return (
57+
<Card className="flex-1">
58+
<CardHeader className="space-y-4">
59+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
60+
<CardTitle className="text-xl sm:text-2xl">Coaching Sessions</CardTitle>
61+
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
62+
<DialogTrigger asChild>
63+
<Button
64+
variant="outline"
65+
size="sm"
66+
className="w-full sm:w-auto"
67+
disabled={!currentCoachingRelationshipId}
68+
>
69+
<CalendarPlus className="mr-2 h-4 w-4" />
70+
Create New Coaching Session
71+
</Button>
72+
</DialogTrigger>
73+
<DialogContent>
74+
<DialogHeader>
75+
<DialogTitle>Create New Coaching Session</DialogTitle>
76+
</DialogHeader>
77+
<form onSubmit={handleCreateSession} className="space-y-4">
78+
<div className="space-y-2">
79+
<Label htmlFor="session-date">Session Date and Time</Label>
80+
<Input
81+
id="session-date"
82+
type="datetime-local"
83+
required
84+
value={newSessionDate}
85+
onChange={(e) => setNewSessionDate(e.target.value)}
86+
/>
87+
</div>
88+
<Button type="submit">Create Session</Button>
89+
</form>
90+
</DialogContent>
91+
</Dialog>
92+
</div>
93+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
94+
<Button
95+
variant="ghost"
96+
size="sm"
97+
className="text-muted-foreground w-full sm:w-auto justify-between"
98+
onClick={() => setSortByDate(true)}
99+
>
100+
<span>Date and Time</span>
101+
<ArrowUpDown className="ml-2 h-4 w-4" />
102+
</Button>
103+
<Button
104+
variant="ghost"
105+
size="sm"
106+
className="text-muted-foreground w-full sm:w-auto justify-between"
107+
onClick={() => setSortByDate(false)}
108+
>
109+
<span>Overarching Goal</span>
110+
<ArrowUpDown className="ml-2 h-4 w-4" />
111+
</Button>
112+
</div>
113+
</CardHeader>
114+
<CardContent>
115+
<div className="space-y-4">
116+
{sortedSessions.map((coachingSession) => (
117+
<CoachingSession key={coachingSession.id} coachingSession={coachingSession} />
118+
))}
119+
</div>
120+
</CardContent>
121+
</Card>
122+
)
123+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client"
2+
3+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
4+
import { Label } from "@/components/ui/label"
5+
import { useAuthStore } from "@/lib/providers/auth-store-provider"
6+
import OrganizationSelector from "../organization-selector"
7+
import CoachingRelationshipSelector from "../coaching-relationship-selector"
8+
import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider"
9+
import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"
10+
11+
export default function SelectCoachingRelationship() {
12+
const { userId } = useAuthStore((state) => state)
13+
const { currentOrganizationId } = useOrganizationStateStore((state) => state)
14+
const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore((state) => state)
15+
16+
return (
17+
<Card className="w-full">
18+
<CardHeader className="space-y-2">
19+
<CardTitle className="text-xl sm:text-2xl">Choose a Coaching Relationship</CardTitle>
20+
<CardDescription className="text-sm">Select current organization and relationship</CardDescription>
21+
</CardHeader>
22+
<CardContent className="space-y-6">
23+
<div className="space-y-2">
24+
<Label htmlFor="organization">Organization</Label>
25+
<OrganizationSelector userId={userId} />
26+
</div>
27+
28+
<div className="space-y-2">
29+
<Label htmlFor="relationship">Relationship</Label>
30+
<CoachingRelationshipSelector organizationId={currentOrganizationId} disabled={!currentOrganizationId} />
31+
</div>
32+
</CardContent>
33+
</Card>
34+
)
35+
}

0 commit comments

Comments
 (0)