Files
myeasycms-v2/packages/features/sitzungsprotokolle/src/components/meetings-dashboard.tsx
Zaid Marzguioui ebd0fd4638
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m26s
Workflow / ⚫️ Test (push) Has been skipped
feat: complete CMS v2 with Docker, Fischerei, Meetings, Verband modules + UX audit fixes
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
2026-03-31 16:35:46 +02:00

211 lines
7.3 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.

'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>
);
}