Files
myeasycms-v2/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
T. Zehetbauer c6b2824da8
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 4m53s
Workflow / ⚫️ Test (push) Has been skipped
feat: add update and delete functionality for courses, events, and species; enhance attendance tracking and category creation
2026-04-01 16:03:50 +02:00

274 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Link from 'next/link';
import {
GraduationCap,
Users,
Calendar,
Euro,
User,
Clock,
Pencil,
} from 'lucide-react';
import { createCourseManagementApi } from '@kit/course-management/api';
import { formatDate } from '@kit/shared/dates';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { DeleteCourseButton } from './delete-course-button';
interface PageProps {
params: Promise<{ account: string; courseId: string }>;
}
const STATUS_LABEL: Record<string, string> = {
planned: 'Geplant',
open: 'Offen',
running: 'Laufend',
completed: 'Abgeschlossen',
cancelled: 'Abgesagt',
};
const STATUS_VARIANT: Record<
string,
'secondary' | 'default' | 'info' | 'outline' | 'destructive'
> = {
planned: 'secondary',
open: 'default',
running: 'info',
completed: 'outline',
cancelled: 'destructive',
};
export default async function CourseDetailPage({ params }: PageProps) {
const { account, courseId } = await params;
const client = getSupabaseServerClient();
const api = createCourseManagementApi(client);
const [course, participants, sessions] = await Promise.all([
api.getCourse(courseId),
api.getParticipants(courseId),
api.getSessions(courseId),
]);
if (!course) return <AccountNotFound />;
const courseData = course as Record<string, unknown>;
return (
<CmsPageShell account={account} title={String(courseData.name)}>
<div className="flex w-full flex-col gap-6">
{/* Action Buttons */}
<div className="flex justify-end gap-2">
<Button asChild variant="outline" size="sm">
<Link href={`/home/${account}/courses/${courseId}/edit`}>
<Pencil className="mr-2 h-4 w-4" />
Bearbeiten
</Link>
</Button>
<DeleteCourseButton courseId={courseId} accountSlug={account} />
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<Card>
<CardContent className="flex items-center gap-3 p-4">
<GraduationCap className="text-primary h-5 w-5" />
<div>
<p className="text-muted-foreground text-xs">Name</p>
<p className="font-semibold">{String(courseData.name)}</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<Clock className="text-primary h-5 w-5" />
<div>
<p className="text-muted-foreground text-xs">Status</p>
<Badge
variant={
STATUS_VARIANT[String(courseData.status)] ?? 'secondary'
}
>
{STATUS_LABEL[String(courseData.status)] ??
String(courseData.status)}
</Badge>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<User className="text-primary h-5 w-5" />
<div>
<p className="text-muted-foreground text-xs">Dozent</p>
<p className="font-semibold">
{String(courseData.instructor_id ?? '—')}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<Calendar className="text-primary h-5 w-5" />
<div>
<p className="text-muted-foreground text-xs">Beginn Ende</p>
<p className="font-semibold">
{formatDate(courseData.start_date as string)}
{' '}
{formatDate(courseData.end_date as string)}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<Euro className="text-primary h-5 w-5" />
<div>
<p className="text-muted-foreground text-xs">Gebühr</p>
<p className="font-semibold">
{courseData.fee != null
? `${Number(courseData.fee).toFixed(2)}`
: '—'}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<Users className="text-primary h-5 w-5" />
<div>
<p className="text-muted-foreground text-xs">Teilnehmer</p>
<p className="font-semibold">
{participants.length} / {String(courseData.capacity ?? '∞')}
</p>
</div>
</CardContent>
</Card>
</div>
{/* Teilnehmer Section */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Teilnehmer</CardTitle>
<Link href={`/home/${account}/courses/${courseId}/participants`}>
<Button variant="outline" size="sm">
Alle anzeigen
</Button>
</Link>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Name</th>
<th className="p-3 text-left font-medium">E-Mail</th>
<th className="p-3 text-left font-medium">Status</th>
<th className="p-3 text-left font-medium">Datum</th>
</tr>
</thead>
<tbody>
{participants.length === 0 ? (
<tr>
<td
colSpan={4}
className="text-muted-foreground p-6 text-center"
>
Keine Teilnehmer
</td>
</tr>
) : (
participants.map((p: Record<string, unknown>) => (
<tr
key={String(p.id)}
className="hover:bg-muted/30 border-b"
>
<td className="p-3 font-medium">
{String(p.last_name ?? '')},{' '}
{String(p.first_name ?? '')}
</td>
<td className="p-3">{String(p.email ?? '—')}</td>
<td className="p-3">
<Badge variant="outline">
{String(p.status ?? '—')}
</Badge>
</td>
<td className="p-3">
{formatDate(p.enrolled_at as string)}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</CardContent>
</Card>
{/* Termine Section */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Termine</CardTitle>
<Link href={`/home/${account}/courses/${courseId}/attendance`}>
<Button variant="outline" size="sm">
Anwesenheit
</Button>
</Link>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Datum</th>
<th className="p-3 text-left font-medium">Beginn</th>
<th className="p-3 text-left font-medium">Ende</th>
<th className="p-3 text-left font-medium">Abgesagt?</th>
</tr>
</thead>
<tbody>
{sessions.length === 0 ? (
<tr>
<td
colSpan={4}
className="text-muted-foreground p-6 text-center"
>
Keine Termine
</td>
</tr>
) : (
sessions.map((s: Record<string, unknown>) => (
<tr
key={String(s.id)}
className="hover:bg-muted/30 border-b"
>
<td className="p-3">
{formatDate(s.session_date as string)}
</td>
<td className="p-3">{String(s.start_time ?? '—')}</td>
<td className="p-3">{String(s.end_time ?? '—')}</td>
<td className="p-3">
{s.cancelled ? (
<Badge variant="destructive">Ja</Badge>
) : (
'—'
)}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
</CmsPageShell>
);
}