- {booking.check_in
- ? new Date(
- String(booking.check_in),
- ).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(booking.check_in)}
- {booking.check_out
- ? new Date(
- String(booking.check_out),
- ).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(booking.check_out)}
1 && !searchQuery && (
-
+
Seite {page} von {totalPages} ({total} Einträge)
{page > 1 ? (
-
+
Zurück
@@ -293,9 +292,7 @@ export default async function BookingsPage({ params, searchParams }: PageProps)
)}
{page < totalPages ? (
-
+
Weiter
diff --git a/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx
index ab05ccb88..853cc6f62 100644
--- a/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx
@@ -1,15 +1,14 @@
import { BedDouble, Plus } from 'lucide-react';
+import { createBookingManagementApi } from '@kit/booking-management/api';
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 { createBookingManagementApi } from '@kit/booking-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
@@ -63,26 +62,37 @@ export default async function RoomsPage({ params }: PageProps) {
-
+
Zimmernr.
Name
Typ
Kapazität
- Preis/Nacht
+
+ Preis/Nacht
+
Aktiv
{rooms.map((room: Record) => (
-
+
{String(room.room_number ?? '—')}
- {String(room.name ?? '—')}
-
- {String(room.room_type ?? '—')}
+
+ {String(room.name ?? '—')}
+
+
+
+ {String(room.room_type ?? '—')}
+
+
+
+ {String(room.capacity ?? '—')}
- {String(room.capacity ?? '—')}
{room.price_per_night != null
? `${Number(room.price_per_night).toFixed(2)} €`
diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/page.tsx
index 09e4db878..e0668428b 100644
--- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/page.tsx
@@ -1,11 +1,11 @@
import { ClipboardCheck, Calendar } 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 { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createCourseManagementApi } from '@kit/course-management/api';
-
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
@@ -14,7 +14,10 @@ interface PageProps {
searchParams: Promise>;
}
-export default async function AttendancePage({ params, searchParams }: PageProps) {
+export default async function AttendancePage({
+ params,
+ searchParams,
+}: PageProps) {
const { account, courseId } = await params;
const search = await searchParams;
const client = getSupabaseServerClient();
@@ -28,14 +31,21 @@ export default async function AttendancePage({ params, searchParams }: PageProps
if (!course) return Kurs nicht gefunden
;
- const selectedSessionId = (search.session as string) ?? (sessions.length > 0 ? String((sessions[0] as Record).id) : null);
+ const selectedSessionId =
+ (search.session as string) ??
+ (sessions.length > 0
+ ? String((sessions[0] as Record).id)
+ : null);
const attendance = selectedSessionId
? await api.getAttendance(selectedSessionId)
: [];
const attendanceMap = new Map(
- attendance.map((a: Record) => [String(a.participant_id), Boolean(a.present)]),
+ attendance.map((a: Record) => [
+ String(a.participant_id),
+ Boolean(a.present),
+ ]),
);
return (
@@ -70,9 +80,12 @@ export default async function AttendancePage({ params, searchParams }: PageProps
key={String(s.id)}
href={`/home/${account}/courses/${courseId}/attendance?session=${String(s.id)}`}
>
-
+
{s.session_date
- ? new Date(String(s.session_date)).toLocaleDateString('de-DE')
+ ? formatDate(s.session_date as string)
: String(s.id)}
@@ -92,28 +105,38 @@ export default async function AttendancePage({ params, searchParams }: PageProps
{participants.length === 0 ? (
-
+
Keine Teilnehmer in diesem Kurs
) : (
-
- Teilnehmer
- Anwesend
+
+
+ Teilnehmer
+
+
+ Anwesend
+
{participants.map((p: Record) => (
-
+
- {String(p.last_name ?? '')}, {String(p.first_name ?? '')}
+ {String(p.last_name ?? '')},{' '}
+ {String(p.first_name ?? '')}
diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
index ae98008b0..79e20c0d2 100644
--- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
@@ -1,14 +1,21 @@
import Link from 'next/link';
-import { GraduationCap, Users, Calendar, Euro, User, Clock } from 'lucide-react';
+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 { createCourseManagementApi } from '@kit/course-management/api';
-
import { CmsPageShell } from '~/components/cms-page-shell';
interface PageProps {
@@ -16,13 +23,22 @@ interface PageProps {
}
const STATUS_LABEL: Record = {
- planned: 'Geplant', open: 'Offen', running: 'Laufend',
- completed: 'Abgeschlossen', cancelled: 'Abgesagt',
+ planned: 'Geplant',
+ open: 'Offen',
+ running: 'Laufend',
+ completed: 'Abgeschlossen',
+ cancelled: 'Abgesagt',
};
-const STATUS_VARIANT: Record = {
- planned: 'secondary', open: 'default', running: 'info',
- completed: 'outline', cancelled: 'destructive',
+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) {
@@ -47,19 +63,21 @@ export default async function CourseDetailPage({ params }: PageProps) {
-
+
-
Name
+
Name
{String(c.name)}
-
+
-
Status
-
+ Status
+
{STATUS_LABEL[String(c.status)] ?? String(c.status)}
@@ -67,31 +85,33 @@ export default async function CourseDetailPage({ params }: PageProps) {
-
+
-
Dozent
-
{String(c.instructor_id ?? '—')}
-
-
-
-
-
-
-
-
Beginn – Ende
+
Dozent
- {c.start_date ? new Date(String(c.start_date)).toLocaleDateString('de-DE') : '—'}
- {' – '}
- {c.end_date ? new Date(String(c.end_date)).toLocaleDateString('de-DE') : '—'}
+ {String(c.instructor_id ?? '—')}
-
+
-
Gebühr
+
Beginn – Ende
+
+ {formatDate(c.start_date as string)}
+ {' – '}
+ {formatDate(c.end_date as string)}
+
+
+
+
+
+
+
+
+
Gebühr
{c.fee != null ? `${Number(c.fee).toFixed(2)} €` : '—'}
@@ -100,9 +120,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
-
+
-
Teilnehmer
+
Teilnehmer
{participants.length} / {String(c.capacity ?? '∞')}
@@ -116,14 +136,16 @@ export default async function CourseDetailPage({ params }: PageProps) {
Teilnehmer
- Alle anzeigen
+
+ Alle anzeigen
+
-
+
Name
E-Mail
Status
@@ -132,15 +154,36 @@ export default async function CourseDetailPage({ params }: PageProps) {
{participants.length === 0 ? (
- Keine Teilnehmer
- ) : participants.map((p: Record) => (
-
- {String(p.last_name ?? '')}, {String(p.first_name ?? '')}
- {String(p.email ?? '—')}
- {String(p.status ?? '—')}
- {p.enrolled_at ? new Date(String(p.enrolled_at)).toLocaleDateString('de-DE') : '—'}
+
+
+ Keine Teilnehmer
+
- ))}
+ ) : (
+ participants.map((p: Record) => (
+
+
+ {String(p.last_name ?? '')},{' '}
+ {String(p.first_name ?? '')}
+
+ {String(p.email ?? '—')}
+
+
+ {String(p.status ?? '—')}
+
+
+
+ {formatDate(p.enrolled_at as string)}
+
+
+ ))
+ )}
@@ -152,14 +195,16 @@ export default async function CourseDetailPage({ params }: PageProps) {
Termine
- Anwesenheit
+
+ Anwesenheit
+
-
+
Datum
Beginn
Ende
@@ -168,15 +213,35 @@ export default async function CourseDetailPage({ params }: PageProps) {
{sessions.length === 0 ? (
- Keine Termine
- ) : sessions.map((s: Record) => (
-
- {s.session_date ? new Date(String(s.session_date)).toLocaleDateString('de-DE') : '—'}
- {String(s.start_time ?? '—')}
- {String(s.end_time ?? '—')}
- {s.cancelled ? Ja : '—'}
+
+
+ Keine Termine
+
- ))}
+ ) : (
+ sessions.map((s: Record) => (
+
+
+ {formatDate(s.session_date as string)}
+
+ {String(s.start_time ?? '—')}
+ {String(s.end_time ?? '—')}
+
+ {s.cancelled ? (
+ Ja
+ ) : (
+ '—'
+ )}
+
+
+ ))
+ )}
diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx
index 8810bb981..00770f520 100644
--- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx
@@ -2,13 +2,13 @@ import Link from 'next/link';
import { Plus, Users } 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 { createCourseManagementApi } from '@kit/course-management/api';
-
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
@@ -16,7 +16,10 @@ interface PageProps {
params: Promise<{ account: string; courseId: string }>;
}
-const STATUS_VARIANT: Record = {
+const STATUS_VARIANT: Record<
+ string,
+ 'secondary' | 'default' | 'info' | 'outline' | 'destructive'
+> = {
enrolled: 'default',
waitlisted: 'secondary',
cancelled: 'destructive',
@@ -49,7 +52,8 @@ export default async function ParticipantsPage({ params }: PageProps) {
Teilnehmer
- {String((course as Record).name)} — {participants.length} Teilnehmer
+ {String((course as Record).name)} —{' '}
+ {participants.length} Teilnehmer
@@ -74,31 +78,39 @@ export default async function ParticipantsPage({ params }: PageProps) {
-
+
Name
E-Mail
Telefon
Status
- Anmeldedatum
+
+ Anmeldedatum
+
{participants.map((p: Record) => (
-
+
- {String(p.last_name ?? '')}, {String(p.first_name ?? '')}
+ {String(p.last_name ?? '')},{' '}
+ {String(p.first_name ?? '')}
{String(p.email ?? '—')}
{String(p.phone ?? '—')}
-
+
{STATUS_LABEL[String(p.status)] ?? String(p.status)}
- {p.enrolled_at
- ? new Date(String(p.enrolled_at)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(p.enrolled_at as string)}
))}
diff --git a/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx b/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx
index 9be29c505..f7af78aa1 100644
--- a/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx
@@ -2,15 +2,15 @@ import Link from 'next/link';
import { ArrowLeft, ChevronLeft, ChevronRight } 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 { createCourseManagementApi } from '@kit/course-management/api';
-
-import { CmsPageShell } from '~/components/cms-page-shell';
import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
interface PageProps {
params: Promise<{ account: string }>;
@@ -86,7 +86,11 @@ export default async function CourseCalendarPage({ params }: PageProps) {
}
// Build calendar grid
- const cells: Array<{ day: number | null; hasCourse: boolean; isToday: boolean }> = [];
+ const cells: Array<{
+ day: number | null;
+ hasCourse: boolean;
+ isToday: boolean;
+ }> = [];
for (let i = 0; i < firstWeekday; i++) {
cells.push({ day: null, hasCourse: false, isToday: false });
@@ -96,7 +100,10 @@ export default async function CourseCalendarPage({ params }: PageProps) {
cells.push({
day: d,
hasCourse: courseDates.has(d),
- isToday: d === now.getDate() && month === now.getMonth() && year === now.getFullYear(),
+ isToday:
+ d === now.getDate() &&
+ month === now.getMonth() &&
+ year === now.getFullYear(),
});
}
@@ -120,9 +127,7 @@ export default async function CourseCalendarPage({ params }: PageProps) {
-
- Kurstermine im Überblick
-
+ Kurstermine im Überblick
@@ -143,11 +148,11 @@ export default async function CourseCalendarPage({ params }: PageProps) {
{/* Weekday Header */}
-
+
{WEEKDAYS.map((day) => (
{day}
@@ -163,15 +168,15 @@ export default async function CourseCalendarPage({ params }: PageProps) {
cell.day === null
? 'bg-transparent'
: cell.hasCourse
- ? 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 font-semibold'
+ ? 'bg-emerald-500/15 font-semibold text-emerald-700 dark:text-emerald-400'
: 'bg-muted/30 hover:bg-muted/50'
- } ${cell.isToday ? 'ring-2 ring-primary ring-offset-1' : ''}`}
+ } ${cell.isToday ? 'ring-primary ring-2 ring-offset-1' : ''}`}
>
{cell.day !== null && (
<>
{cell.day}
{cell.hasCourse && (
-
+
)}
>
)}
@@ -180,17 +185,17 @@ export default async function CourseCalendarPage({ params }: PageProps) {
{/* Legend */}
-
+
Kurstag
-
+
Frei
-
+
Heute
@@ -204,7 +209,7 @@ export default async function CourseCalendarPage({ params }: PageProps) {
{activeCourses.length === 0 ? (
-
+
Keine aktiven Kurse in diesem Monat.
) : (
@@ -221,18 +226,19 @@ export default async function CourseCalendarPage({ params }: PageProps) {
>
{String(course.name)}
-
- {course.start_date
- ? new Date(String(course.start_date)).toLocaleDateString('de-DE')
- : '—'}{' '}
- –{' '}
- {course.end_date
- ? new Date(String(course.end_date)).toLocaleDateString('de-DE')
- : '—'}
+
+ {formatDate(course.start_date as string)} –{' '}
+ {formatDate(course.end_date as string)}
-
- {String(course.status) === 'running' ? 'Laufend' : 'Offen'}
+
+ {String(course.status) === 'running'
+ ? 'Laufend'
+ : 'Offen'}
))}
diff --git a/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx b/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx
index 3a0b7f08d..a89033e75 100644
--- a/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx
@@ -1,14 +1,13 @@
import { FolderTree, Plus } from 'lucide-react';
+import { createCourseManagementApi } from '@kit/course-management/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createCourseManagementApi } from '@kit/course-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
@@ -56,17 +55,24 @@ export default async function CategoriesPage({ params }: PageProps) {
-
+
Name
- Beschreibung
- Übergeordnet
+
+ Beschreibung
+
+
+ Übergeordnet
+
{categories.map((cat: Record) => (
-
+
{String(cat.name)}
-
+
{String(cat.description ?? '—')}
{String(cat.parent_id ?? '—')}
diff --git a/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx b/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx
index ed1187999..d839d71ab 100644
--- a/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx
@@ -1,14 +1,13 @@
import { GraduationCap, Plus } from 'lucide-react';
+import { createCourseManagementApi } from '@kit/course-management/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createCourseManagementApi } from '@kit/course-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
@@ -56,23 +55,33 @@ export default async function InstructorsPage({ params }: PageProps) {
-
+
Name
E-Mail
Telefon
- Qualifikation
- Stundensatz
+
+ Qualifikation
+
+
+ Stundensatz
+
{instructors.map((inst: Record) => (
-
+
- {String(inst.last_name ?? '')}, {String(inst.first_name ?? '')}
+ {String(inst.last_name ?? '')},{' '}
+ {String(inst.first_name ?? '')}
{String(inst.email ?? '—')}
{String(inst.phone ?? '—')}
- {String(inst.qualification ?? '—')}
+
+ {String(inst.qualification ?? '—')}
+
{inst.hourly_rate != null
? `${Number(inst.hourly_rate).toFixed(2)} €`
diff --git a/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx b/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx
index f02c163b1..fabb7c09d 100644
--- a/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx
@@ -1,14 +1,13 @@
import { MapPin, Plus } from 'lucide-react';
+import { createCourseManagementApi } from '@kit/course-management/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createCourseManagementApi } from '@kit/course-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
@@ -33,7 +32,9 @@ export default async function LocationsPage({ params }: PageProps) {
-
Kurs- und Veranstaltungsorte verwalten
+
+ Kurs- und Veranstaltungsorte verwalten
+
Neuer Ort
@@ -56,7 +57,7 @@ export default async function LocationsPage({ params }: PageProps) {
-
+
Name
Adresse
Raum
@@ -65,7 +66,10 @@ export default async function LocationsPage({ params }: PageProps) {
{locations.map((loc: Record) => (
-
+
{String(loc.name)}
{[loc.street, loc.postal_code, loc.city]
@@ -74,7 +78,9 @@ export default async function LocationsPage({ params }: PageProps) {
.join(', ') || '—'}
{String(loc.room ?? '—')}
- {String(loc.capacity ?? '—')}
+
+ {String(loc.capacity ?? '—')}
+
))}
diff --git a/apps/web/app/[locale]/home/[account]/courses/new/page.tsx b/apps/web/app/[locale]/home/[account]/courses/new/page.tsx
index 2285c7550..eb0de0d55 100644
--- a/apps/web/app/[locale]/home/[account]/courses/new/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/new/page.tsx
@@ -1,18 +1,29 @@
-import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { CreateCourseForm } from '@kit/course-management/components';
-import { CmsPageShell } from '~/components/cms-page-shell';
-import { AccountNotFound } from '~/components/account-not-found';
+import { getSupabaseServerClient } from '@kit/supabase/server-client';
-interface Props { params: Promise<{ account: string }> }
+import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
+
+interface Props {
+ params: Promise<{ account: string }>;
+}
export default async function NewCoursePage({ params }: Props) {
const { account } = await params;
const client = getSupabaseServerClient();
- const { data: acct } = await client.from('accounts').select('id').eq('slug', account).single();
+ const { data: acct } = await client
+ .from('accounts')
+ .select('id')
+ .eq('slug', account)
+ .single();
if (!acct) return ;
return (
-
+
);
diff --git a/apps/web/app/[locale]/home/[account]/courses/page.tsx b/apps/web/app/[locale]/home/[account]/courses/page.tsx
index c1fc46744..9e1d2fb3d 100644
--- a/apps/web/app/[locale]/home/[account]/courses/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/page.tsx
@@ -1,19 +1,30 @@
import Link from 'next/link';
-import { ChevronLeft, ChevronRight, GraduationCap, Plus, Users, Calendar, Euro } from 'lucide-react';
+import {
+ ChevronLeft,
+ ChevronRight,
+ GraduationCap,
+ Plus,
+ Users,
+ Calendar,
+ Euro,
+} 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 { createCourseManagementApi } from '@kit/course-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
import { StatsCard } from '~/components/stats-card';
-import { AccountNotFound } from '~/components/account-not-found';
-import { COURSE_STATUS_VARIANT, COURSE_STATUS_LABEL } from '~/lib/status-badges';
+import {
+ COURSE_STATUS_VARIANT,
+ COURSE_STATUS_LABEL,
+} from '~/lib/status-badges';
interface PageProps {
params: Promise<{ account: string }>;
@@ -50,9 +61,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {
{/* Header */}
-
- Kursangebot verwalten
-
+
Kursangebot verwalten
@@ -104,7 +113,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {
-
+
Kursnr.
Name
Beginn
@@ -116,7 +125,10 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {
{courses.data.map((course: Record) => (
-
+
{String(course.course_number ?? '—')}
@@ -129,20 +141,20 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {
- {course.start_date
- ? new Date(String(course.start_date)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(course.start_date as string)}
- {course.end_date
- ? new Date(String(course.end_date)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(course.end_date as string)}
- {COURSE_STATUS_LABEL[String(course.status)] ?? String(course.status)}
+ {COURSE_STATUS_LABEL[String(course.status)] ??
+ String(course.status)}
@@ -164,7 +176,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {
{/* Pagination */}
{totalPages > 1 && (
-
+
Seite {page} von {totalPages} ({courses.total} Einträge)
diff --git a/apps/web/app/[locale]/home/[account]/courses/statistics/page.tsx b/apps/web/app/[locale]/home/[account]/courses/statistics/page.tsx
index acb2d8007..f6ed23f08 100644
--- a/apps/web/app/[locale]/home/[account]/courses/statistics/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/statistics/page.tsx
@@ -1,14 +1,19 @@
-import { GraduationCap, Users, Calendar, TrendingUp, BarChart3 } from 'lucide-react';
+import {
+ GraduationCap,
+ Users,
+ Calendar,
+ TrendingUp,
+ BarChart3,
+} from 'lucide-react';
+import { createCourseManagementApi } from '@kit/course-management/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createCourseManagementApi } from '@kit/course-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { StatsCard } from '~/components/stats-card';
import { StatsBarChart, StatsPieChart } from '~/components/stats-charts';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
@@ -18,7 +23,11 @@ export default async function CourseStatisticsPage({ params }: PageProps) {
const { account } = await params;
const client = getSupabaseServerClient();
- const { data: acct } = await client.from('accounts').select('id').eq('slug', account).single();
+ const { data: acct } = await client
+ .from('accounts')
+ .select('id')
+ .eq('slug', account)
+ .single();
if (!acct) return
;
const api = createCourseManagementApi(client);
@@ -34,10 +43,26 @@ export default async function CourseStatisticsPage({ params }: PageProps) {
- } />
- } />
- } />
- } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
diff --git a/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx b/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx
index 79be8df3b..c148fe694 100644
--- a/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx
+++ b/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx
@@ -73,7 +73,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
setSelectedType(e.target.value);
setResult(null);
}}
- className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
+ className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
>
Mitgliedsausweis
Rechnung
@@ -92,7 +92,8 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
Demnächst verfügbar
Die Generierung von “{DOCUMENT_LABELS[selectedType]}”
- befindet sich noch in Entwicklung und wird in Kürze verfügbar sein.
+ befindet sich noch in Entwicklung und wird in Kürze verfügbar
+ sein.
@@ -118,7 +119,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
id="format"
name="format"
disabled={isPending}
- className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50"
+ className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-50"
>
A4
A5
@@ -131,7 +132,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
id="orientation"
name="orientation"
disabled={isPending}
- className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50"
+ className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-50"
>
Hochformat
Querformat
@@ -140,7 +141,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
{/* Hint */}
-
+
Hinweis: {' '}
{selectedType === 'member-card'
@@ -211,11 +212,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
* Trigger a browser download from a base64 string.
* Uses an anchor element with the download attribute set to the full filename.
*/
-function downloadFile(
- base64Data: string,
- mimeType: string,
- fileName: string,
-) {
+function downloadFile(base64Data: string, mimeType: string, fileName: string) {
const byteCharacters = atob(base64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
diff --git a/apps/web/app/[locale]/home/[account]/documents/_lib/server/generate-document.ts b/apps/web/app/[locale]/home/[account]/documents/_lib/server/generate-document.ts
index 094bffb75..b7f1ac978 100644
--- a/apps/web/app/[locale]/home/[account]/documents/_lib/server/generate-document.ts
+++ b/apps/web/app/[locale]/home/[account]/documents/_lib/server/generate-document.ts
@@ -2,8 +2,10 @@
import React from 'react';
-import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createDocumentGeneratorApi } from '@kit/document-generator/api';
+import { formatDate } from '@kit/shared/dates';
+import { getLogger } from '@kit/shared/logger';
+import { getSupabaseServerClient } from '@kit/supabase/server-client';
export type GenerateDocumentInput = {
accountSlug: string;
@@ -55,7 +57,11 @@ export async function generateDocumentAction(
return { success: false, error: 'Unbekannter Dokumenttyp.' };
}
} catch (err) {
- console.error('Document generation error:', err);
+ const logger = await getLogger();
+ logger.error(
+ { error: err, context: 'document-generation' },
+ 'Document generation error',
+ );
return {
success: false,
error: err instanceof Error ? err.message : 'Unbekannter Fehler.',
@@ -73,8 +79,7 @@ const LABELS: Record = {
};
function fmtDate(d: string | null): string {
- if (!d) return '–';
- try { return new Date(d).toLocaleDateString('de-DE'); } catch { return d; }
+ return formatDate(d);
}
// ═══════════════════════════════════════════════════════════════════════════
@@ -88,16 +93,28 @@ async function generateMemberCards(
): Promise {
const { data: members, error } = await client
.from('members')
- .select('id, member_number, first_name, last_name, entry_date, status, date_of_birth, street, house_number, postal_code, city, email')
+ .select(
+ 'id, member_number, first_name, last_name, entry_date, status, date_of_birth, street, house_number, postal_code, city, email',
+ )
.eq('account_id', accountId)
.eq('status', 'active')
.order('last_name');
if (error) return { success: false, error: `DB-Fehler: ${error.message}` };
- if (!members?.length) return { success: false, error: 'Keine aktiven Mitglieder.' };
+ if (!members?.length)
+ return { success: false, error: 'Keine aktiven Mitglieder.' };
- const { Document, Page, View, Text, StyleSheet, renderToBuffer, Svg, Rect, Circle } =
- await import('@react-pdf/renderer');
+ const {
+ Document,
+ Page,
+ View,
+ Text,
+ StyleSheet,
+ renderToBuffer,
+ Svg,
+ Rect,
+ Circle,
+ } = await import('@react-pdf/renderer');
// — Brand colors (configurable later via account settings) —
const PRIMARY = '#1e40af';
@@ -107,7 +124,13 @@ async function generateMemberCards(
const LIGHT_GRAY = '#f1f5f9';
const s = StyleSheet.create({
- page: { padding: 24, flexDirection: 'row', flexWrap: 'wrap', gap: 16, fontFamily: 'Helvetica' },
+ page: {
+ padding: 24,
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 16,
+ fontFamily: 'Helvetica',
+ },
// ── Card shell ──
card: {
@@ -138,10 +161,22 @@ async function generateMemberCards(
paddingHorizontal: 6,
paddingVertical: 2,
},
- badgeText: { fontSize: 6, color: PRIMARY, fontFamily: 'Helvetica-Bold', textTransform: 'uppercase' as const, letterSpacing: 0.8 },
+ badgeText: {
+ fontSize: 6,
+ color: PRIMARY,
+ fontFamily: 'Helvetica-Bold',
+ textTransform: 'uppercase' as const,
+ letterSpacing: 0.8,
+ },
// ── Main content ──
- body: { flexDirection: 'row', paddingHorizontal: 14, paddingTop: 8, gap: 12, flex: 1 },
+ body: {
+ flexDirection: 'row',
+ paddingHorizontal: 14,
+ paddingTop: 8,
+ gap: 12,
+ flex: 1,
+ },
// Photo column
photoCol: { width: 64, alignItems: 'center' },
@@ -165,11 +200,22 @@ async function generateMemberCards(
// Info column
infoCol: { flex: 1, justifyContent: 'center' },
- memberName: { fontSize: 14, fontFamily: 'Helvetica-Bold', color: DARK, marginBottom: 6 },
+ memberName: {
+ fontSize: 14,
+ fontFamily: 'Helvetica-Bold',
+ color: DARK,
+ marginBottom: 6,
+ },
fieldGroup: { flexDirection: 'row', flexWrap: 'wrap', gap: 4 },
field: { width: '48%', marginBottom: 5 },
- fieldLabel: { fontSize: 6, color: GRAY, textTransform: 'uppercase' as const, letterSpacing: 0.6, marginBottom: 1 },
+ fieldLabel: {
+ fontSize: 6,
+ color: GRAY,
+ textTransform: 'uppercase' as const,
+ letterSpacing: 0.6,
+ marginBottom: 1,
+ },
fieldValue: { fontSize: 8, color: DARK, fontFamily: 'Helvetica-Bold' },
// ── Footer ──
@@ -184,10 +230,16 @@ async function generateMemberCards(
},
footerLeft: { fontSize: 6, color: GRAY },
footerRight: { fontSize: 6, color: GRAY },
- validDot: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: '#22c55e', marginRight: 3 },
+ validDot: {
+ width: 5,
+ height: 5,
+ borderRadius: 2.5,
+ backgroundColor: '#22c55e',
+ marginRight: 3,
+ },
});
- const today = new Date().toLocaleDateString('de-DE');
+ const today = formatDate(new Date());
const year = new Date().getFullYear();
const cardsPerPage = 4;
const pages: React.ReactElement[] = [];
@@ -198,51 +250,118 @@ async function generateMemberCards(
pages.push(
React.createElement(
Page,
- { key: `p${i}`, size: input.format === 'letter' ? 'LETTER' : (input.format.toUpperCase() as 'A4'|'A5'), orientation: input.orientation, style: s.page },
+ {
+ key: `p${i}`,
+ size:
+ input.format === 'letter'
+ ? 'LETTER'
+ : (input.format.toUpperCase() as 'A4' | 'A5'),
+ orientation: input.orientation,
+ style: s.page,
+ },
...batch.map((m) =>
- React.createElement(View, { key: m.id, style: s.card },
+ React.createElement(
+ View,
+ { key: m.id, style: s.card },
// Accent bar
React.createElement(View, { style: s.accentBar }),
// Header
- React.createElement(View, { style: s.header },
+ React.createElement(
+ View,
+ { style: s.header },
React.createElement(Text, { style: s.clubName }, accountName),
- React.createElement(View, { style: s.badge },
- React.createElement(Text, { style: s.badgeText }, 'Mitgliedsausweis'),
+ React.createElement(
+ View,
+ { style: s.badge },
+ React.createElement(
+ Text,
+ { style: s.badgeText },
+ 'Mitgliedsausweis',
+ ),
),
),
// Body: photo + info
- React.createElement(View, { style: s.body },
+ React.createElement(
+ View,
+ { style: s.body },
// Photo column
- React.createElement(View, { style: s.photoCol },
- React.createElement(View, { style: s.photoFrame },
+ React.createElement(
+ View,
+ { style: s.photoCol },
+ React.createElement(
+ View,
+ { style: s.photoFrame },
React.createElement(Text, { style: s.photoIcon }, '👤'),
),
- React.createElement(Text, { style: s.memberNumber }, `Nr. ${m.member_number ?? '–'}`),
+ React.createElement(
+ Text,
+ { style: s.memberNumber },
+ `Nr. ${m.member_number ?? '–'}`,
+ ),
),
// Info column
- React.createElement(View, { style: s.infoCol },
- React.createElement(Text, { style: s.memberName }, `${m.first_name} ${m.last_name}`),
- React.createElement(View, { style: s.fieldGroup },
+ React.createElement(
+ View,
+ { style: s.infoCol },
+ React.createElement(
+ Text,
+ { style: s.memberName },
+ `${m.first_name} ${m.last_name}`,
+ ),
+ React.createElement(
+ View,
+ { style: s.fieldGroup },
// Entry date
- React.createElement(View, { style: s.field },
- React.createElement(Text, { style: s.fieldLabel }, 'Mitglied seit'),
- React.createElement(Text, { style: s.fieldValue }, fmtDate(m.entry_date)),
+ React.createElement(
+ View,
+ { style: s.field },
+ React.createElement(
+ Text,
+ { style: s.fieldLabel },
+ 'Mitglied seit',
+ ),
+ React.createElement(
+ Text,
+ { style: s.fieldValue },
+ fmtDate(m.entry_date),
+ ),
),
// Date of birth
- React.createElement(View, { style: s.field },
- React.createElement(Text, { style: s.fieldLabel }, 'Geb.-Datum'),
- React.createElement(Text, { style: s.fieldValue }, fmtDate(m.date_of_birth)),
+ React.createElement(
+ View,
+ { style: s.field },
+ React.createElement(
+ Text,
+ { style: s.fieldLabel },
+ 'Geb.-Datum',
+ ),
+ React.createElement(
+ Text,
+ { style: s.fieldValue },
+ fmtDate(m.date_of_birth),
+ ),
),
// Address
- React.createElement(View, { style: { ...s.field, width: '100%' } },
- React.createElement(Text, { style: s.fieldLabel }, 'Adresse'),
- React.createElement(Text, { style: s.fieldValue },
- [m.street, m.house_number].filter(Boolean).join(' ') || '–',
+ React.createElement(
+ View,
+ { style: { ...s.field, width: '100%' } },
+ React.createElement(
+ Text,
+ { style: s.fieldLabel },
+ 'Adresse',
),
- React.createElement(Text, { style: { ...s.fieldValue, marginTop: 1 } },
+ React.createElement(
+ Text,
+ { style: s.fieldValue },
+ [m.street, m.house_number].filter(Boolean).join(' ') ||
+ '–',
+ ),
+ React.createElement(
+ Text,
+ { style: { ...s.fieldValue, marginTop: 1 } },
[m.postal_code, m.city].filter(Boolean).join(' ') || '',
),
),
@@ -251,12 +370,24 @@ async function generateMemberCards(
),
// Footer
- React.createElement(View, { style: s.footer },
- React.createElement(View, { style: { flexDirection: 'row', alignItems: 'center' } },
+ React.createElement(
+ View,
+ { style: s.footer },
+ React.createElement(
+ View,
+ { style: { flexDirection: 'row', alignItems: 'center' } },
React.createElement(View, { style: s.validDot }),
- React.createElement(Text, { style: s.footerLeft }, `Gültig ${year}/${year + 1}`),
+ React.createElement(
+ Text,
+ { style: s.footerLeft },
+ `Gültig ${year}/${year + 1}`,
+ ),
+ ),
+ React.createElement(
+ Text,
+ { style: s.footerRight },
+ `Ausgestellt ${today}`,
),
- React.createElement(Text, { style: s.footerRight }, `Ausgestellt ${today}`),
),
),
),
@@ -285,17 +416,22 @@ async function generateLabels(
): Promise {
const { data: members, error } = await client
.from('members')
- .select('first_name, last_name, street, house_number, postal_code, city, salutation, title')
+ .select(
+ 'first_name, last_name, street, house_number, postal_code, city, salutation, title',
+ )
.eq('account_id', accountId)
.eq('status', 'active')
.order('last_name');
if (error) return { success: false, error: `DB-Fehler: ${error.message}` };
- if (!members?.length) return { success: false, error: 'Keine aktiven Mitglieder.' };
+ if (!members?.length)
+ return { success: false, error: 'Keine aktiven Mitglieder.' };
const api = createDocumentGeneratorApi();
const records = members.map((m) => ({
- line1: [m.salutation, m.title, m.first_name, m.last_name].filter(Boolean).join(' '),
+ line1: [m.salutation, m.title, m.first_name, m.last_name]
+ .filter(Boolean)
+ .join(' '),
line2: [m.street, m.house_number].filter(Boolean).join(' ') || undefined,
line3: [m.postal_code, m.city].filter(Boolean).join(' ') || undefined,
}));
@@ -320,7 +456,9 @@ async function generateMemberReport(
): Promise {
const { data: members, error } = await client
.from('members')
- .select('member_number, last_name, first_name, email, postal_code, city, status, entry_date')
+ .select(
+ 'member_number, last_name, first_name, email, postal_code, city, status, entry_date',
+ )
.eq('account_id', accountId)
.order('last_name');
@@ -346,11 +484,21 @@ async function generateMemberReport(
const hdr = ws.getRow(1);
hdr.font = { bold: true, color: { argb: 'FFFFFFFF' } };
- hdr.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF1E40AF' } };
+ hdr.fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FF1E40AF' },
+ };
hdr.alignment = { vertical: 'middle', horizontal: 'center' };
hdr.height = 24;
- const SL: Record = { active: 'Aktiv', inactive: 'Inaktiv', pending: 'Ausstehend', resigned: 'Ausgetreten', excluded: 'Ausgeschlossen' };
+ const SL: Record = {
+ active: 'Aktiv',
+ inactive: 'Inaktiv',
+ pending: 'Ausstehend',
+ resigned: 'Ausgetreten',
+ excluded: 'Ausgeschlossen',
+ };
for (const m of members) {
ws.addRow({
@@ -361,12 +509,17 @@ async function generateMemberReport(
plz: m.postal_code ?? '',
ort: m.city ?? '',
status: SL[m.status] ?? m.status,
- eintritt: m.entry_date ? new Date(m.entry_date).toLocaleDateString('de-DE') : '',
+ eintritt: m.entry_date ? formatDate(m.entry_date) : '',
});
}
ws.eachRow((row, n) => {
- if (n > 1 && n % 2 === 0) row.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF1F5F9' } };
+ if (n > 1 && n % 2 === 0)
+ row.fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFF1F5F9' },
+ };
row.border = { bottom: { style: 'thin', color: { argb: 'FFE2E8F0' } } };
});
@@ -379,7 +532,8 @@ async function generateMemberReport(
return {
success: true,
data: Buffer.from(buf as ArrayBuffer).toString('base64'),
- mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ mimeType:
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
fileName: `${input.title || 'Mitgliederbericht'}.xlsx`,
};
}
diff --git a/apps/web/app/[locale]/home/[account]/documents/generate/page.tsx b/apps/web/app/[locale]/home/[account]/documents/generate/page.tsx
index 5452d592d..ec140d23b 100644
--- a/apps/web/app/[locale]/home/[account]/documents/generate/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/documents/generate/page.tsx
@@ -13,10 +13,10 @@ import {
CardTitle,
} from '@kit/ui/card';
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { GenerateDocumentForm } from '../_components/generate-document-form';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
diff --git a/apps/web/app/[locale]/home/[account]/documents/page.tsx b/apps/web/app/[locale]/home/[account]/documents/page.tsx
index b480df5c1..a39066625 100644
--- a/apps/web/app/[locale]/home/[account]/documents/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/documents/page.tsx
@@ -13,8 +13,8 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { CmsPageShell } from '~/components/cms-page-shell';
import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
interface PageProps {
params: Promise<{ account: string }>;
@@ -40,32 +40,28 @@ const DOCUMENT_TYPES = [
{
id: 'labels',
title: 'Etiketten',
- description:
- 'Adressetiketten für Serienbriefe im Avery-Format drucken.',
+ description: 'Adressetiketten für Serienbriefe im Avery-Format drucken.',
icon: Tag,
color: 'text-orange-600 bg-orange-50',
},
{
id: 'report',
title: 'Bericht',
- description:
- 'Statistische Auswertungen und Berichte als PDF oder Excel.',
+ description: 'Statistische Auswertungen und Berichte als PDF oder Excel.',
icon: BarChart3,
color: 'text-purple-600 bg-purple-50',
},
{
id: 'letter',
title: 'Brief',
- description:
- 'Serienbriefe mit personalisierten Platzhaltern erstellen.',
+ description: 'Serienbriefe mit personalisierten Platzhaltern erstellen.',
icon: Mail,
color: 'text-rose-600 bg-rose-50',
},
{
id: 'certificate',
title: 'Zertifikat',
- description:
- 'Teilnahmebescheinigungen und Zertifikate mit Unterschrift.',
+ description: 'Teilnahmebescheinigungen und Zertifikate mit Unterschrift.',
icon: Award,
color: 'text-amber-600 bg-amber-50',
},
@@ -84,7 +80,11 @@ export default async function DocumentsPage({ params }: PageProps) {
if (!acct) return ;
return (
-
+
{/* Actions */}
@@ -108,7 +108,7 @@ export default async function DocumentsPage({ params }: PageProps) {
-
+
{docType.description}
;
@@ -69,7 +69,7 @@ export default async function DocumentTemplatesPage({ params }: PageProps) {
-
+
Name
Typ
@@ -81,11 +81,11 @@ export default async function DocumentTemplatesPage({ params }: PageProps) {
{templates.map((template) => (
{template.name}
{template.type}
-
+
{template.description}
diff --git a/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx b/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx
index 023998b27..b7aac80bf 100644
--- a/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx
@@ -9,13 +9,13 @@ import {
UserPlus,
} from 'lucide-react';
+import { createEventManagementApi } from '@kit/event-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 { createEventManagementApi } from '@kit/event-management/api';
-
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
@@ -32,7 +32,10 @@ const STATUS_LABEL: Record = {
completed: 'Abgeschlossen',
};
-const STATUS_VARIANT: Record = {
+const STATUS_VARIANT: Record<
+ string,
+ 'secondary' | 'default' | 'info' | 'outline' | 'destructive'
+> = {
draft: 'secondary',
published: 'default',
registration_open: 'info',
@@ -62,7 +65,10 @@ export default async function EventDetailPage({ params }: PageProps) {
{String(e.name)}
-
+
{STATUS_LABEL[String(e.status)] ?? String(e.status)}
@@ -76,22 +82,20 @@ export default async function EventDetailPage({ params }: PageProps) {
-
+
-
Datum
+
Datum
- {e.event_date
- ? new Date(String(e.event_date)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(e.event_date as string)}
-
+
-
Uhrzeit
+
Uhrzeit
{String(e.start_time ?? '—')} – {String(e.end_time ?? '—')}
@@ -100,18 +104,18 @@ export default async function EventDetailPage({ params }: PageProps) {
-
+
-
Ort
+
Ort
{String(e.location ?? '—')}
-
+
-
Anmeldungen
+
Anmeldungen
{registrations.length} / {String(e.capacity ?? '∞')}
@@ -127,7 +131,7 @@ export default async function EventDetailPage({ params }: PageProps) {
Beschreibung
-
+
{String(e.description)}
@@ -141,14 +145,14 @@ export default async function EventDetailPage({ params }: PageProps) {
{registrations.length === 0 ? (
-
+
Noch keine Anmeldungen
) : (
-
+
Name
E-Mail
Elternteil
@@ -157,16 +161,20 @@ export default async function EventDetailPage({ params }: PageProps) {
{registrations.map((reg: Record) => (
-
+
- {String(reg.last_name ?? '')}, {String(reg.first_name ?? '')}
+ {String(reg.last_name ?? '')},{' '}
+ {String(reg.first_name ?? '')}
{String(reg.email ?? '—')}
- {String(reg.parent_name ?? '—')}
- {reg.created_at
- ? new Date(String(reg.created_at)).toLocaleDateString('de-DE')
- : '—'}
+ {String(reg.parent_name ?? '—')}
+
+
+ {formatDate(reg.created_at as string)}
))}
diff --git a/apps/web/app/[locale]/home/[account]/events/holiday-passes/page.tsx b/apps/web/app/[locale]/home/[account]/events/holiday-passes/page.tsx
index f6387232f..b71fc54b1 100644
--- a/apps/web/app/[locale]/home/[account]/events/holiday-passes/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/events/holiday-passes/page.tsx
@@ -1,15 +1,15 @@
import { Ticket, Plus } from 'lucide-react';
-
import { getTranslations } from 'next-intl/server';
+
+import { createEventManagementApi } from '@kit/event-management/api';
+import { formatDate } from '@kit/shared/dates';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createEventManagementApi } from '@kit/event-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
interface PageProps {
params: Promise<{ account: string }>;
@@ -37,7 +37,9 @@ export default async function HolidayPassesPage({ params }: PageProps) {
{t('holidayPasses')}
-
{t('holidayPassesDescription')}
+
+ {t('holidayPassesDescription')}
+
@@ -55,23 +57,34 @@ export default async function HolidayPassesPage({ params }: PageProps) {
) : (
- {t('allHolidayPasses')} ({passes.length})
+
+ {t('allHolidayPasses')} ({passes.length})
+
-
+
{t('name')}
{t('year')}
- {t('price')}
- {t('validFrom')}
- {t('validUntil')}
+
+ {t('price')}
+
+
+ {t('validFrom')}
+
+
+ {t('validUntil')}
+
{passes.map((pass: Record) => (
-
+
{String(pass.name)}
{String(pass.year ?? '—')}
@@ -80,14 +93,10 @@ export default async function HolidayPassesPage({ params }: PageProps) {
: '—'}
- {pass.valid_from
- ? new Date(String(pass.valid_from)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(pass.valid_from as string)}
- {pass.valid_until
- ? new Date(String(pass.valid_until)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(pass.valid_until as string)}
))}
diff --git a/apps/web/app/[locale]/home/[account]/events/new/page.tsx b/apps/web/app/[locale]/home/[account]/events/new/page.tsx
index 59c2395b8..6ee3cdf5e 100644
--- a/apps/web/app/[locale]/home/[account]/events/new/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/events/new/page.tsx
@@ -1,20 +1,32 @@
import { getTranslations } from 'next-intl/server';
-import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import { CreateEventForm } from '@kit/event-management/components';
-import { CmsPageShell } from '~/components/cms-page-shell';
-import { AccountNotFound } from '~/components/account-not-found';
-interface Props { params: Promise<{ account: string }> }
+import { CreateEventForm } from '@kit/event-management/components';
+import { getSupabaseServerClient } from '@kit/supabase/server-client';
+
+import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
+
+interface Props {
+ params: Promise<{ account: string }>;
+}
export default async function NewEventPage({ params }: Props) {
const { account } = await params;
const client = getSupabaseServerClient();
const t = await getTranslations('cms.events');
- const { data: acct } = await client.from('accounts').select('id').eq('slug', account).single();
+ const { data: acct } = await client
+ .from('accounts')
+ .select('id')
+ .eq('slug', account)
+ .single();
if (!acct) return ;
return (
-
+
);
diff --git a/apps/web/app/[locale]/home/[account]/events/page.tsx b/apps/web/app/[locale]/home/[account]/events/page.tsx
index 707d3bc95..f2a354a4d 100644
--- a/apps/web/app/[locale]/home/[account]/events/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/events/page.tsx
@@ -1,19 +1,26 @@
import Link from 'next/link';
-import { CalendarDays, ChevronLeft, ChevronRight, MapPin, Plus, Users } from 'lucide-react';
-
+import {
+ CalendarDays,
+ ChevronLeft,
+ ChevronRight,
+ MapPin,
+ Plus,
+ Users,
+} from 'lucide-react';
import { getTranslations } from 'next-intl/server';
+
+import { createEventManagementApi } from '@kit/event-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 { createEventManagementApi } from '@kit/event-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
import { StatsCard } from '~/components/stats-card';
-import { AccountNotFound } from '~/components/account-not-found';
import { EVENT_STATUS_VARIANT, EVENT_STATUS_LABEL } from '~/lib/status-badges';
interface PageProps {
@@ -40,14 +47,14 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
const events = await api.listEvents(acct.id, { page });
// Fetch registration counts for all events on this page
- const eventIds = events.data.map((e: Record) => String(e.id));
+ const eventIds = events.data.map((e: Record) =>
+ String(e.id),
+ );
const registrationCounts = await api.getRegistrationCounts(eventIds);
// Pre-compute stats before rendering
const uniqueLocationCount = new Set(
- events.data
- .map((e: Record) => e.location)
- .filter(Boolean),
+ events.data.map((e: Record) => e.location).filter(Boolean),
).size;
const totalCapacity = events.data.reduce(
@@ -63,9 +70,7 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
{t('title')}
-
- {t('description')}
-
+
{t('description')}
@@ -107,19 +112,31 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
) : (
- {t('allEvents')} ({events.total})
+
+ {t('allEvents')} ({events.total})
+
-
+
{t('name')}
- {t('eventDate')}
- {t('eventLocation')}
- {t('capacity')}
- {t('status')}
- {t('registrations')}
+
+ {t('eventDate')}
+
+
+ {t('eventLocation')}
+
+
+ {t('capacity')}
+
+
+ {t('status')}
+
+
+ {t('registrations')}
+
@@ -130,7 +147,7 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
return (
- {event.event_date
- ? new Date(String(event.event_date)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(event.event_date as string)}
{String(event.location ?? '—')}
@@ -156,10 +171,12 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
- {EVENT_STATUS_LABEL[String(event.status)] ?? String(event.status)}
+ {EVENT_STATUS_LABEL[String(event.status)] ??
+ String(event.status)}
@@ -175,12 +192,17 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
{/* Pagination */}
{events.totalPages > 1 && (
-
- {t('paginationPage', { page: events.page, totalPages: events.totalPages })}
+
+ {t('paginationPage', {
+ page: events.page,
+ totalPages: events.totalPages,
+ })}
{events.page > 1 && (
-
+
{t('paginationPrevious')}
@@ -188,7 +210,9 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
)}
{events.page < events.totalPages && (
-
+
{t('paginationNext')}
diff --git a/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx b/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx
index e4ca2280f..cfdfd6fd3 100644
--- a/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx
@@ -1,18 +1,18 @@
import Link from 'next/link';
import { CalendarDays, ClipboardList, Users } from 'lucide-react';
-
import { getTranslations } from 'next-intl/server';
+
+import { createEventManagementApi } from '@kit/event-management/api';
+import { formatDate } from '@kit/shared/dates';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { createEventManagementApi } from '@kit/event-management/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
import { StatsCard } from '~/components/stats-card';
-import { AccountNotFound } from '~/components/account-not-found';
import { EVENT_STATUS_VARIANT, EVENT_STATUS_LABEL } from '~/lib/status-badges';
interface PageProps {
@@ -64,9 +64,7 @@ export default async function EventRegistrationsPage({ params }: PageProps) {
{/* Header */}
{t('registrations')}
-
- {t('registrationsOverview')}
-
+
{t('registrationsOverview')}
{/* Stats */}
@@ -108,17 +106,25 @@ export default async function EventRegistrationsPage({ params }: PageProps) {
-
+
{t('event')}
- {t('eventDate')}
- {t('status')}
- {t('capacity')}
+
+ {t('eventDate')}
+
+
+ {t('status')}
+
+
+ {t('capacity')}
+
{t('registrations')}
- {t('utilization')}
+
+ {t('utilization')}
+
@@ -133,7 +139,7 @@ export default async function EventRegistrationsPage({ params }: PageProps) {
return (
-
- {event.eventDate
- ? new Date(event.eventDate).toLocaleDateString(
- 'de-DE',
- )
- : '—'}
-
+ {formatDate(event.eventDate)}
{EVENT_STATUS_LABEL[event.status] ?? event.status}
diff --git a/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx b/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx
index 1f9f5612c..cde04ebef 100644
--- a/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx
@@ -2,15 +2,15 @@ import Link from 'next/link';
import { ArrowLeft, Send, CheckCircle } from 'lucide-react';
+import { createFinanceApi } from '@kit/finance/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 { createFinanceApi } from '@kit/finance/api';
-
-import { CmsPageShell } from '~/components/cms-page-shell';
import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
interface PageProps {
params: Promise<{ account: string; id: string }>;
@@ -87,7 +87,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
-
+
Empfänger
@@ -95,31 +95,23 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
-
+
Rechnungsdatum
- {invoice.issue_date
- ? new Date(
- String(invoice.issue_date),
- ).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(invoice.issue_date)}
-
+
Fälligkeitsdatum
- {invoice.due_date
- ? new Date(String(invoice.due_date)).toLocaleDateString(
- 'de-DE',
- )
- : '—'}
+ {formatDate(invoice.due_date)}
-
+
Gesamtbetrag
@@ -155,14 +147,14 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
{items.length === 0 ? (
-
+
Keine Positionen vorhanden.
) : (
-
+
Beschreibung
@@ -177,7 +169,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
{items.map((item) => (
{String(item.description ?? '—')}
@@ -199,7 +191,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
))}
-
+
Zwischensumme
diff --git a/apps/web/app/[locale]/home/[account]/finance/invoices/new/page.tsx b/apps/web/app/[locale]/home/[account]/finance/invoices/new/page.tsx
index dd5f7aebf..a3d78886e 100644
--- a/apps/web/app/[locale]/home/[account]/finance/invoices/new/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/invoices/new/page.tsx
@@ -1,18 +1,29 @@
-import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { CreateInvoiceForm } from '@kit/finance/components';
-import { CmsPageShell } from '~/components/cms-page-shell';
-import { AccountNotFound } from '~/components/account-not-found';
+import { getSupabaseServerClient } from '@kit/supabase/server-client';
-interface Props { params: Promise<{ account: string }> }
+import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
+
+interface Props {
+ params: Promise<{ account: string }>;
+}
export default async function NewInvoicePage({ params }: Props) {
const { account } = await params;
const client = getSupabaseServerClient();
- const { data: acct } = await client.from('accounts').select('id').eq('slug', account).single();
+ const { data: acct } = await client
+ .from('accounts')
+ .select('id')
+ .eq('slug', account)
+ .single();
if (!acct) return ;
return (
-
+
);
diff --git a/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx b/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx
index 60f81aeb6..20880192f 100644
--- a/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx
@@ -2,16 +2,16 @@ import Link from 'next/link';
import { FileText, Plus } from 'lucide-react';
+import { createFinanceApi } from '@kit/finance/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 { createFinanceApi } from '@kit/finance/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
import {
INVOICE_STATUS_VARIANT,
INVOICE_STATUS_LABEL,
@@ -77,7 +77,7 @@ export default async function InvoicesPage({ params }: PageProps) {
-
+
Nr.
Empfänger
Datum
@@ -92,7 +92,7 @@ export default async function InvoicesPage({ params }: PageProps) {
return (
- {invoice.issue_date
- ? new Date(
- String(invoice.issue_date),
- ).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(invoice.issue_date)}
- {invoice.due_date
- ? new Date(
- String(invoice.due_date),
- ).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(invoice.due_date)}
{invoice.total_amount != null
diff --git a/apps/web/app/[locale]/home/[account]/finance/page.tsx b/apps/web/app/[locale]/home/[account]/finance/page.tsx
index 8f3a708eb..9e65b89a4 100644
--- a/apps/web/app/[locale]/home/[account]/finance/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/page.tsx
@@ -2,17 +2,17 @@ import Link from 'next/link';
import { Landmark, FileText, Euro, ArrowRight, Plus } from 'lucide-react';
+import { createFinanceApi } from '@kit/finance/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 { createFinanceApi } from '@kit/finance/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
import { StatsCard } from '~/components/stats-card';
-import { AccountNotFound } from '~/components/account-not-found';
import {
BATCH_STATUS_VARIANT,
BATCH_STATUS_LABEL,
@@ -61,9 +61,7 @@ export default async function FinancePage({ params }: PageProps) {
Finanzen
-
- SEPA-Einzüge und Rechnungen
-
+
SEPA-Einzüge und Rechnungen
@@ -124,7 +122,7 @@ export default async function FinancePage({ params }: PageProps) {
-
+
Status
Typ
Betrag
@@ -135,15 +133,17 @@ export default async function FinancePage({ params }: PageProps) {
{batches.map((batch: Record) => (
- {BATCH_STATUS_LABEL[String(batch.status)] ?? String(batch.status)}
+ {BATCH_STATUS_LABEL[String(batch.status)] ??
+ String(batch.status)}
@@ -157,11 +157,7 @@ export default async function FinancePage({ params }: PageProps) {
: '—'}
- {batch.execution_date
- ? new Date(String(batch.execution_date)).toLocaleDateString('de-DE')
- : batch.created_at
- ? new Date(String(batch.created_at)).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(batch.execution_date ?? batch.created_at)}
))}
@@ -196,7 +192,7 @@ export default async function FinancePage({ params }: PageProps) {
-
+
Nr.
Empfänger
Betrag
@@ -207,7 +203,7 @@ export default async function FinancePage({ params }: PageProps) {
{invoices.map((invoice: Record) => (
;
@@ -120,12 +119,14 @@ export default async function PaymentsPage({ params }: PageProps) {
Offene Rechnungen
- 0 ? 'default' : 'secondary'}>
+ 0 ? 'default' : 'secondary'}
+ >
{openInvoices.length}
-
+
{openInvoices.length > 0
? `${openInvoices.length} Rechnungen mit einem Gesamtbetrag von ${formatCurrency(openTotal)} sind offen.`
: 'Keine offenen Rechnungen vorhanden.'}
@@ -147,7 +148,7 @@ export default async function PaymentsPage({ params }: PageProps) {
-
+
{batches.length > 0
? `${batches.length} SEPA-Einzüge mit einem Gesamtvolumen von ${formatCurrency(sepaTotal)}.`
: 'Keine SEPA-Einzüge vorhanden.'}
diff --git a/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx b/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx
index d93f3699d..a8de505f1 100644
--- a/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx
@@ -2,15 +2,15 @@ import Link from 'next/link';
import { ArrowLeft, Download } from 'lucide-react';
+import { createFinanceApi } from '@kit/finance/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 { createFinanceApi } from '@kit/finance/api';
-
-import { CmsPageShell } from '~/components/cms-page-shell';
import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
interface PageProps {
params: Promise<{ account: string; batchId: string }>;
@@ -95,9 +95,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
{/* Summary Card */}
-
- {String(batch.description ?? 'SEPA-Einzug')}
-
+ {String(batch.description ?? 'SEPA-Einzug')}
{STATUS_LABEL[status] ?? status}
@@ -105,7 +103,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
-
+
Typ
@@ -115,7 +113,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
-
+
Betrag
@@ -125,7 +123,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
-
+
Anzahl
@@ -133,15 +131,11 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
-
+
Ausführungsdatum
- {batch.execution_date
- ? new Date(
- String(batch.execution_date),
- ).toLocaleDateString('de-DE')
- : '—'}
+ {formatDate(batch.execution_date)}
@@ -162,14 +156,14 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
{items.length === 0 ? (
-
+
Keine Positionen vorhanden.
) : (
-
+
Name
IBAN
Betrag
@@ -182,7 +176,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
return (
{String(item.debtor_name ?? '—')}
diff --git a/apps/web/app/[locale]/home/[account]/finance/sepa/new/page.tsx b/apps/web/app/[locale]/home/[account]/finance/sepa/new/page.tsx
index 05675aee9..3eda3ad41 100644
--- a/apps/web/app/[locale]/home/[account]/finance/sepa/new/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/sepa/new/page.tsx
@@ -1,8 +1,8 @@
+import { CreateSepaBatchForm } from '@kit/finance/components';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import { CreateSepaBatchForm } from '@kit/finance/components';
-import { CmsPageShell } from '~/components/cms-page-shell';
import { AccountNotFound } from '~/components/account-not-found';
+import { CmsPageShell } from '~/components/cms-page-shell';
interface Props {
params: Promise<{ account: string }>;
@@ -21,7 +21,11 @@ export default async function NewSepaBatchPage({ params }: Props) {
if (!acct) return ;
return (
-
+
);
diff --git a/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx b/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx
index e68d597ca..96159c50c 100644
--- a/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx
@@ -2,20 +2,17 @@ import Link from 'next/link';
import { Landmark, Plus } from 'lucide-react';
+import { createFinanceApi } from '@kit/finance/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 { createFinanceApi } from '@kit/finance/api';
-
+import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
-import { AccountNotFound } from '~/components/account-not-found';
-import {
- BATCH_STATUS_VARIANT,
- BATCH_STATUS_LABEL,
-} from '~/lib/status-badges';
+import { BATCH_STATUS_VARIANT, BATCH_STATUS_LABEL } from '~/lib/status-badges';
interface PageProps {
params: Promise<{ account: string }>;
@@ -79,7 +76,7 @@ export default async function SepaPage({ params }: PageProps) {