feat: complete CMS v2 with Docker, Fischerei, Meetings, Verband modules + UX audit fixes
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m26s
Workflow / ⚫️ Test (push) Has been skipped

Major changes:
- Docker Compose: full Supabase stack (11 services) equivalent to supabase CLI
- Fischerei module: 16 DB tables, waters/species/stocking/catch books/competitions
- Sitzungsprotokolle module: meeting protocols, agenda items, task tracking
- Verbandsverwaltung module: federation management, member clubs, contacts, fees
- Per-account module activation via Modules page toggle
- Site Builder: live CMS data in Puck blocks (courses, events, membership registration)
- Public registration APIs: course signup, event registration, membership application
- Document generation: PDF member cards, Excel reports, HTML labels
- Landing page: real Com.BISS content (no filler text)
- UX audit fixes: AccountNotFound component, shared status badges, confirm dialog,
  pagination, duplicate heading removal, emoji→badge replacement, a11y fixes
- QA: healthcheck fix, API auth fix, enum mismatch fix, password required attribute
This commit is contained in:
Zaid Marzguioui
2026-03-31 16:35:46 +02:00
parent 16648c92eb
commit ebd0fd4638
176 changed files with 17133 additions and 981 deletions

View File

@@ -0,0 +1,210 @@
'use client';
import Link from 'next/link';
import {
FileText,
Calendar,
ListChecks,
AlertTriangle,
} from 'lucide-react';
import { Badge } from '@kit/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { MEETING_TYPE_LABELS } from '../lib/meetings-constants';
interface DashboardStats {
totalProtocols: number;
thisYearProtocols: number;
openTasks: number;
overdueTasks: number;
}
interface RecentProtocol {
id: string;
title: string;
meeting_date: string;
meeting_type: string;
is_published: boolean;
}
interface OverdueTask {
id: string;
title: string;
responsible_person: string | null;
due_date: string | null;
status: string;
meeting_protocols: {
id: string;
title: string;
};
}
interface MeetingsDashboardProps {
stats: DashboardStats;
recentProtocols: RecentProtocol[];
overdueTasks: OverdueTask[];
account: string;
}
export function MeetingsDashboard({
stats,
recentProtocols,
overdueTasks,
account,
}: MeetingsDashboardProps) {
return (
<div className="flex w-full flex-col gap-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold">Sitzungsprotokolle Übersicht</h1>
<p className="text-muted-foreground">
Protokolle, Tagesordnungspunkte und Aufgaben verwalten
</p>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Link href={`/home/${account}/meetings/protocols`}>
<Card className="cursor-pointer transition-shadow hover:shadow-md">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Protokolle gesamt</p>
<p className="text-2xl font-bold">{stats.totalProtocols}</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<FileText className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
</Link>
<Link href={`/home/${account}/meetings/protocols`}>
<Card className="cursor-pointer transition-shadow hover:shadow-md">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Protokolle dieses Jahr</p>
<p className="text-2xl font-bold">{stats.thisYearProtocols}</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<Calendar className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
</Link>
<Link href={`/home/${account}/meetings/tasks`}>
<Card className="cursor-pointer transition-shadow hover:shadow-md">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Offene Aufgaben</p>
<p className="text-2xl font-bold">{stats.openTasks}</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<ListChecks className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
</Link>
<Link href={`/home/${account}/meetings/tasks`}>
<Card className="cursor-pointer transition-shadow hover:shadow-md">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Überfällige Aufgaben</p>
<p className="text-2xl font-bold">{stats.overdueTasks}</p>
</div>
<div className="rounded-full bg-destructive/10 p-3 text-destructive">
<AlertTriangle className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
</Link>
</div>
{/* Recent + Overdue Sections */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<Card>
<CardHeader>
<CardTitle className="text-base">Letzte Protokolle</CardTitle>
</CardHeader>
<CardContent>
{recentProtocols.length === 0 ? (
<p className="text-sm text-muted-foreground">
Noch keine Protokolle vorhanden.
</p>
) : (
<div className="space-y-3">
{recentProtocols.map((protocol) => (
<Link
key={protocol.id}
href={`/home/${account}/meetings/protocols/${protocol.id}`}
className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50"
>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{protocol.title}</p>
<p className="text-xs text-muted-foreground">
{new Date(protocol.meeting_date).toLocaleDateString('de-DE')}
{' · '}
{MEETING_TYPE_LABELS[protocol.meeting_type] ?? protocol.meeting_type}
</p>
</div>
{protocol.is_published && (
<Badge variant="default" className="ml-2 shrink-0">Veröffentlicht</Badge>
)}
</Link>
))}
</div>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Überfällige Aufgaben</CardTitle>
</CardHeader>
<CardContent>
{overdueTasks.length === 0 ? (
<p className="text-sm text-muted-foreground">
Keine überfälligen Aufgaben.
</p>
) : (
<div className="space-y-3">
{overdueTasks.map((task) => (
<div
key={task.id}
className="rounded-lg border border-destructive/20 bg-destructive/5 p-3"
>
<p className="text-sm font-medium">{task.title}</p>
<div className="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
{task.responsible_person && (
<span>Zuständig: {task.responsible_person}</span>
)}
{task.due_date && (
<span>
Fällig: {new Date(task.due_date).toLocaleDateString('de-DE')}
</span>
)}
</div>
<p className="mt-1 text-xs text-muted-foreground">
Protokoll: {task.meeting_protocols.title}
</p>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
);
}