Skip to content

Commit

Permalink
split choosing relationship and coaching sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
calebbourg committed Feb 6, 2025
1 parent a0f6206 commit 703fe78
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 149 deletions.
12 changes: 6 additions & 6 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
# will have no effect on the values passed into the containers as those are
# all set in the backend's .env file.

# NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL="http"
# NEXT_PUBLIC_BACKEND_SERVICE_PORT=4000
# NEXT_PUBLIC_BACKEND_SERVICE_HOST="localhost"
# NEXT_PUBLIC_BACKEND_API_VERSION="0.0.1"
NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL="http"
NEXT_PUBLIC_BACKEND_SERVICE_PORT=4000
NEXT_PUBLIC_BACKEND_SERVICE_HOST="localhost"
NEXT_PUBLIC_BACKEND_API_VERSION="0.0.1"

# FRONTEND_SERVICE_INTERFACE=0.0.0.0
# FRONTEND_SERVICE_PORT=3000
FRONTEND_SERVICE_INTERFACE=0.0.0.0
FRONTEND_SERVICE_PORT=3000
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 22 additions & 21 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
import { Metadata } from "next";

import * as React from "react";

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

import SelectCoachingSession from "@/components/ui/dashboard/select-coaching-session";
import type { Metadata } from "next"
import type * as React from "react"
import { cn } from "@/lib/utils"
import SelectCoachingRelationship from "@/components/ui/dashboard/select-coaching-relationship"
import CoachingSessionList from "@/components/ui/dashboard/coaching-session-list"

export const metadata: Metadata = {
title: "Dashboard",
description: "Coaching dashboard",
};
}

function DashboardContainer({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
function DashboardContainer({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn(
"flex items-center justify-center [&>div]:w-full",
className
// Base styles
"p-4",
// Mobile: stack vertically
"flex flex-col gap-6",
// Tablet and up (640px+): side by side
"sm:grid sm:grid-cols-2",
// Ensure full width for children
"[&>*]:w-full",
className,
)}
{...props}
/>
);
)
}

export default function DashboardPage() {
return (
<div className="items-start justify-center gap-6 rounded-lg p-8 md:grid lg:grid-cols-2 xl:grid-cols-3">
<DashboardContainer>
<SelectCoachingSession />
</DashboardContainer>
</div>
);
<DashboardContainer>
<SelectCoachingRelationship />
<CoachingSessionList />
</DashboardContainer>
)
}
71 changes: 71 additions & 0 deletions src/components/ui/coaching-session.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client"

import React from 'react';
import { format } from 'date-fns';
import { Card, CardHeader } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider";
import { useOverarchingGoalByCoachingSessionId } from "@/lib/api/overarching-goals";
import { Id } from '@/types/general';

interface CoachingSessionProps {
coachingSession: {
id: Id;
date: string;
};
}

const CoachingSession: React.FC<CoachingSessionProps> = ({ coachingSession }) => {
const { setCurrentCoachingSessionId } = useCoachingSessionStateStore((state) => state);

return (
<Card>
<CardHeader className="p-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div className="space-y-1">
<OverarchingGoal coachingSessionId={coachingSession.id} />
<div className="text-sm text-muted-foreground">
{format(new Date(coachingSession.date), "MMMM d, yyyy h:mm a")}
</div>
</div>
<Link href={`/coaching-sessions/${coachingSession.id}`} passHref>
<Button
size="sm"
className="w-full sm:w-auto mt-2 sm:mt-0"
onClick={() => setCurrentCoachingSessionId(coachingSession.id)}
>
Join Session
</Button>
</Link>
</div>
</CardHeader>
</Card>
);
};

interface OverarchingGoalProps {
coachingSessionId: Id;
}

const OverarchingGoal: React.FC<OverarchingGoalProps> = ({ coachingSessionId }) => {
const {
overarchingGoal,
isLoading: isLoadingOverarchingGoal,
isError: isErrorOverarchingGoal,
} = useOverarchingGoalByCoachingSessionId(coachingSessionId);

let titleText: string;

if (isLoadingOverarchingGoal) {
titleText = "Loading...";
} else if (isErrorOverarchingGoal) {
titleText = "Error loading Overarching Goal";
} else {
titleText = overarchingGoal?.title || "No goal set";
}

return <div>{titleText}</div>;
};

