From b26e5aaafa10e1775a0423adf69fa19cd2630357 Mon Sep 17 00:00:00 2001 From: Zaid Marzguioui Date: Thu, 2 Apr 2026 01:19:54 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20pre-existing=20local=20changes=20?= =?UTF-8?q?=E2=80=94=20fischerei,=20verband,=20modules,=20members,=20packa?= =?UTF-8?q?ges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commits all remaining uncommitted local work: - apps/web: fischerei, verband, modules, members-cms, documents, newsletter, meetings, site-builder, courses, bookings, events, finance pages and components - apps/web: marketing page updates, layout, paths config, next.config.mjs, styles/makerkit.css - apps/web/i18n: documents, fischerei, marketing, verband (de+en) - packages/features: finance, fischerei, member-management, module-builder, newsletter, sitzungsprotokolle, verbandsverwaltung server APIs and components - packages/ui: button.tsx updates - pnpm-lock.yaml --- .bg-shell/manifest.json | 1 + apps/web/.bg-shell/manifest.json | 1 + .../_components/animate-on-scroll.tsx | 37 ++ apps/web/app/[locale]/(marketing)/page.tsx | 624 ++++++++++-------- .../home/[account]/bookings/guests/page.tsx | 44 +- .../home/[account]/bookings/new/page.tsx | 9 +- .../home/[account]/bookings/rooms/page.tsx | 53 +- .../[courseId]/attendance/attendance-grid.tsx | 12 +- .../courses/[courseId]/attendance/page.tsx | 15 +- .../[courseId]/create-session-dialog.tsx | 14 +- .../[courseId]/delete-course-button.tsx | 24 +- .../courses/[courseId]/edit/page.tsx | 7 +- .../courses/[courseId]/participants/page.tsx | 52 +- .../categories/create-category-dialog.tsx | 6 +- .../[account]/courses/categories/page.tsx | 32 +- .../instructors/create-instructor-dialog.tsx | 8 +- .../[account]/courses/instructors/page.tsx | 43 +- .../locations/create-location-dialog.tsx | 8 +- .../home/[account]/courses/locations/page.tsx | 38 +- .../home/[account]/courses/new/page.tsx | 8 +- .../[account]/courses/statistics/page.tsx | 22 +- .../_lib/server/generate-document.ts | 9 +- .../[account]/documents/generate/page.tsx | 30 +- .../home/[account]/documents/page.tsx | 30 +- .../[account]/documents/templates/page.tsx | 37 +- .../events/[eventId]/delete-event-button.tsx | 14 +- .../[account]/events/[eventId]/edit/page.tsx | 8 +- .../[account]/events/holiday-passes/page.tsx | 23 +- .../home/[account]/files/files-table.tsx | 30 +- .../[locale]/home/[account]/files/page.tsx | 9 +- .../invoices/invoice-action-buttons.tsx | 141 ++++ .../[account]/finance/invoices/new/page.tsx | 7 +- .../home/[account]/finance/payments/page.tsx | 55 +- .../home/[account]/finance/sepa/new/page.tsx | 7 +- .../[account]/fischerei/catch-books/page.tsx | 5 +- .../fischerei/competitions/new/page.tsx | 5 +- .../[account]/fischerei/competitions/page.tsx | 5 +- .../[account]/fischerei/leases/new/page.tsx | 5 +- .../home/[account]/fischerei/leases/page.tsx | 13 +- .../home/[account]/fischerei/page.tsx | 5 +- .../[account]/fischerei/permits/new/page.tsx | 5 +- .../home/[account]/fischerei/permits/page.tsx | 13 +- .../species/[speciesId]/edit/page.tsx | 5 +- .../[account]/fischerei/species/new/page.tsx | 5 +- .../home/[account]/fischerei/species/page.tsx | 5 +- .../[account]/fischerei/statistics/page.tsx | 6 +- .../stocking/[stockingId]/edit/page.tsx | 5 +- .../[account]/fischerei/stocking/new/page.tsx | 5 +- .../[account]/fischerei/stocking/page.tsx | 5 +- .../fischerei/waters/[waterId]/edit/page.tsx | 5 +- .../[account]/fischerei/waters/new/page.tsx | 5 +- .../home/[account]/fischerei/waters/page.tsx | 5 +- .../app/[locale]/home/[account]/layout.tsx | 1 - .../[locale]/home/[account]/meetings/page.tsx | 5 +- .../[account]/meetings/protocols/new/page.tsx | 6 +- .../[account]/meetings/protocols/page.tsx | 5 +- .../home/[account]/meetings/tasks/page.tsx | 9 +- .../members-cms/[memberId]/edit/page.tsx | 6 +- .../[account]/members-cms/[memberId]/page.tsx | 2 +- .../members-cms/applications/page.tsx | 7 +- .../home/[account]/members-cms/cards/page.tsx | 18 +- .../departments/create-department-dialog.tsx | 22 +- .../departments/delete-department-button.tsx | 93 +++ .../members-cms/departments/page.tsx | 35 +- .../home/[account]/members-cms/dues/page.tsx | 7 +- .../[account]/members-cms/import/page.tsx | 7 +- .../members-cms/invitations/page.tsx | 7 +- .../home/[account]/members-cms/new/page.tsx | 7 +- .../home/[account]/members-cms/page.tsx | 7 +- .../[account]/members-cms/statistics/page.tsx | 24 +- .../modules/[moduleId]/[recordId]/page.tsx | 6 +- .../[recordId]/record-detail-client.tsx | 24 +- .../[moduleId]/import/import-wizard.tsx | 8 +- .../modules/[moduleId]/import/page.tsx | 8 +- .../[account]/modules/[moduleId]/new/page.tsx | 6 +- .../[account]/modules/[moduleId]/page.tsx | 89 ++- .../settings/delete-module-button.tsx | 22 +- .../settings/module-permissions.tsx | 6 +- .../[moduleId]/settings/module-relations.tsx | 26 +- .../modules/[moduleId]/settings/page.tsx | 28 +- .../[locale]/home/[account]/modules/page.tsx | 19 +- .../dispatch-newsletter-button.tsx | 20 +- .../newsletter/[campaignId]/edit/page.tsx | 5 +- .../home/[account]/newsletter/new/page.tsx | 9 +- .../[account]/newsletter/templates/page.tsx | 37 +- apps/web/app/[locale]/home/[account]/page.tsx | 175 ++--- .../home/[account]/site-builder/new/page.tsx | 6 +- .../[account]/site-builder/posts/new/page.tsx | 6 +- .../[account]/site-builder/settings/page.tsx | 8 +- .../verband/clubs/[clubId]/edit/page.tsx | 5 +- .../[account]/verband/clubs/[clubId]/page.tsx | 15 +- .../home/[account]/verband/clubs/new/page.tsx | 5 +- .../home/[account]/verband/clubs/page.tsx | 5 +- .../home/[account]/verband/events/page.tsx | 7 +- .../home/[account]/verband/hierarchy/page.tsx | 7 +- .../home/[account]/verband/members/page.tsx | 7 +- .../[locale]/home/[account]/verband/page.tsx | 5 +- .../home/[account]/verband/reporting/page.tsx | 7 +- .../settings/_components/settings-content.tsx | 13 +- .../home/[account]/verband/settings/page.tsx | 5 +- .../_components/statistics-content.tsx | 1 - .../[account]/verband/statistics/page.tsx | 5 +- .../home/[account]/verband/templates/page.tsx | 7 +- apps/web/config/paths.config.ts | 2 + apps/web/i18n/messages/de/documents.json | 16 +- apps/web/i18n/messages/de/fischerei.json | 11 +- apps/web/i18n/messages/de/marketing.json | 19 + apps/web/i18n/messages/de/verband.json | 5 +- apps/web/i18n/messages/en/documents.json | 16 +- apps/web/i18n/messages/en/fischerei.json | 11 +- apps/web/i18n/messages/en/marketing.json | 19 + apps/web/i18n/messages/en/verband.json | 5 +- apps/web/next.config.mjs | 12 + apps/web/styles/makerkit.css | 33 + packages/features/finance/src/server/api.ts | 5 +- .../src/components/catch-books-data-table.tsx | 27 +- .../components/competitions-data-table.tsx | 21 +- .../src/components/create-water-form.tsx | 4 +- .../src/components/delete-confirm-button.tsx | 3 +- .../src/components/leases-data-table.tsx | 26 +- .../src/components/permits-data-table.tsx | 24 +- .../src/components/species-data-table.tsx | 23 +- .../src/components/stocking-data-table.tsx | 35 +- .../src/components/waters-data-table.tsx | 27 +- packages/features/fischerei/src/server/api.ts | 22 +- .../src/components/application-workflow.tsx | 20 +- .../src/components/dues-category-manager.tsx | 24 +- .../src/components/mandate-manager.tsx | 28 +- .../src/components/member-detail-view.tsx | 56 +- .../src/components/member-import-wizard.tsx | 4 +- .../src/components/members-data-table.tsx | 24 +- packages/features/module-builder/package.json | 4 +- .../src/components/module-table.tsx | 2 +- .../src/server/services/file.service.ts | 14 +- .../features/newsletter/src/server/api.ts | 5 +- .../src/components/index.ts | 2 +- .../src/components/meetings-dashboard.tsx | 9 +- .../src/components/open-tasks-view.tsx | 29 +- .../src/components/protocol-items-list.tsx | 30 +- .../src/components/protocols-data-table.tsx | 20 +- .../sitzungsprotokolle/src/server/api.ts | 47 +- .../src/components/club-contacts-manager.tsx | 31 +- .../src/components/club-fee-billing-table.tsx | 38 +- .../src/components/club-notes-list.tsx | 20 +- .../src/components/clubs-data-table.tsx | 28 +- .../components/cross-org-member-search.tsx | 28 +- .../src/components/hierarchy-events.tsx | 32 +- .../src/components/hierarchy-report.tsx | 30 +- .../src/components/shared-templates.tsx | 24 +- .../src/server/actions/verband-actions.ts | 9 +- .../verbandsverwaltung/src/server/api.ts | 150 ++--- packages/ui/src/shadcn/button.tsx | 40 +- pnpm-lock.yaml | 7 +- 153 files changed, 2329 insertions(+), 1227 deletions(-) create mode 100644 .bg-shell/manifest.json create mode 100644 apps/web/.bg-shell/manifest.json create mode 100644 apps/web/app/[locale]/(marketing)/_components/animate-on-scroll.tsx create mode 100644 apps/web/app/[locale]/home/[account]/finance/invoices/invoice-action-buttons.tsx create mode 100644 apps/web/app/[locale]/home/[account]/members-cms/departments/delete-department-button.tsx diff --git a/.bg-shell/manifest.json b/.bg-shell/manifest.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/.bg-shell/manifest.json @@ -0,0 +1 @@ +[] diff --git a/apps/web/.bg-shell/manifest.json b/apps/web/.bg-shell/manifest.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/apps/web/.bg-shell/manifest.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/apps/web/app/[locale]/(marketing)/_components/animate-on-scroll.tsx b/apps/web/app/[locale]/(marketing)/_components/animate-on-scroll.tsx new file mode 100644 index 000000000..ad3347216 --- /dev/null +++ b/apps/web/app/[locale]/(marketing)/_components/animate-on-scroll.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { useEffect, useRef } from 'react'; + +export function AnimateOnScroll(props: { + children: React.ReactNode; + className?: string; +}) { + const ref = useRef(null); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const reveals = el.querySelectorAll('.reveal'); + reveals.forEach((r) => r.setAttribute('data-visible', 'true')); + observer.disconnect(); + } + } + }, + { threshold: 0.15 }, + ); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + + return ( +
+ {props.children} +
+ ); +} diff --git a/apps/web/app/[locale]/(marketing)/page.tsx b/apps/web/app/[locale]/(marketing)/page.tsx index a79736f12..810f6b07c 100644 --- a/apps/web/app/[locale]/(marketing)/page.tsx +++ b/apps/web/app/[locale]/(marketing)/page.tsx @@ -3,7 +3,6 @@ import Link from 'next/link'; import { ArrowRightIcon, - BookOpenIcon, CalendarIcon, FileTextIcon, GraduationCapIcon, @@ -27,15 +26,19 @@ import { EcosystemShowcase, FeatureShowcase, FeatureShowcaseIconContainer, + GradientText, Hero, Pill, SecondaryHero, } from '@kit/ui/marketing'; import { Trans } from '@kit/ui/trans'; +import { cn } from '@kit/ui/utils'; import billingConfig from '~/config/billing.config'; import pathsConfig from '~/config/paths.config'; +import { AnimateOnScroll } from './_components/animate-on-scroll'; + function Home() { return (
@@ -51,7 +54,10 @@ function Home() { } title={ - + {' '} + + + } subtitle={ @@ -61,283 +67,343 @@ function Home() { } cta={} image={ - {`MyEasyCMS +
+ } />
- {/* Trust Indicators */} -
-
-

- -

- -
- - - - + {/* Stats Bar */} + +
+
+

+ +

+
+ + + + +
-
+ {/* Core Modules Feature Grid */} -
-
- - - - - .{' '} - - - - - } - icon={ - - - - - - - } - > -
- +
+
+ + + + + .{' '} + + + + + } + icon={ + + + + + + + } + > +
+ + + + + + +
+
+
+
+ + + {/* Testimonials */} + +
+
+
+

+ +

+

+ +

+
+
+ - - - - -
- +
-
- - {/* Dashboard Showcase */} -
- } - description={} - > - MyEasyCMS Dashboard - -
+ {/* Additional Features Row */} -
-
- - - - - .{' '} - - - - - } - icon={ - - - - - - - } - > -
- - - -
-
+ +
+
+ + + + + .{' '} + + + + + } + icon={ + + + + + + + } + > +
+ + + +
+
+
-
+ {/* Why Choose Us Section */} -
- } - description={} - textPosition="right" - > -
- - - - -
-
-
+ +
+ } + description={} + textPosition="right" + className="border-primary/10 rounded-xl border" + > +
+ + + + +
+
+
+
{/* How It Works */} -
-
-
-

- -

-

- -

-
+ +
+
+
+

+ +

+

+ +

+
-
- - - +
+
-
+ {/* Pricing Section */} -
-
- }> - - + +
+
} - subheading={} - /> - -
- + }> + + + } + heading={ + + + + } + subheading={} /> + +
+ +
-
+
{/* Final CTA */} -
-
-

- -

-

- -

-
- - - - - - - - - - - - - + +
+
+

+ + + +

+

+ +

+
+ + + + + + + + + + + + + +
+

+ + +

-

- - -

-
+
); } @@ -347,7 +413,7 @@ export default Home; function MainCallToActionButton() { return (
- + @@ -364,7 +430,7 @@ function MainCallToActionButton() { - + @@ -377,11 +443,20 @@ function IconFeatureCard(props: { icon: React.ComponentType<{ className?: string }>; titleKey: string; descKey: string; + accentBg?: string; + accentText?: string; }) { return ( -
-
- +
+
+

@@ -393,16 +468,39 @@ function IconFeatureCard(props: { ); } -function TrustItem(props: { - icon: React.ComponentType<{ className?: string }>; - label: string; +function StatItem(props: { value: string; labelKey: string }) { + return ( +
+ + {props.value} + + + + +
+ ); +} + +function TestimonialCard(props: { + quoteKey: string; + nameKey: string; + roleKey: string; }) { return ( -
- - - - +
+

+ “ + + ” +

+
+

+ +

+

+ +

+
); } @@ -414,7 +512,7 @@ function WhyItem(props: { }) { return (
-
+
@@ -431,8 +529,10 @@ function WhyItem(props: { function StepCard(props: { step: string; titleKey: string; descKey: string }) { return ( -
- {props.step} +
+
+ {props.step} +

diff --git a/apps/web/app/[locale]/home/[account]/bookings/guests/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/guests/page.tsx index f1f724091..966950c02 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/guests/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/guests/page.tsx @@ -1,4 +1,5 @@ import { UserCircle, Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createBookingManagementApi } from '@kit/booking-management/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -16,6 +17,7 @@ interface PageProps { export default async function GuestsPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('bookings'); const { data: acct } = await client .from('accounts') @@ -25,7 +27,7 @@ export default async function GuestsPage({ params }: PageProps) { if (!acct) { return ( - + ); @@ -35,38 +37,50 @@ export default async function GuestsPage({ params }: PageProps) { const guests = await api.listGuests(acct.id); return ( - +
-

Gästeverwaltung

+

{t('guests.manage')}

{guests.length === 0 ? ( } - title="Keine Gäste vorhanden" - description="Legen Sie Ihren ersten Gast an." - actionLabel="Neuer Gast" + title={t('guests.noGuests')} + description={t('guests.addFirst')} + actionLabel={t('guests.newGuest')} /> ) : ( - Alle Gäste ({guests.length}) + + {t('guests.allGuests', { count: guests.length })} + -
- +
+
- - - - - + + + + + diff --git a/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx index 653bcd912..9c2260269 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createBookingManagementApi } from '@kit/booking-management/api'; import { CreateBookingForm } from '@kit/booking-management/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -11,6 +13,7 @@ interface Props { export default async function NewBookingPage({ params }: Props) { const { account } = await params; + const t = await getTranslations('bookings'); const client = getSupabaseServerClient(); const { data: acct } = await client .from('accounts') @@ -19,7 +22,7 @@ export default async function NewBookingPage({ params }: Props) { .single(); if (!acct) { return ( - + ); @@ -31,8 +34,8 @@ export default async function NewBookingPage({ params }: Props) { return ( + ); @@ -36,41 +39,53 @@ export default async function RoomsPage({ params }: PageProps) { const rooms = await api.listRooms(acct.id); return ( - +
-

Zimmerverwaltung

+

{t('rooms.manage')}

{rooms.length === 0 ? ( } - title="Keine Zimmer vorhanden" - description="Fügen Sie Ihr erstes Zimmer hinzu." - actionLabel="Neues Zimmer" + title={t('rooms.noRooms')} + description={t('rooms.addFirst')} + actionLabel={t('rooms.newRoom')} /> ) : ( - Alle Zimmer ({rooms.length}) + + {t('rooms.allRooms', { count: rooms.length })} + -
-
NameE-MailTelefonStadtLand + {t('guests.name')} + + {t('guests.email')} + + {t('guests.phone')} + + {t('guests.city')} + + {t('guests.country')} +
+
+
- - - - - + + + + + - @@ -95,7 +110,9 @@ export default async function RoomsPage({ params }: PageProps) {
Zimmernr.NameTypKapazität - Preis/Nacht + + {t('rooms.roomNumber')} + + {t('rooms.name')} + + {t('rooms.type')} + + {t('rooms.capacity')} + + {t('rooms.price')} + + {t('rooms.active')} Aktiv
{room.price_per_night != null - ? `${Number(room.price_per_night).toFixed(2)} €` + ? formatCurrencyAmount( + room.price_per_night as number, + ) : '—'} diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/attendance-grid.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/attendance-grid.tsx index c64d18236..629ee719e 100644 --- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/attendance-grid.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/attendance/attendance-grid.tsx @@ -69,12 +69,16 @@ export function AttendanceGrid({ Keine Teilnehmer in diesem Kurs

) : ( -
- +
+
- - + + 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 6ff5086d9..c3ede8b54 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,4 +1,5 @@ import { ClipboardCheck, Calendar } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; import { formatDate } from '@kit/shared/dates'; @@ -25,6 +26,7 @@ export default async function AttendancePage({ const search = await searchParams; const client = getSupabaseServerClient(); const api = createCourseManagementApi(client); + const t = await getTranslations('courses'); const [course, sessions, participants] = await Promise.all([ api.getCourse(courseId), @@ -58,10 +60,9 @@ export default async function AttendancePage({ })); return ( - +
-

Anwesenheit

{String((course as Record).name)}

@@ -70,14 +71,14 @@ export default async function AttendancePage({ {sessions.length === 0 ? ( } - title="Keine Termine vorhanden" - description="Erstellen Sie zuerst Termine für diesen Kurs." + title={t('attendance.noSessions')} + description={t('attendance.noSessionsDescription')} /> ) : ( <> - Termin auswählen + {t('attendance.selectSession')}
@@ -107,7 +108,7 @@ export default async function AttendancePage({ - Anwesenheitsliste + {t('attendance.attendanceList')} @@ -119,7 +120,7 @@ export default async function AttendancePage({ /> ) : (

- Bitte wählen Sie einen Termin aus + {t('attendance.selectSessionPrompt')}

)}
diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx index b759805b9..7bc79d455 100644 --- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx @@ -39,12 +39,14 @@ export function CreateSessionDialog({ courseId }: Props) { return ( - - - + +
TeilnehmerAnwesend + Teilnehmer + + Anwesend +
+
+
- - - - - + + + + @@ -107,7 +118,8 @@ export default async function ParticipantsPage({ params }: PageProps) { STATUS_VARIANT[String(p.status)] ?? 'secondary' } > - {STATUS_LABEL[String(p.status)] ?? String(p.status)} + {ENROLLMENT_STATUS_LABEL[String(p.status)] ?? + String(p.status)}
NameE-MailTelefonStatus - Anmeldedatum + + {t('common.name')} + + {t('common.email')} + + {t('common.phone')} + + {t('common.status')} + + {t('enrollment.registrationDate')}
diff --git a/apps/web/app/[locale]/home/[account]/courses/categories/create-category-dialog.tsx b/apps/web/app/[locale]/home/[account]/courses/categories/create-category-dialog.tsx index 0169965cb..faff7e154 100644 --- a/apps/web/app/[locale]/home/[account]/courses/categories/create-category-dialog.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/categories/create-category-dialog.tsx @@ -5,6 +5,7 @@ import { useCallback, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Plus } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { createCategory } from '@kit/course-management/actions/course-actions'; import { Button } from '@kit/ui/button'; @@ -26,6 +27,7 @@ interface CreateCategoryDialogProps { } export function CreateCategoryDialog({ accountId }: CreateCategoryDialogProps) { + const t = useTranslations('courses'); const router = useRouter(); const [open, setOpen] = useState(false); const [name, setName] = useState(''); @@ -76,7 +78,7 @@ export function CreateCategoryDialog({ accountId }: CreateCategoryDialogProps) { id="cat-name" value={name} onChange={(e) => setName(e.target.value)} - placeholder="z. B. Sprachkurse" + placeholder={t('categories.namePlaceholder')} required minLength={1} maxLength={128} @@ -88,7 +90,7 @@ export function CreateCategoryDialog({ accountId }: CreateCategoryDialogProps) { id="cat-description" value={description} onChange={(e) => setDescription(e.target.value)} - placeholder="Kurze Beschreibung" + placeholder={t('categories.descriptionPlaceholder')} /> 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 81a674cfa..d289131cf 100644 --- a/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx @@ -1,4 +1,5 @@ import { FolderTree } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -17,6 +18,7 @@ interface PageProps { export default async function CategoriesPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); const { data: acct } = await client .from('accounts') @@ -30,36 +32,40 @@ export default async function CategoriesPage({ params }: PageProps) { const categories = await api.listCategories(acct.id); return ( - +
-

Kurskategorien verwalten

+

{t('categories.manage')}

{categories.length === 0 ? ( } - title="Keine Kategorien vorhanden" - description="Erstellen Sie Ihre erste Kurskategorie." - actionLabel="Neue Kategorie" + title={t('categories.noCategories')} + description={t('categories.manage')} + actionLabel={t('categories.newCategory')} /> ) : ( - Alle Kategorien ({categories.length}) + + {t('categories.allTitle', { count: categories.length })} + -
- +
+
- - - + diff --git a/apps/web/app/[locale]/home/[account]/courses/instructors/create-instructor-dialog.tsx b/apps/web/app/[locale]/home/[account]/courses/instructors/create-instructor-dialog.tsx index 878ac4b7a..fb4c9348f 100644 --- a/apps/web/app/[locale]/home/[account]/courses/instructors/create-instructor-dialog.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/instructors/create-instructor-dialog.tsx @@ -5,6 +5,7 @@ import { useCallback, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Plus } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { createInstructor } from '@kit/course-management/actions/course-actions'; import { Button } from '@kit/ui/button'; @@ -29,6 +30,7 @@ interface CreateInstructorDialogProps { export function CreateInstructorDialog({ accountId, }: CreateInstructorDialogProps) { + const t = useTranslations('courses'); const router = useRouter(); const [open, setOpen] = useState(false); const [firstName, setFirstName] = useState(''); @@ -101,7 +103,7 @@ export function CreateInstructorDialog({ id="inst-first-name" value={firstName} onChange={(e) => setFirstName(e.target.value)} - placeholder="Vorname" + placeholder={t('instructors.firstNamePlaceholder')} required minLength={1} maxLength={128} @@ -113,7 +115,7 @@ export function CreateInstructorDialog({ id="inst-last-name" value={lastName} onChange={(e) => setLastName(e.target.value)} - placeholder="Nachname" + placeholder={t('instructors.lastNamePlaceholder')} required minLength={1} maxLength={128} @@ -150,7 +152,7 @@ export function CreateInstructorDialog({ id="inst-qualifications" value={qualifications} onChange={(e) => setQualifications(e.target.value)} - placeholder="z. B. Zertifizierter Trainer, Erste-Hilfe-Ausbilder" + placeholder={t('instructors.qualificationsPlaceholder')} rows={3} /> 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 00cad7c31..24a051496 100644 --- a/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx @@ -1,6 +1,8 @@ import { GraduationCap } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; +import { formatCurrencyAmount } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; @@ -17,6 +19,7 @@ interface PageProps { export default async function InstructorsPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); const { data: acct } = await client .from('accounts') @@ -30,38 +33,46 @@ export default async function InstructorsPage({ params }: PageProps) { const instructors = await api.listInstructors(acct.id); return ( - +
-

Dozentenpool verwalten

+

{t('instructors.manage')}

{instructors.length === 0 ? ( } - title="Keine Dozenten vorhanden" - description="Fügen Sie Ihren ersten Dozenten hinzu." - actionLabel="Neuer Dozent" + title={t('instructors.noInstructors')} + description={t('instructors.manage')} + actionLabel={t('instructors.newInstructor')} /> ) : ( - Alle Dozenten ({instructors.length}) + + {t('instructors.allTitle', { count: instructors.length })} + -
-
Name - Beschreibung + + {t('common.name')} - Übergeordnet + + {t('common.description')} + + {t('common.parent')}
+
+
- - - - - + + + @@ -82,7 +93,7 @@ export default async function InstructorsPage({ params }: PageProps) { diff --git a/apps/web/app/[locale]/home/[account]/courses/locations/create-location-dialog.tsx b/apps/web/app/[locale]/home/[account]/courses/locations/create-location-dialog.tsx index db20373a1..bb46b6f37 100644 --- a/apps/web/app/[locale]/home/[account]/courses/locations/create-location-dialog.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/locations/create-location-dialog.tsx @@ -5,6 +5,7 @@ import { useCallback, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Plus } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { createLocation } from '@kit/course-management/actions/course-actions'; import { Button } from '@kit/ui/button'; @@ -26,6 +27,7 @@ interface CreateLocationDialogProps { } export function CreateLocationDialog({ accountId }: CreateLocationDialogProps) { + const t = useTranslations('courses'); const router = useRouter(); const [open, setOpen] = useState(false); const [name, setName] = useState(''); @@ -82,7 +84,7 @@ export function CreateLocationDialog({ accountId }: CreateLocationDialogProps) { id="loc-name" value={name} onChange={(e) => setName(e.target.value)} - placeholder="z. B. Vereinsheim" + placeholder={t('locations.namePlaceholder')} required minLength={1} maxLength={128} @@ -94,7 +96,7 @@ export function CreateLocationDialog({ accountId }: CreateLocationDialogProps) { id="loc-address" value={address} onChange={(e) => setAddress(e.target.value)} - placeholder="Musterstr. 1, 12345 Musterstadt" + placeholder={t('locations.addressPlaceholder')} />
@@ -104,7 +106,7 @@ export function CreateLocationDialog({ accountId }: CreateLocationDialogProps) { id="loc-room" value={room} onChange={(e) => setRoom(e.target.value)} - placeholder="z. B. Raum 101" + placeholder={t('locations.roomPlaceholder')} />
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 a6fe6eb30..c64024329 100644 --- a/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx @@ -1,4 +1,5 @@ import { MapPin } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -17,6 +18,7 @@ interface PageProps { export default async function LocationsPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); const { data: acct } = await client .from('accounts') @@ -30,36 +32,44 @@ export default async function LocationsPage({ params }: PageProps) { const locations = await api.listLocations(acct.id); return ( - +
-

- Kurs- und Veranstaltungsorte verwalten -

+

{t('locations.manage')}

{locations.length === 0 ? ( } - title="Keine Orte vorhanden" - description="Fügen Sie Ihren ersten Veranstaltungsort hinzu." - actionLabel="Neuer Ort" + title={t('locations.noLocations')} + description={t('locations.noLocationsDescription')} + actionLabel={t('locations.newLocationLabel')} /> ) : ( - Alle Orte ({locations.length}) + + {t('locations.allTitle', { count: locations.length })} + -
-
NameE-MailTelefon - Qualifikation + + {t('common.name')} - Stundensatz + + {t('common.email')} + + {t('common.phone')} + + {t('instructors.qualification')} + + {t('instructors.hourlyRate')}
{inst.hourly_rate != null - ? `${Number(inst.hourly_rate).toFixed(2)} €` + ? formatCurrencyAmount(inst.hourly_rate as number) : '—'}
+
+
- - - - + + + + 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 eb0de0d55..9d25340ce 100644 --- a/apps/web/app/[locale]/home/[account]/courses/new/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/new/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { CreateCourseForm } from '@kit/course-management/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -11,6 +13,8 @@ interface Props { export default async function NewCoursePage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); + const { data: acct } = await client .from('accounts') .select('id') @@ -21,8 +25,8 @@ export default async function NewCoursePage({ params }: Props) { return ( 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 f6ed23f08..394f6e474 100644 --- a/apps/web/app/[locale]/home/[account]/courses/statistics/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/statistics/page.tsx @@ -5,6 +5,7 @@ import { TrendingUp, BarChart3, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -22,6 +23,7 @@ interface PageProps { export default async function CourseStatisticsPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); const { data: acct } = await client .from('accounts') @@ -34,32 +36,32 @@ export default async function CourseStatisticsPage({ params }: PageProps) { const stats = await api.getStatistics(acct.id); const statusChartData = [ - { name: 'Aktiv', value: stats.openCourses }, - { name: 'Abgeschlossen', value: stats.completedCourses }, - { name: 'Gesamt', value: stats.totalCourses }, + { name: t('stats.active'), value: stats.openCourses }, + { name: t('stats.completed'), value: stats.completedCourses }, + { name: t('stats.total'), value: stats.totalCourses }, ]; return ( - +
} /> } /> } /> } /> @@ -70,7 +72,7 @@ export default async function CourseStatisticsPage({ params }: PageProps) { - Kursauslastung + {t('stats.utilization')} @@ -82,7 +84,7 @@ export default async function CourseStatisticsPage({ params }: PageProps) { - Verteilung + {t('stats.distribution')} 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 ef17dd750..ab6e95f82 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,9 +2,12 @@ import React from 'react'; +import type { SupabaseClient } from '@supabase/supabase-js'; + import { createDocumentGeneratorApi } from '@kit/document-generator/api'; import { formatDate } from '@kit/shared/dates'; import { getLogger } from '@kit/shared/logger'; +import type { Database } from '@kit/supabase/database'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; export type GenerateDocumentInput = { @@ -86,7 +89,7 @@ function fmtDate(d: string | null): string { // Member Card PDF — premium design with color accent bar, structured layout // ═══════════════════════════════════════════════════════════════════════════ async function generateMemberCards( - client: ReturnType, + client: SupabaseClient, accountId: string, accountName: string, input: GenerateDocumentInput, @@ -413,7 +416,7 @@ async function generateMemberCards( // Address Labels (HTML — Avery L7163) // ═══════════════════════════════════════════════════════════════════════════ async function generateLabels( - client: ReturnType, + client: SupabaseClient, accountId: string, input: GenerateDocumentInput, ): Promise { @@ -461,7 +464,7 @@ async function generateLabels( // Member Report (Excel) // ═══════════════════════════════════════════════════════════════════════════ async function generateMemberReport( - client: ReturnType, + client: SupabaseClient, accountId: string, input: GenerateDocumentInput, ): Promise { 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 ec140d23b..31ad72662 100644 --- a/apps/web/app/[locale]/home/[account]/documents/generate/page.tsx +++ b/apps/web/app/[locale]/home/[account]/documents/generate/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { ArrowLeft } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Button } from '@kit/ui/button'; @@ -23,15 +24,6 @@ interface PageProps { searchParams: Promise<{ type?: string }>; } -const DOCUMENT_LABELS: Record = { - 'member-card': 'Mitgliedsausweis', - invoice: 'Rechnung', - labels: 'Etiketten', - report: 'Bericht', - letter: 'Brief', - certificate: 'Zertifikat', -}; - export default async function GenerateDocumentPage({ params, searchParams, @@ -39,6 +31,7 @@ export default async function GenerateDocumentPage({ const { account } = await params; const { type } = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('documents'); const { data: acct } = await client .from('accounts') @@ -49,10 +42,13 @@ export default async function GenerateDocumentPage({ if (!acct) return ; const selectedType = type ?? 'member-card'; - const selectedLabel = DOCUMENT_LABELS[selectedType] ?? 'Dokument'; + // Resolve the label from translations; fall back to generic 'Document' + const selectedLabel = + (t.raw(`types.${selectedType}`) as string | undefined) ?? + t('generate.document'); return ( - +
{/* Back link */}
@@ -61,16 +57,16 @@ export default async function GenerateDocumentPage({ className="text-muted-foreground hover:text-foreground inline-flex items-center text-sm" > - Zurück zu Dokumente + {t('generate.backToDocuments')}
- {selectedLabel} generieren - - Wählen Sie den Dokumenttyp und die gewünschten Optionen. - + + {t('generate.generateLabel', { label: selectedLabel })} + + {t('generate.chooseOptions')} @@ -82,7 +78,7 @@ export default async function GenerateDocumentPage({ - + diff --git a/apps/web/app/[locale]/home/[account]/documents/page.tsx b/apps/web/app/[locale]/home/[account]/documents/page.tsx index a39066625..7444b2daa 100644 --- a/apps/web/app/[locale]/home/[account]/documents/page.tsx +++ b/apps/web/app/[locale]/home/[account]/documents/page.tsx @@ -8,6 +8,7 @@ import { Mail, Award, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Button } from '@kit/ui/button'; @@ -23,45 +24,31 @@ interface PageProps { const DOCUMENT_TYPES = [ { id: 'member-card', - title: 'Mitgliedsausweis', - description: - 'Mitgliedsausweise mit Foto, Name und Mitgliedsnummer generieren.', icon: CreditCard, color: 'text-blue-600 bg-blue-50', }, { id: 'invoice', - title: 'Rechnung', - description: - 'Professionelle Rechnungen im PDF-Format mit Logo und Positionen.', icon: FileText, color: 'text-green-600 bg-green-50', }, { id: 'labels', - title: 'Etiketten', - 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.', icon: BarChart3, color: 'text-purple-600 bg-purple-50', }, { id: 'letter', - title: 'Brief', - 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.', icon: Award, color: 'text-amber-600 bg-amber-50', }, @@ -70,6 +57,7 @@ const DOCUMENT_TYPES = [ export default async function DocumentsPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('documents'); const { data: acct } = await client .from('accounts') @@ -82,14 +70,14 @@ export default async function DocumentsPage({ params }: PageProps) { return (
{/* Actions */}
- +
@@ -104,18 +92,20 @@ export default async function DocumentsPage({ params }: PageProps) {
- {docType.title} + + {t(`types.${docType.id}`)} +

- {docType.description} + {t(`typeDescriptions.${docType.id}`)}

diff --git a/apps/web/app/[locale]/home/[account]/documents/templates/page.tsx b/apps/web/app/[locale]/home/[account]/documents/templates/page.tsx index 3371d6424..6cbd7d7f7 100644 --- a/apps/web/app/[locale]/home/[account]/documents/templates/page.tsx +++ b/apps/web/app/[locale]/home/[account]/documents/templates/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { FileText, Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Button } from '@kit/ui/button'; @@ -17,6 +18,7 @@ interface PageProps { export default async function DocumentTemplatesPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('documents'); const { data: acct } = await client .from('accounts') @@ -35,20 +37,17 @@ export default async function DocumentTemplatesPage({ params }: PageProps) { }> = []; return ( - +
{/* Header */}
-

Dokumentvorlagen

-

- Vorlagen für Mitgliedsausweise, Rechnungen, Etiketten und mehr -

+

{t('templates.subtitle')}

@@ -56,24 +55,30 @@ export default async function DocumentTemplatesPage({ params }: PageProps) { {templates.length === 0 ? ( } - title="Keine Vorlagen vorhanden" - description="Erstellen Sie Ihre erste Dokumentvorlage, um Mitgliedsausweise, Rechnungen und mehr zu generieren." - actionLabel="Neue Vorlage" + title={t('templates.noTemplates')} + description={t('templates.createFirstLong')} + actionLabel={t('templates.newTemplate')} /> ) : ( - Alle Vorlagen ({templates.length}) + + {t('templates.allTemplates', { count: templates.length })} + -
-
NameAdresseRaumKapazität + {t('common.name')} + + {t('common.address')} + + {t('common.room')} + + {t('list.capacity')} +
+
+
- - - + + diff --git a/apps/web/app/[locale]/home/[account]/events/[eventId]/delete-event-button.tsx b/apps/web/app/[locale]/home/[account]/events/[eventId]/delete-event-button.tsx index 4fe9ee7d2..cbaf791df 100644 --- a/apps/web/app/[locale]/home/[account]/events/[eventId]/delete-event-button.tsx +++ b/apps/web/app/[locale]/home/[account]/events/[eventId]/delete-event-button.tsx @@ -35,12 +35,14 @@ export function DeleteEventButton({ eventId, accountSlug }: Props) { return ( - - - + +
NameTyp - Beschreibung + + {t('templates.name')} + + {t('templates.type')} + + {t('templates.description')}
+
+
- - - + + - - @@ -89,7 +92,7 @@ export default async function HolidayPassesPage({ params }: PageProps) {
{t('name')}{t('year')} + + {t('name')} + + {t('year')} + {t('price')} + {t('validFrom')} + {t('validUntil')}
{String(pass.year ?? '—')} {pass.price != null - ? `${Number(pass.price).toFixed(2)} €` + ? formatCurrencyAmount(pass.price as number) : '—'} diff --git a/apps/web/app/[locale]/home/[account]/files/files-table.tsx b/apps/web/app/[locale]/home/[account]/files/files-table.tsx index 28c664ee7..9639f51ce 100644 --- a/apps/web/app/[locale]/home/[account]/files/files-table.tsx +++ b/apps/web/app/[locale]/home/[account]/files/files-table.tsx @@ -5,6 +5,7 @@ import { useCallback } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { Download, FileIcon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { deleteFile } from '@kit/module-builder/actions/file-actions'; import { Badge } from '@kit/ui/badge'; @@ -67,6 +68,7 @@ function getMimeLabel(mimeType: string): string { } export function FilesTable({ files, pagination }: FilesTableProps) { + const t = useTranslations('common'); const router = useRouter(); const searchParams = useSearchParams(); const { total, page, pageSize } = pagination; @@ -104,15 +106,25 @@ export function FilesTable({ files, pagination }: FilesTableProps) {

) : ( -
- +
+
- - - - - + + + + + @@ -149,8 +161,8 @@ export function FilesTable({ files, pagination }: FilesTableProps) { executeDelete({ fileId: file.id })} /> diff --git a/apps/web/app/[locale]/home/[account]/files/page.tsx b/apps/web/app/[locale]/home/[account]/files/page.tsx index 319c12ab1..3bf1f7d67 100644 --- a/apps/web/app/[locale]/home/[account]/files/page.tsx +++ b/apps/web/app/[locale]/home/[account]/files/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createModuleBuilderApi } from '@kit/module-builder/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { ListToolbar } from '@kit/ui/list-toolbar'; @@ -15,6 +17,7 @@ interface Props { export default async function FilesPage({ params, searchParams }: Props) { const { account } = await params; + const t = await getTranslations('common'); const search = await searchParams; const client = getSupabaseServerClient(); @@ -51,12 +54,12 @@ export default async function FilesPage({ params, searchParams }: Props) { return (
- +
diff --git a/apps/web/app/[locale]/home/[account]/finance/invoices/invoice-action-buttons.tsx b/apps/web/app/[locale]/home/[account]/finance/invoices/invoice-action-buttons.tsx new file mode 100644 index 000000000..dd57a1314 --- /dev/null +++ b/apps/web/app/[locale]/home/[account]/finance/invoices/invoice-action-buttons.tsx @@ -0,0 +1,141 @@ +'use client'; + +import { useTransition } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { CheckCircle, Send } from 'lucide-react'; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@kit/ui/alert-dialog'; +import { Button } from '@kit/ui/button'; + +interface SendInvoiceButtonProps { + invoiceId: string; + accountId: string; +} + +export function SendInvoiceButton({ + invoiceId, + accountId, +}: SendInvoiceButtonProps) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + + const handleSend = () => { + startTransition(async () => { + try { + const response = await fetch( + `/api/finance/invoices/${invoiceId}/send`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accountId }), + }, + ); + + if (response.ok) { + router.refresh(); + } + } catch (error) { + console.error('Failed to send invoice:', error); + } + }); + }; + + return ( + + + + ); +} + +interface MarkPaidButtonProps { + invoiceId: string; + accountId: string; +} + +export function MarkPaidButton({ invoiceId, accountId }: MarkPaidButtonProps) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + + const handleMarkPaid = () => { + startTransition(async () => { + try { + const response = await fetch( + `/api/finance/invoices/${invoiceId}/mark-paid`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accountId }), + }, + ); + + if (response.ok) { + router.refresh(); + } + } catch (error) { + console.error('Failed to mark invoice as paid:', error); + } + }); + }; + + return ( + + + + ); +} 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 a3d78886e..10db27601 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,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { CreateInvoiceForm } from '@kit/finance/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -10,6 +12,7 @@ interface Props { export default async function NewInvoicePage({ params }: Props) { const { account } = await params; + const t = await getTranslations('finance'); const client = getSupabaseServerClient(); const { data: acct } = await client .from('accounts') @@ -21,8 +24,8 @@ export default async function NewInvoicePage({ params }: Props) { return ( diff --git a/apps/web/app/[locale]/home/[account]/finance/payments/page.tsx b/apps/web/app/[locale]/home/[account]/finance/payments/page.tsx index 8a04967c3..5ebe762e6 100644 --- a/apps/web/app/[locale]/home/[account]/finance/payments/page.tsx +++ b/apps/web/app/[locale]/home/[account]/finance/payments/page.tsx @@ -7,6 +7,7 @@ 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 { getTranslations } from 'next-intl/server'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; @@ -23,6 +24,7 @@ const formatCurrency = (amount: number) => export default async function PaymentsPage({ params }: PageProps) { const { account } = await params; + const t = await getTranslations('finance'); const client = getSupabaseServerClient(); const { data: acct } = await client @@ -78,41 +80,40 @@ export default async function PaymentsPage({ params }: PageProps) { ); return ( - +
{/* Header */}
-

Zahlungsübersicht

- Zusammenfassung aller Zahlungen und offenen Beträge + {t('payments.subtitle')}

{/* Stats */}
} - description={`${paidInvoices.length} Rechnungen`} + description={`${paidInvoices.length} ${t('payments.paidInvoices')}`} /> } - description={`${openInvoices.length} Rechnungen`} + description={`${openInvoices.length} ${t('invoices.title')}`} /> } - description={`${overdueInvoices.length} Rechnungen`} + description={`${overdueInvoices.length} ${t('invoices.title')}`} /> } - description={`${batches.length} Einzüge`} + description={`${batches.length} ${t('payments.batchUnit')}`} />
@@ -120,7 +121,7 @@ export default async function PaymentsPage({ params }: PageProps) {
- Offene Rechnungen + {t('payments.openInvoices')} 0 ? 'default' : 'secondary'} > @@ -130,21 +131,21 @@ export default async function PaymentsPage({ params }: PageProps) {

{openInvoices.length > 0 - ? `${openInvoices.length} Rechnungen mit einem Gesamtbetrag von ${formatCurrency(openTotal)} sind offen.` - : 'Keine offenen Rechnungen vorhanden.'} + ? t('payments.invoicesOpenSummary', { count: openInvoices.length, total: formatCurrency(openTotal) }) + : t('payments.noOpenInvoices')}

- - - + +
- SEPA-Einzüge + {t('payments.sepaBatches')} 0 ? 'default' : 'secondary'}> {batches.length} @@ -152,15 +153,15 @@ 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.'} + ? t('payments.batchSummary', { count: batches.length, total: formatCurrency(sepaTotal) }) + : t('payments.noBatchesFound')}

- - - + +
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 3eda3ad41..472d42567 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,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { CreateSepaBatchForm } from '@kit/finance/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -10,6 +12,7 @@ interface Props { export default async function NewSepaBatchPage({ params }: Props) { const { account } = await params; + const t = await getTranslations('finance'); const client = getSupabaseServerClient(); const { data: acct } = await client @@ -23,8 +26,8 @@ export default async function NewSepaBatchPage({ params }: Props) { return ( diff --git a/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx index 26064e9df..83ab04478 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createFischereiApi } from '@kit/fischerei/api'; import { FischereiTabNavigation, @@ -18,6 +20,7 @@ export default async function CatchBooksPage({ params, searchParams }: Props) { const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -48,7 +51,7 @@ export default async function CatchBooksPage({ params, searchParams }: Props) { }); return ( - + + + + diff --git a/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx index 5857cf6ee..b17d01677 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createFischereiApi } from '@kit/fischerei/api'; import { @@ -23,6 +24,7 @@ export default async function LeasesPage({ params, searchParams }: Props) { const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -58,22 +60,21 @@ export default async function LeasesPage({ params, searchParams }: Props) { ]; return ( - +
-

Pachten

Gewässerpachtverträge verwalten

- - - + +
+ diff --git a/apps/web/app/[locale]/home/[account]/fischerei/permits/new/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/permits/new/page.tsx index 27f97a5da..feac0ea3a 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/permits/new/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/permits/new/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createFischereiApi } from '@kit/fischerei/api'; import { FischereiTabNavigation, @@ -15,6 +17,7 @@ interface Props { export default async function NewPermitPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -33,7 +36,7 @@ export default async function NewPermitPage({ params }: Props) { })); return ( - + diff --git a/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx index a347dc40f..5e3e3cb16 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createFischereiApi } from '@kit/fischerei/api'; import { @@ -20,6 +21,7 @@ interface Props { export default async function PermitsPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -33,22 +35,21 @@ export default async function PermitsPage({ params }: Props) { const permits = await api.listPermits(acct.id); return ( - +
-

Erlaubnisscheine

Erlaubnisscheine und Gewässerkarten verwalten

- - - + +
>} diff --git a/apps/web/app/[locale]/home/[account]/fischerei/species/[speciesId]/edit/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/species/[speciesId]/edit/page.tsx index af4e977ea..e7f03137d 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/species/[speciesId]/edit/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/species/[speciesId]/edit/page.tsx @@ -1,5 +1,7 @@ import { notFound } from 'next/navigation'; +import { getTranslations } from 'next-intl/server'; + import { createFischereiApi } from '@kit/fischerei/api'; import { FischereiTabNavigation, @@ -17,6 +19,7 @@ interface Props { export default async function EditSpeciesPage({ params }: Props) { const { account, speciesId } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -37,7 +40,7 @@ export default async function EditSpeciesPage({ params }: Props) { } return ( - + ; return ( - + diff --git a/apps/web/app/[locale]/home/[account]/fischerei/species/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/species/page.tsx index 23376d52b..9dd8b9f0e 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/species/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/species/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createFischereiApi } from '@kit/fischerei/api'; import { FischereiTabNavigation, @@ -18,6 +20,7 @@ export default async function SpeciesPage({ params, searchParams }: Props) { const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -41,7 +44,7 @@ export default async function SpeciesPage({ params, searchParams }: Props) { }); return ( - + ; return ( - +
-

Statistiken

Fangstatistiken und Auswertungen

diff --git a/apps/web/app/[locale]/home/[account]/fischerei/stocking/[stockingId]/edit/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/stocking/[stockingId]/edit/page.tsx index 56906e947..f34960353 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/stocking/[stockingId]/edit/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/stocking/[stockingId]/edit/page.tsx @@ -1,5 +1,7 @@ import { notFound } from 'next/navigation'; +import { getTranslations } from 'next-intl/server'; + import { createFischereiApi } from '@kit/fischerei/api'; import { FischereiTabNavigation, @@ -17,6 +19,7 @@ interface Props { export default async function EditStockingPage({ params }: Props) { const { account, stockingId } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -53,7 +56,7 @@ export default async function EditStockingPage({ params }: Props) { })); return ( - + + + + diff --git a/apps/web/app/[locale]/home/[account]/fischerei/waters/new/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/waters/new/page.tsx index b1358130d..673a71863 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/waters/new/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/waters/new/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { FischereiTabNavigation, CreateWaterForm, @@ -14,6 +16,7 @@ interface Props { export default async function NewWaterPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -24,7 +27,7 @@ export default async function NewWaterPage({ params }: Props) { if (!acct) return ; return ( - + diff --git a/apps/web/app/[locale]/home/[account]/fischerei/waters/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/waters/page.tsx index 5ae271514..bf840858d 100644 --- a/apps/web/app/[locale]/home/[account]/fischerei/waters/page.tsx +++ b/apps/web/app/[locale]/home/[account]/fischerei/waters/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createFischereiApi } from '@kit/fischerei/api'; import { FischereiTabNavigation, @@ -17,6 +19,7 @@ export default async function WatersPage({ params, searchParams }: Props) { const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('fischerei'); const { data: acct } = await client .from('accounts') @@ -36,7 +39,7 @@ export default async function WatersPage({ params, searchParams }: Props) { }); return ( - +
diff --git a/apps/web/app/[locale]/home/[account]/meetings/page.tsx b/apps/web/app/[locale]/home/[account]/meetings/page.tsx index 90b4a24bc..0b15086ce 100644 --- a/apps/web/app/[locale]/home/[account]/meetings/page.tsx +++ b/apps/web/app/[locale]/home/[account]/meetings/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createMeetingsApi } from '@kit/sitzungsprotokolle/api'; import { MeetingsTabNavigation, @@ -15,6 +17,7 @@ interface PageProps { export default async function MeetingsPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('meetings'); const { data: acct } = await client .from('accounts') @@ -33,7 +36,7 @@ export default async function MeetingsPage({ params }: PageProps) { ]); return ( - + ; return ( - +
-

Neues Protokoll erstellen

Erstellen Sie ein neues Sitzungsprotokoll mit Tagesordnungspunkten.

diff --git a/apps/web/app/[locale]/home/[account]/meetings/protocols/page.tsx b/apps/web/app/[locale]/home/[account]/meetings/protocols/page.tsx index 661782a3a..166caa90f 100644 --- a/apps/web/app/[locale]/home/[account]/meetings/protocols/page.tsx +++ b/apps/web/app/[locale]/home/[account]/meetings/protocols/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createMeetingsApi } from '@kit/sitzungsprotokolle/api'; import { MeetingsTabNavigation, @@ -20,6 +22,7 @@ export default async function ProtocolsPage({ const { account } = await params; const sp = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('meetings'); const { data: acct } = await client .from('accounts') @@ -42,7 +45,7 @@ export default async function ProtocolsPage({ }); return ( - + +
-

Offene Aufgaben

Alle offenen und in Bearbeitung befindlichen Tagesordnungspunkte über alle Protokolle.

Mitglied nicht gefunden
; + if (!member) return
{t('detail.notFound')}
; return ( diff --git a/apps/web/app/[locale]/home/[account]/members-cms/[memberId]/page.tsx b/apps/web/app/[locale]/home/[account]/members-cms/[memberId]/page.tsx index fec93ecad..1ad966883 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/[memberId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/[memberId]/page.tsx @@ -21,7 +21,7 @@ export default async function MemberDetailPage({ params }: Props) { const api = createMemberManagementApi(client); const member = await api.getMember(memberId); - if (!member) return
Mitglied nicht gefunden
; + if (!member) return ; // Fetch sub-entities in parallel const [roles, honors, mandates] = await Promise.all([ diff --git a/apps/web/app/[locale]/home/[account]/members-cms/applications/page.tsx b/apps/web/app/[locale]/home/[account]/members-cms/applications/page.tsx index e6439ce17..f30ac0b3b 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/applications/page.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/applications/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createMemberManagementApi } from '@kit/member-management/api'; import { ApplicationWorkflow } from '@kit/member-management/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -12,6 +14,7 @@ interface Props { export default async function ApplicationsPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('members'); const { data: acct } = await client .from('accounts') .select('id') @@ -25,8 +28,8 @@ export default async function ApplicationsPage({ params }: Props) { return ( {members.length === 0 ? ( } - title="Keine aktiven Mitglieder" - description="Erstellen Sie zuerst Mitglieder, um Ausweise zu generieren." - actionLabel="Mitglieder verwalten" + title={t('cards.noMembers')} + description={t('cards.noMembersDesc')} + actionLabel={t('nav.members')} actionHref={`/home/${account}/members-cms`} /> ) : ( } - title="Feature in Entwicklung" - description={`Die Ausweiserstellung für ${members.length} aktive Mitglieder wird derzeit entwickelt. Diese Funktion wird in einem kommenden Update verfügbar sein.`} - actionLabel="Mitglieder verwalten" + title={t('cards.inDevelopment')} + description={t('cards.inDevelopmentDesc', { count: result.total })} + actionLabel={t('nav.members')} actionHref={`/home/${account}/members-cms`} /> )} diff --git a/apps/web/app/[locale]/home/[account]/members-cms/departments/create-department-dialog.tsx b/apps/web/app/[locale]/home/[account]/members-cms/departments/create-department-dialog.tsx index f2fd7565f..7ea9ee76e 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/departments/create-department-dialog.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/departments/create-department-dialog.tsx @@ -5,6 +5,7 @@ import { useCallback, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Plus } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { createDepartment } from '@kit/member-management/actions/member-actions'; import { Button } from '@kit/ui/button'; @@ -28,14 +29,15 @@ interface CreateDepartmentDialogProps { export function CreateDepartmentDialog({ accountId, }: CreateDepartmentDialogProps) { + const t = useTranslations('members.departments'); const router = useRouter(); const [open, setOpen] = useState(false); const [name, setName] = useState(''); const [description, setDescription] = useState(''); const { execute, isPending } = useActionWithToast(createDepartment, { - successMessage: 'Abteilung erstellt', - errorMessage: 'Fehler beim Erstellen der Abteilung', + successMessage: t('created'), + errorMessage: t('createError'), onSuccess: () => { setOpen(false); setName(''); @@ -61,42 +63,42 @@ export function CreateDepartmentDialog({ }> - Neue Abteilung + {t('newDepartment')} - Neue Abteilung + {t('newDepartment')} - Erstellen Sie eine neue Abteilung oder Sparte für Ihren Verein. + {t('createDialogDescription')}
- + setName(e.target.value)} - placeholder="z. B. Jugendabteilung" + placeholder={t('namePlaceholder')} required minLength={1} maxLength={128} />
- + setDescription(e.target.value)} - placeholder="Kurze Beschreibung" + placeholder={t('descriptionPlaceholder')} />
diff --git a/apps/web/app/[locale]/home/[account]/members-cms/departments/delete-department-button.tsx b/apps/web/app/[locale]/home/[account]/members-cms/departments/delete-department-button.tsx new file mode 100644 index 000000000..48f8767aa --- /dev/null +++ b/apps/web/app/[locale]/home/[account]/members-cms/departments/delete-department-button.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { useTransition } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { Trash2 } from 'lucide-react'; +import { useTranslations } from 'next-intl'; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@kit/ui/alert-dialog'; +import { Button } from '@kit/ui/button'; + +interface DeleteDepartmentButtonProps { + departmentId: string; + departmentName: string; + accountId: string; +} + +export function DeleteDepartmentButton({ + departmentId, + departmentName, + accountId, +}: DeleteDepartmentButtonProps) { + const t = useTranslations('members.departments'); + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + + const handleDelete = () => { + startTransition(async () => { + try { + const response = await fetch( + `/api/members/departments/${departmentId}`, + { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accountId }), + }, + ); + + if (response.ok) { + router.refresh(); + } + } catch (error) { + console.error('Failed to delete department:', error); + } + }); + }; + + return ( + + + + ); +} diff --git a/apps/web/app/[locale]/home/[account]/members-cms/departments/page.tsx b/apps/web/app/[locale]/home/[account]/members-cms/departments/page.tsx index 87d007bd9..85f3db52b 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/departments/page.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/departments/page.tsx @@ -1,4 +1,5 @@ import { Users } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createMemberManagementApi } from '@kit/member-management/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -8,6 +9,7 @@ import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; import { CreateDepartmentDialog } from './create-department-dialog'; +import { DeleteDepartmentButton } from './delete-department-button'; interface Props { params: Promise<{ account: string }>; @@ -16,6 +18,7 @@ interface Props { export default async function DepartmentsPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('members'); const { data: acct } = await client .from('accounts') .select('id') @@ -29,8 +32,8 @@ export default async function DepartmentsPage({ params }: Props) { return (
@@ -40,16 +43,23 @@ export default async function DepartmentsPage({ params }: Props) { {departments.length === 0 ? ( } - title="Keine Abteilungen vorhanden" - description="Erstellen Sie Ihre erste Abteilung." + title={t('departments.noDepartments')} + description={t('departments.createFirst')} /> ) : ( -
-
DateinameTypGrößeHochgeladenAktionen + Dateiname + + Typ + + Größe + + Hochgeladen + + Aktionen +
+
+
- - + + + @@ -62,6 +72,15 @@ export default async function DepartmentsPage({ params }: Props) { + ))} diff --git a/apps/web/app/[locale]/home/[account]/members-cms/dues/page.tsx b/apps/web/app/[locale]/home/[account]/members-cms/dues/page.tsx index 1e407230b..950a29295 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/dues/page.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/dues/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createMemberManagementApi } from '@kit/member-management/api'; import { DuesCategoryManager } from '@kit/member-management/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -11,6 +13,7 @@ interface Props { export default async function DuesPage({ params }: Props) { const { account } = await params; + const t = await getTranslations('members'); const client = getSupabaseServerClient(); const { data: acct } = await client .from('accounts') @@ -25,8 +28,8 @@ export default async function DuesPage({ params }: Props) { return ( diff --git a/apps/web/app/[locale]/home/[account]/members-cms/import/page.tsx b/apps/web/app/[locale]/home/[account]/members-cms/import/page.tsx index 678483193..119c91616 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/import/page.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/import/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { MemberImportWizard } from '@kit/member-management/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -11,6 +13,7 @@ interface Props { export default async function MemberImportPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('members'); const { data: acct } = await client .from('accounts') .select('id') @@ -21,8 +24,8 @@ export default async function MemberImportPage({ params }: Props) { return ( diff --git a/apps/web/app/[locale]/home/[account]/members-cms/invitations/page.tsx b/apps/web/app/[locale]/home/[account]/members-cms/invitations/page.tsx index 2f06522b3..d51b3d57f 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/invitations/page.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/invitations/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { createMemberManagementApi } from '@kit/member-management/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -13,6 +15,7 @@ interface Props { export default async function InvitationsPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('members'); const { data: acct } = await client .from('accounts') .select('id') @@ -34,8 +37,8 @@ export default async function InvitationsPage({ params }: Props) { return ( +
} /> } /> } /> } /> @@ -73,7 +75,7 @@ export default async function MemberStatisticsPage({ params }: PageProps) { - Mitglieder nach Status + {t('statistics.title')} @@ -85,7 +87,7 @@ export default async function MemberStatisticsPage({ params }: PageProps) { - Verteilung + {t('statistics.totalMembers')} diff --git a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/page.tsx b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/page.tsx index 0c9c77f0d..7d38c3633 100644 --- a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/page.tsx @@ -1,6 +1,7 @@ import { createModuleBuilderApi } from '@kit/module-builder/api'; import { ModuleForm } from '@kit/module-builder/components'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { getTranslations } from 'next-intl/server'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; @@ -15,6 +16,7 @@ export default async function RecordDetailPage({ params, }: RecordDetailPageProps) { const { account, moduleId, recordId } = await params; + const t = await getTranslations('cms.modules'); const client = getSupabaseServerClient(); const api = createModuleBuilderApi(client); @@ -31,14 +33,14 @@ export default async function RecordDetailPage({ api.records.getRecord(recordId), ]); - if (!moduleWithFields || !record) return
Nicht gefunden
; + if (!moduleWithFields || !record) return
{t('notFound')}
; const fields = moduleWithFields.fields; return ( [0]['fields']} diff --git a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/record-detail-client.tsx b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/record-detail-client.tsx index a549b2cdc..378800a09 100644 --- a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/record-detail-client.tsx +++ b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/[recordId]/record-detail-client.tsx @@ -147,17 +147,19 @@ export function RecordDetailClient({ - - - + +
) : ( <> -
-
NameBeschreibung + {t('departments.name')} + + {t('departments.description')} + + {t('departments.actions')} +
{String(dept.description ?? '—')} +
+ +
+
+
+
- + {mappedFields.map((f) => (
# + # + {f.display_name} diff --git a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/import/page.tsx b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/import/page.tsx index 7c1d2217b..be37bb101 100644 --- a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/import/page.tsx +++ b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/import/page.tsx @@ -1,5 +1,6 @@ import { createModuleBuilderApi } from '@kit/module-builder/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { getTranslations } from 'next-intl/server'; import { CmsPageShell } from '~/components/cms-page-shell'; @@ -11,11 +12,12 @@ interface ImportPageProps { export default async function ImportPage({ params }: ImportPageProps) { const { account, moduleId } = await params; + const t = await getTranslations('cms.modules'); const client = getSupabaseServerClient(); const api = createModuleBuilderApi(client); const moduleWithFields = await api.modules.getModuleWithFields(moduleId); - if (!moduleWithFields) return
Modul nicht gefunden
; + if (!moduleWithFields) return
{t('notFound')}
; const { data: acct } = await client .from('accounts') @@ -23,7 +25,7 @@ export default async function ImportPage({ params }: ImportPageProps) { .eq('slug', account) .single(); - if (!acct) return
Account nicht gefunden
; + if (!acct) return
{t('accountNotFound')}
; const fields = (moduleWithFields.fields ?? []).map((f) => ({ name: String(f.name), @@ -33,7 +35,7 @@ export default async function ImportPage({ params }: ImportPageProps) { return ( ; const moduleWithFields = await api.modules.getModuleWithFields(moduleId); - if (!moduleWithFields) return
Modul nicht gefunden
; + if (!moduleWithFields) return
{t('notFound')}
; const fields = moduleWithFields.fields; return (
Modul nicht gefunden
; + return ; } const page = Number(search.page) || 1; @@ -50,7 +55,20 @@ export default async function ModuleDetailPage({ sortField, sortDirection, search: (search.q as string) ?? undefined, - filters, + filters: filters as Array<{ + field: string; + operator: + | 'eq' + | 'neq' + | 'gt' + | 'gte' + | 'lt' + | 'lte' + | 'like' + | 'is_null' + | 'not_null'; + value?: string; + }>, }); const allFields = moduleWithFields.fields; @@ -64,38 +82,43 @@ export default async function ModuleDetailPage({ })); return ( -
-
-
-

- {moduleWithFields.display_name} -

- {moduleWithFields.description && ( -

- {moduleWithFields.description} -

- )} + +
+
+
+ {moduleWithFields.description && ( +

+ {moduleWithFields.description} +

+ )} +
+
- + + + +
- - - - -
+ ); } diff --git a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/settings/delete-module-button.tsx b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/settings/delete-module-button.tsx index 95d8fdf48..39d00760d 100644 --- a/apps/web/app/[locale]/home/[account]/modules/[moduleId]/settings/delete-module-button.tsx +++ b/apps/web/app/[locale]/home/[account]/modules/[moduleId]/settings/delete-module-button.tsx @@ -47,16 +47,18 @@ export function DeleteModuleButton({ return ( - - - + +