Files
myeasycms-v2/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
T. Zehetbauer abac22feb1
Some checks failed
Workflow / ⚫️ Test (push) Has been cancelled
Workflow / ʦ TypeScript (push) Has been cancelled
feat: enhance accessibility and testing with data-test attributes and improve error handling
2026-04-01 10:46:44 +02:00

260 lines
9.2 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,
} 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';
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">
{/* 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>
);
}