export default CoachingSession;
123 changes: 123 additions & 0 deletions src/components/ui/dashboard/coaching-session-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use client"

import { useState, useEffect, useCallback } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ArrowUpDown, CalendarPlus } from "lucide-react"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import CoachingSession from "@/components/ui/coaching-session"
import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"
import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider";
import { createCoachingSession, useCoachingSessions } from "@/lib/api/coaching-sessions";


export default function CoachingSessionList() {
const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore((state) => state)
const {
coachingSessions,
isLoading: isLoadingCoachingSessions,
isError: isErrorCoachingSessions,
} = useCoachingSessions(currentCoachingRelationshipId);
const { setCurrentCoachingSessions } = useCoachingSessionStateStore(
(state) => state
);

const [sortByDate, setSortByDate] = useState(true)
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [newSessionDate, setNewSessionDate] = useState("")

useEffect(() => {
if (!coachingSessions.length) return;
setCurrentCoachingSessions(coachingSessions);
}, [coachingSessions]);

const handleCreateSession = (e: React.FormEvent) => {
e.preventDefault()
createCoachingSession(currentCoachingRelationshipId, newSessionDate).then((createdCoachingSession) => {
setIsDialogOpen(false)
setNewSessionDate("")
setCurrentCoachingSessions([...coachingSessions, createdCoachingSession])
}).catch((err) => {
console.error("Failed to create new Coaching Session: " + err);
throw err;
});
}

const sortedSessions = [...coachingSessions].sort((a, b) => {
return new Date(b.date).getTime() - new Date(a.date).getTime()
})

if (!currentCoachingRelationshipId) return <div>Choose a Coaching Relationship to View Coaching Sessions</div>
if (isLoadingCoachingSessions) return <div>Loading coaching sessions...</div>
if (isErrorCoachingSessions) return <div>Error loading coaching sessions</div>

return (
<Card className="flex-1">
<CardHeader className="space-y-4">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<CardTitle className="text-xl sm:text-2xl">Coaching Sessions</CardTitle>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="w-full sm:w-auto"
disabled={!currentCoachingRelationshipId}
>
<CalendarPlus className="mr-2 h-4 w-4" />
Create New Coaching Session
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Coaching Session</DialogTitle>
</DialogHeader>
<form onSubmit={handleCreateSession} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="session-date">Session Date and Time</Label>
<Input
id="session-date"
type="datetime-local"
required
value={newSessionDate}
onChange={(e) => setNewSessionDate(e.target.value)}
/>
</div>
<Button type="submit">Create Session</Button>
</form>
</DialogContent>
</Dialog>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
<Button
variant="ghost"
size="sm"
className="text-muted-foreground w-full sm:w-auto justify-between"
onClick={() => setSortByDate(true)}
>
<span>Date and Time</span>
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
className="text-muted-foreground w-full sm:w-auto justify-between"
onClick={() => setSortByDate(false)}
>
<span>Overarching Goal</span>
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{sortedSessions.map((coachingSession) => (
<CoachingSession key={coachingSession.id} coachingSession={coachingSession} />
))}
</div>
</CardContent>
</Card>
)
}
35 changes: 35 additions & 0 deletions src/components/ui/dashboard/select-coaching-relationship.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client"

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { useAuthStore } from "@/lib/providers/auth-store-provider"
import OrganizationSelector from "../organization-selector"
import CoachingRelationshipSelector from "../coaching-relationship-selector"
import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider"
import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"

export default function SelectCoachingRelationship() {
const { userId } = useAuthStore((state) => state)
const { currentOrganizationId } = useOrganizationStateStore((state) => state)
const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore((state) => state)

return (
<Card className="w-full">
<CardHeader className="space-y-2">
<CardTitle className="text-xl sm:text-2xl">Choose a Coaching Relationship</CardTitle>
<CardDescription className="text-sm">Select current organization and relationship</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="organization">Organization</Label>
<OrganizationSelector userId={userId} />
</div>

<div className="space-y-2">
<Label htmlFor="relationship">Relationship</Label>
<CoachingRelationshipSelector organizationId={currentOrganizationId} disabled={!currentOrganizationId} />
</div>
</CardContent>
</Card>
)
}
Loading

0 comments on commit 703fe78

Please sign in to comment.