diff --git a/.build-cache-buster b/.build-cache-buster new file mode 100644 index 000000000..675e77beb --- /dev/null +++ b/.build-cache-buster @@ -0,0 +1 @@ +Di. 31 März 2026 01:21:52 CEST diff --git a/.dockerignore b/.dockerignore index d26dfabb5..afbfc0dca 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ node_modules -.next +**/.next .turbo +**/.turbo .git *.md .env* @@ -10,3 +11,8 @@ apps/dev-tool .gitnexus .gsd .claude +.gemini +.junie +.github +docs +**/*.tsbuildinfo diff --git a/.env b/.env new file mode 100644 index 000000000..179a63669 --- /dev/null +++ b/.env @@ -0,0 +1,23 @@ +# ===================================================== +# MyEasyCMS v2 — Local Development Environment +# Auto-generated — Mo. 30 März 2026 17:11:01 CEST +# ===================================================== + +POSTGRES_PASSWORD=BqgGAwNuKeZZtaE2VJRAkxR48zj9XVw +JWT_SECRET=BhD5zXZXcdI23FtNk9kIeGN5z06UQ1fZUhCLvMr6 + +SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzc0ODgzNDYwLCJleHAiOjIwOTAyNDM0NjB9.hbeQae1gYRTcJQ_6m7MPjoDlYFhp4tsszEQD2g5q6vY +SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3NzQ4ODM0NjAsImV4cCI6MjA5MDI0MzQ2MH0.gqAnWDmPmnf-XMA9FcIKGo7qVVxtJw5UvSsTlTup3Ns + +SITE_URL=http://localhost:3000 +APP_PORT=3000 + +KONG_HTTP_PORT=8000 +KONG_HTTPS_PORT=8443 +API_EXTERNAL_URL=http://localhost:8000 + +ENABLE_EMAIL_AUTOCONFIRM=true +DISABLE_SIGNUP=false +JWT_EXPIRY=3600 + +DB_WEBHOOK_SECRET=local-dev-webhook-secret diff --git a/.env.production.example b/.env.production.example index f801bc32b..a79d41842 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,10 +1,12 @@ # ===================================================== -# MyEasyCMS v2 — Environment Variables +# MyEasyCMS v2 — Environment Variables (Production) # Copy to .env and fill in your values # ===================================================== -# --- Supabase --- +# --- Supabase Database --- POSTGRES_PASSWORD=change-me-to-a-strong-password + +# --- Supabase Auth --- JWT_SECRET=change-me-to-at-least-32-characters-long-secret # Generate these with: npx supabase gen keys @@ -15,11 +17,17 @@ SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here SITE_URL=https://myeasycms.de APP_PORT=3000 -# --- Kong --- +# --- Kong (API Gateway) --- KONG_HTTP_PORT=8000 KONG_HTTPS_PORT=8443 API_EXTERNAL_URL=https://api.myeasycms.de +# --- Studio (Dashboard) --- +STUDIO_PORT=54323 + +# --- Inbucket (Email testing — dev only) --- +INBUCKET_PORT=54324 + # --- Email (SMTP) --- SMTP_HOST=smtp.example.com SMTP_PORT=587 @@ -27,7 +35,7 @@ SMTP_USER=noreply@myeasycms.de SMTP_PASS=your-smtp-password SMTP_ADMIN_EMAIL=admin@myeasycms.de -# --- Auth --- +# --- Auth Settings --- ENABLE_EMAIL_AUTOCONFIRM=false DISABLE_SIGNUP=false JWT_EXPIRY=3600 diff --git a/Dockerfile b/Dockerfile index 875c24f4e..b70083d30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,23 +2,15 @@ FROM node:22-alpine AS base RUN corepack enable && corepack prepare pnpm@latest --activate WORKDIR /app -# --- Install deps --- -FROM base AS deps -COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ -COPY apps/web/package.json ./apps/web/ -COPY apps/dev-tool/package.json ./apps/dev-tool/ -COPY tooling/ ./tooling/ -COPY packages/ ./packages/ -RUN pnpm install --frozen-lockfile - -# --- Build --- +# --- Install + Build in one stage --- FROM base AS builder -COPY --from=deps /app/ ./ +ARG CACHE_BUST=1 COPY . . +RUN pnpm install --no-frozen-lockfile ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_PUBLIC_SITE_URL=https://myeasycms.de -ENV NEXT_PUBLIC_SUPABASE_URL=placeholder -ENV NEXT_PUBLIC_SUPABASE_PUBLIC_KEY=placeholder +ENV NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000 +ENV NEXT_PUBLIC_SUPABASE_PUBLIC_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzc0ODgzNDYwLCJleHAiOjIwOTAyNDM0NjB9.hbeQae1gYRTcJQ_6m7MPjoDlYFhp4tsszEQD2g5q6vY RUN pnpm --filter web build # --- Run --- diff --git a/QA_TEST_PLAN.md b/QA_TEST_PLAN.md new file mode 100644 index 000000000..755445a9a --- /dev/null +++ b/QA_TEST_PLAN.md @@ -0,0 +1,501 @@ +# MyEasyCMS v2 — Comprehensive QA Test Plan + +## Test Environment +- App: localhost:3000 (Docker) +- Supabase: localhost:8000 (Kong gateway) +- Studio: localhost:54323 +- DB: supabase/postgres:15.8.1.060 + +## Test Accounts +| Email | Password | Role | Team | +|-------|----------|------|------| +| super-admin@makerkit.dev | testingpassword | Super Admin | - | +| test@makerkit.dev | testingpassword | Owner | Makerkit | +| owner@makerkit.dev | testingpassword | Owner | Makerkit | +| member@makerkit.dev | testingpassword | Member | Makerkit | +| custom@makerkit.dev | testingpassword | Custom | Makerkit | + +## Test Categories + +### A. Authentication & Authorization (12 tests) +### B. Public Pages (8 tests) +### C. Team Dashboard & Navigation (10 tests) +### D. Member Management CRUD (15 tests) +### E. Course Management CRUD (10 tests) +### F. Event Management CRUD (8 tests) +### G. Document Generation (8 tests) +### H. Newsletter System (6 tests) +### I. Site Builder & Public Club Pages (12 tests) +### J. Finance / SEPA (6 tests) +### K. Fischerei Module (12 tests) +### L. Sitzungsprotokolle Module (8 tests) +### M. Verbandsverwaltung Module (8 tests) +### N. Module Activation System (6 tests) +### O. Admin Panel (8 tests) +### P. Public Registration APIs (9 tests) +### Q. Edge Cases & Error Handling (10 tests) +### R. Permission Boundaries (8 tests) + +Total: ~156 test cases + +--- + +## A. AUTHENTICATION & AUTHORIZATION + +### A1. Login — Valid credentials +- Setup: Logged out +- Steps: Navigate /auth/sign-in, enter test@makerkit.dev / testingpassword, submit +- Expected: Redirect to /home, user avatar visible +- Pass: URL contains /home, no error toast + +### A2. Login — Invalid password +- Setup: Logged out +- Steps: Enter test@makerkit.dev / wrongpassword, submit +- Expected: Error message, stays on sign-in page +- Pass: Error alert visible, URL still /auth/sign-in + +### A3. Login — Empty fields +- Steps: Click submit without entering anything +- Expected: Client-side validation prevents submit +- Pass: Form doesn't submit, validation indicators shown + +### A4. Login — SQL injection attempt +- Steps: Enter ' OR 1=1-- as email +- Expected: Validation error (not valid email format) +- Pass: No crash, proper error message + +### A5. Registration — Valid +- Steps: Navigate /auth/sign-up, enter unique email, password >= 6 chars, matching repeat +- Expected: Account created, redirect to /home +- Pass: User exists in DB, logged in + +### A6. Registration — Duplicate email +- Steps: Try registering with test@makerkit.dev +- Expected: "Diese Anmeldedaten werden bereits verwendet" +- Pass: Error shown, no crash + +### A7. Registration — Weak password +- Steps: Enter password "123" +- Expected: Validation error (too short) +- Pass: Form doesn't submit + +### A8. Registration — Mismatched passwords +- Steps: Enter different passwords in password and repeat fields +- Expected: Validation error +- Pass: Form shows mismatch error + +### A9. Session persistence +- Steps: Login, close tab, open new tab to /home +- Expected: Still logged in +- Pass: Dashboard loads, not redirected to sign-in + +### A10. Logout +- Steps: Click account dropdown > "Abmelden" +- Expected: Session cleared, redirect to sign-in +- Pass: Accessing /home redirects to /auth/sign-in + +### A11. Protected route — unauthenticated access +- Steps: Clear cookies, navigate to /home/makerkit +- Expected: Redirect to /auth/sign-in +- Pass: URL changes to sign-in + +### A12. Admin route — non-admin access +- Steps: Login as member@makerkit.dev, navigate to /admin +- Expected: 404 (AdminGuard returns notFound) +- Pass: 404 page shown + +--- + +## B. PUBLIC PAGES + +### B1. Landing page loads with real content +- Expected: "Vereinsverwaltung, die mitwächst", "69.000", "SEPA" +- Pass: No placeholder/filler text + +### B2. Pricing page +- Navigate /pricing +- Expected: Pricing table renders + +### B3. FAQ page +- Navigate /faq +- Expected: FAQ items render + +### B4. Contact page +- Navigate /contact +- Expected: Contact form with name/email/message fields + +### B5. Blog page +- Navigate /blog +- Expected: Blog listing (may be empty) + +### B6. Legal pages (privacy, terms, cookies) +- Navigate /privacy-policy, /terms-of-service, /cookie-policy +- Expected: Each loads without error + +### B7. Public club page +- Navigate /club/makerkit +- Expected: Club homepage with Puck content +- Pass: Real data (courses, events) shown, not placeholders + +### B8. Non-existent club page +- Navigate /club/nonexistent +- Expected: 404 page +- Pass: Proper 404, no crash + +--- + +## C. TEAM DASHBOARD & NAVIGATION + +### C1. Team dashboard loads with stats +- Login as test@makerkit.dev, navigate /home/makerkit +- Expected: 4 stat cards, quick actions, activity feed +- Pass: Numbers render (even if 0) + +### C2. All sidebar links work +- Click each sidebar item: Dashboard, Module, Mitglieder, Kurse, Veranstaltungen, Finanzen, Dokumente, Newsletter, Website +- Expected: Each page loads without error + +### C3. Account switcher +- Click account dropdown > "Arbeitsbereich wechseln" +- Expected: Shows list of accounts + +### C4. Team settings — rename +- Navigate /home/makerkit/settings +- Expected: Team name editable, save works + +### C5. Team members list +- Navigate /home/makerkit/members +- Expected: Shows 4 members with roles + +### C6. Non-existent team slug +- Navigate /home/nonexistent +- Expected: Redirect or error page + +### C7. Profile settings +- Navigate /home/settings (personal) +- Expected: Name, language, email change form + +### C8. Theme toggle +- Click theme toggle in nav +- Expected: Dark/light theme switches + +### C9. Breadcrumb navigation +- Navigate to nested page, click breadcrumb links +- Expected: Navigate back correctly + +### C10. Mobile responsive (viewport 375px) +- Resize to mobile +- Expected: Sidebar collapses, hamburger menu works + +--- + +## D. MEMBER MANAGEMENT CRUD + +### D1. List members — empty state +- Navigate /home/makerkit/members-cms +- Expected: Shows "1 Mitglieder insgesamt" (Max Mustermann from earlier test) + +### D2. Create member — all fields +- Navigate /home/makerkit/members-cms/new +- Fill: Vorname=Anna, Nachname=Schmidt, Email=anna@test.de, PLZ=93047, Ort=Regensburg +- Expected: Member created, redirect to list + +### D3. Create member — required fields only +- Fill: Vorname=Test, Nachname=Minimal +- Expected: Created successfully + +### D4. Create member — empty required fields +- Submit with empty Vorname +- Expected: Validation error + +### D5. Create member — invalid email format +- Enter email "notanemail" +- Expected: Validation error + +### D6. Create member — duplicate email +- Create member with same email as existing +- Expected: DB constraint error or validation + +### D7. View member detail +- Click on member name in list +- Expected: Detail page with all fields + +### D8. Edit member +- Navigate to member edit page +- Change Vorname, save +- Expected: Updated in DB and UI + +### D9. Search members +- Type in search box +- Expected: List filters in real-time or on submit + +### D10. Filter by status +- Use status dropdown +- Expected: Only matching members shown + +### D11. Member with SEPA mandate +- Create member with IBAN field filled +- Expected: IBAN saved correctly + +### D12. Member import +- Navigate /home/makerkit/members-cms/import +- Expected: Import wizard loads + +### D13. Member statistics +- Navigate /home/makerkit/members-cms/statistics +- Expected: Statistics page loads + +### D14. Member departments +- Navigate /home/makerkit/members-cms/departments +- Expected: Department management page + +### D15. Pagination +- With many members, verify pagination controls work + +--- + +## E. COURSE MANAGEMENT + +### E1. Course list — shows existing course +- Navigate /home/makerkit/courses +- Expected: "Schwimmkurs Anfänger" visible + +### E2. Create course — valid +- Navigate /home/makerkit/courses/new +- Fill required fields, submit +- Expected: Created, redirect to courses list + +### E3. Course detail +- Click on course name +- Expected: Detail page with participants, schedule + +### E4. Course calendar +- Navigate /home/makerkit/courses/calendar +- Expected: Calendar view loads + +### E5. Course categories +- Navigate /home/makerkit/courses/categories +- Expected: Category management page + +### E6. Course instructors +- Navigate /home/makerkit/courses/instructors +- Expected: Instructor list + +### E7. Course locations +- Navigate /home/makerkit/courses/locations +- Expected: Location management + +### E8. Course statistics +- Navigate /home/makerkit/courses/statistics +- Expected: Statistics page loads + +### E9. Course participants +- Navigate to course > participants tab +- Expected: Participant list (may be empty) + +### E10. Course attendance +- Navigate to course > attendance tab +- Expected: Attendance tracking page + +--- + +## G. DOCUMENT GENERATION + +### G1. Document type selection page +- Navigate /home/makerkit/documents +- Expected: 6 document types shown + +### G2. Generate member cards (PDF) +- Select Mitgliedsausweis, fill title, click Generieren +- Expected: PDF downloads with .pdf extension +- Pass: File downloads, success banner shown + +### G3. Generate labels (HTML) +- Select Etiketten, fill title, click Generieren +- Expected: HTML file downloads with .html extension + +### G4. Generate report (Excel) +- Select Bericht, fill title, click Generieren +- Expected: XLSX downloads with .xlsx extension + +### G5. Coming soon types (invoice, letter, certificate) +- Select Rechnung +- Expected: "Demnächst verfügbar" banner, button disabled + +### G6. Generate with empty title +- Leave title blank, try to submit +- Expected: Validation prevents submit (required field) + +### G7. Generate with no members +- Create new account with no members, try generating +- Expected: Error "Keine aktiven Mitglieder" + +### G8. Document templates page +- Navigate /home/makerkit/documents/templates +- Expected: Page loads, shows empty state + +--- + +## I. SITE BUILDER & PUBLIC CLUB PAGES + +### I1. Site builder list +- Navigate /home/makerkit/site-builder +- Expected: Shows pages list (hello, Über uns) + +### I2. Create new page +- Navigate /home/makerkit/site-builder/new +- Fill title + slug, submit +- Expected: Page created, Puck editor opens + +### I3. Site builder settings +- Navigate /home/makerkit/site-builder/settings +- Expected: Design settings (name, colors, font, publish toggle) + +### I4. Public page — published +- Navigate /club/makerkit/hello +- Expected: Puck content renders + +### I5. Public page — unpublished +- Navigate /club/makerkit/ueber-uns +- Expected: 404 (not published) + +### I6. Public page — non-existent +- Navigate /club/makerkit/nonexistent +- Expected: 404 + +### I7. Course data on public page +- /club/makerkit should show "Schwimmkurs Anfänger" +- Expected: Real course data, not placeholders + +### I8. Course registration form +- Click "Anmelden" on a course on the public page +- Fill form, submit +- Expected: "Anmeldung erfolgreich!" message + +### I9. Event registration (no events) +- EventList block should show "Keine anstehenden Veranstaltungen" + +### I10. Membership application form +- Fill "Mitglied werden" form on public page +- Submit with valid data +- Expected: Application saved in DB + +### I11. Membership application — invalid email +- Submit with invalid email +- Expected: Client-side validation error + +### I12. Newsletter signup +- Use newsletter signup block +- Expected: Subscription created or error + +--- + +## N. MODULE ACTIVATION SYSTEM + +### N1. Module toggles page shows all modules +- Navigate /home/makerkit/modules +- Expected: Fischerei, Sitzungsprotokolle, Verbandsverwaltung toggles visible + +### N2. Activate Fischerei +- Toggle Fischerei ON +- Expected: "Fischerei" appears in sidebar + +### N3. Deactivate Fischerei +- Toggle Fischerei OFF +- Expected: "Fischerei" disappears from sidebar + +### N4. Activate Sitzungsprotokolle +- Toggle ON +- Expected: "Sitzungsprotokolle" appears in sidebar + +### N5. Activate Verbandsverwaltung +- Toggle ON +- Expected: "Verbandsverwaltung" appears in sidebar + +### N6. Direct URL access to deactivated module +- Deactivate Fischerei, navigate /home/makerkit/fischerei +- Expected: Page still loads (data exists) but not in sidebar + +--- + +## P. PUBLIC REGISTRATION APIS + +### P1. Course registration — valid +- POST /api/club/course-register with valid courseId, name, email +- Expected: 200 { success: true } + +### P2. Course registration — missing fields +- POST without email +- Expected: 400 error + +### P3. Course registration — invalid courseId +- POST with random UUID +- Expected: DB error or 500 + +### P4. Event registration — valid +- POST /api/club/event-register with valid data +- Expected: 200 success (need an event first) + +### P5. Membership application — valid +- POST /api/club/membership-apply with all fields +- Expected: Row inserted in membership_applications + +### P6. Membership application — invalid email +- POST with email "notanemail" +- Expected: 400 validation error + +### P7. Membership application — missing accountId +- POST without accountId +- Expected: Error + +### P8. Rate limiting (if any) +- Send 100 rapid POSTs +- Expected: No crash (may or may not rate limit) + +### P9. XSS in form fields +- Submit as firstName +- Expected: Stored as text, not executed on render + +--- + +## Q. EDGE CASES & ERROR HANDLING + +### Q1. API healthcheck +- GET /api/healthcheck +- Expected: { services: { database: true } } + +### Q2. 404 for unknown route +- Navigate /nonexistent +- Expected: 404 page with "Seite nicht gefunden" + +### Q3. Direct DB access via PostgREST (anon) +- GET localhost:8000/rest/v1/members (anon key, no auth) +- Expected: Empty array (RLS blocks anon) + +### Q4. JWT expiration handling +- Login, wait for token expiry (3600s), try action +- Expected: Auto-refresh or redirect to login + +### Q5. Concurrent writes +- Two users edit same member simultaneously +- Expected: Last write wins, no crash + +### Q6. Very long text input +- Enter 10000 char string in a text field +- Expected: Validation limits or graceful handling + +### Q7. Unicode/emoji in names +- Create member with name "Müller-Lüdenscheidt 🎣" +- Expected: Saved and displayed correctly + +### Q8. Browser back button +- Navigate deep, press back +- Expected: Previous page loads correctly + +### Q9. Double form submission +- Click submit twice rapidly +- Expected: Only one record created (isPending disables button) + +### Q10. Network disconnect during submit +- Submit form, disconnect network mid-request +- Expected: Error message, no partial data corruption diff --git a/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx b/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx index 1756eb8d4..a750b2d94 100644 --- a/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx +++ b/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx @@ -1,6 +1,7 @@ import { createClient } from '@supabase/supabase-js'; import { notFound } from 'next/navigation'; import { SiteRenderer } from '@kit/site-builder/components'; +import type { SiteData } from '@kit/site-builder/context'; interface Props { params: Promise<{ slug: string; page: string[] }> } @@ -23,9 +24,26 @@ export default async function ClubSubPage({ params }: Props) { .eq('account_id', account.id).eq('slug', pageSlug).eq('is_published', true).maybeSingle(); if (!sitePageData) notFound(); + // Pre-fetch CMS data for Puck components + const [eventsRes, coursesRes, postsRes] = await Promise.all([ + supabase.from('events').select('id, name, event_date, event_time, location, fee, status') + .eq('account_id', account.id).order('event_date', { ascending: true }).limit(20), + supabase.from('courses').select('id, name, start_date, end_date, fee, capacity, status') + .eq('account_id', account.id).order('start_date', { ascending: true }).limit(20), + supabase.from('cms_posts').select('id, title, excerpt, cover_image, published_at, slug') + .eq('account_id', account.id).eq('status', 'published').order('published_at', { ascending: false }).limit(20), + ]); + + const siteData: SiteData = { + accountId: account.id, + events: eventsRes.data ?? [], + courses: (coursesRes.data ?? []).map(c => ({ ...c, enrolled_count: 0 })), + posts: postsRes.data ?? [], + }; + return (
- Zimmerauslastung im Überblick -
+ Zimmerauslastung im Überblick +
Gästeverwaltung
- Zimmer und Buchungen verwalten -
+ Zimmer und Buchungen verwalten +
+ Seite {page} von {totalPages} ({total} Einträge) +
Zimmerverwaltung
- Kurstermine im Überblick -
+ Kurstermine im Überblick +
Kurskategorien verwalten
Dozentenpool verwalten
Kurs- und Veranstaltungsorte verwalten
- Kursangebot verwalten -
+ Kursangebot verwalten +
+ Seite {page} von {totalPages} ({courses.total} Einträge) +
Demnächst verfügbar
+ Die Generierung von “{DOCUMENT_LABELS[selectedType]}” + befindet sich noch in Entwicklung und wird in Kürze verfügbar sein. +
+ Hinweis:{' '} + {selectedType === 'member-card' + ? 'Es werden Mitgliedsausweise für alle aktiven Mitglieder generiert (4 Karten pro A4-Seite).' + : selectedType === 'labels' + ? 'Es werden Adressetiketten im Avery-L7163-Format für alle aktiven Mitglieder erzeugt.' + : selectedType === 'report' + ? 'Es wird eine Excel-Datei mit allen Mitgliederdaten erstellt.' + : 'Wählen Sie den gewünschten Dokumenttyp, um die Generierung zu starten.'} +
Fehler bei der Generierung
{result.error}
Dokument erfolgreich erstellt!
+ Die Datei “{result.fileName}” wurde heruntergeladen. +
- Hinweis: Die Dokumentgenerierung verwendet - Ihre gespeicherten Vorlagen. Stellen Sie sicher, dass eine - passende Vorlage für den gewählten Dokumenttyp existiert. -
- Dokumente erstellen und verwalten -
Ferienpässe und Ferienprogramme verwalten
{t('holidayPassesDescription')}
- Veranstaltungen und Ferienprogramme + {t('description')}
- Anmeldungen aller Veranstaltungen im Überblick + {t('registrationsOverview')}
- SEPA-Einzüge und Rechnungen -
+ SEPA-Einzüge und Rechnungen +
+ Gewässerpachtverträge verwalten +
+ Erstellen Sie Ihren ersten Pachtvertrag. +
+ Erlaubnisscheine und Gewässerkarten verwalten +
+ Erstellen Sie Ihren ersten Erlaubnisschein. +
+ Fangstatistiken und Auswertungen +
+ Sobald Fangbücher eingereicht und geprüft werden, erscheinen hier Statistiken und Auswertungen. +
Ort
{protocol.location}
Teilnehmer
{protocol.attendees}
Anmerkungen
{protocol.remarks}
+ Erstellen Sie ein neues Sitzungsprotokoll mit Tagesordnungspunkten. +
+ Alle offenen und in Bearbeitung befindlichen Tagesordnungspunkte über alle Protokolle. +
{members.length} aktive Mitglieder
{String(m.last_name)}, {String(m.first_name)}
Nr. {String(m.member_number ?? '—')}
- Die PDF-Generierung erfordert die Installation von @react-pdf/renderer. - Nach der Installation können Mitgliedsausweise einzeln oder als Stapel erstellt werden. -
@react-pdf/renderer
+ Aktivieren oder deaktivieren Sie Module für Ihren Verein +
{mod.label}
+ {mod.description} +
- Verwalten Sie Ihre Datenmodule -
- Noch keine Module vorhanden. Erstellen Sie Ihr erstes Modul. -
- {String(module.description)} -
+ Noch keine Module vorhanden. Erstellen Sie Ihr erstes Modul. +
+ {String(module.description)} +
+ {startIdx + 1}–{Math.min(startIdx + PAGE_SIZE, totalItems)} von {totalItems} +
{booking.check_in @@ -213,12 +223,11 @@ export default async function TeamAccountHomePage({ ))} {bookings.data.length === 0 && events.data.length === 0 && ( -
- Noch keine Aktivitäten vorhanden -
Seiten
{pages.length}
Veröffentlicht
{publishedCount}
Status
{settings?.is_public ? '🟢 Online' : '🔴 Offline'}
+ + + {isOnline ? 'Online' : 'Offline'} + +
{detail.club.short_name}
Keine Einträge vorhanden.
{String(item.description)}
+ Funktionen, Vereinstypen und Beitragsarten verwalten +
+ Entwicklung der Mitgliedsvereine und Gesamtmitglieder im Zeitverlauf +
+ Die Statistiken werden automatisch aus den Vereinsdaten und der Verbandshistorie berechnet. + Pflegen Sie die Mitgliederzahlen in den einzelnen Vereinsdetails, um aktuelle Auswertungen zu erhalten. +
+ Das angeforderte Konto existiert nicht oder Sie haben keine Berechtigung darauf zuzugreifen. +
+ Es wurden noch keine Fangbücher angelegt. +
+ Erstellen Sie Ihren ersten Wettbewerb. +
+ Gewässer, Fischarten, Besatz, Fangbücher und mehr verwalten +
Gewässer
{stats.watersCount}
Fischarten
{stats.speciesCount}
Aktive Pachten
{stats.activeLeasesCount}
Offene Fangbücher
{stats.pendingCatchBooksCount}
Kommende Wettbewerbe
{stats.upcomingCompetitionsCount}
Besatzkosten (lfd. Jahr)
+ {stats.stockingCostYtd.toLocaleString('de-DE', { + style: 'currency', + currency: 'EUR', + })} +
+ Noch keine Besatzaktionen vorhanden. +
+ Keine Fangbücher zur Prüfung ausstehend. +
+ Erstellen Sie Ihre erste Fischart. +
+ Tragen Sie den ersten Besatz ein. +
+ Erstellen Sie Ihr erstes Gewässer, um loszulegen. +
Kurzbeschreibung des Beitrags...
01.01.2026
Noch keine Beiträge vorhanden.
{post.excerpt}
{new Date(post.published_at).toLocaleDateString('de-DE')}
10:00 — Vereinsheim
Mo, 18:00 — 20:00
{description}
{[5, 10, 18][i]} €
pro Monat
Keine anstehenden Veranstaltungen.
+ {event.event_time ? event.event_time.slice(0, 5) : ''}{event.location ? ` — ${event.location}` : ''} +
{errorMsg}
Aktuell keine Kurse verfügbar.
Ab {new Date(course.start_date).toLocaleDateString('de-DE')}
Ihre Bewerbung wurde eingereicht! Wir melden uns bei Ihnen.
+ Protokolle, Tagesordnungspunkte und Aufgaben verwalten +
Protokolle gesamt
{stats.totalProtocols}
Protokolle dieses Jahr
{stats.thisYearProtocols}
Offene Aufgaben
{stats.openTasks}
Überfällige Aufgaben
{stats.overdueTasks}
+ Noch keine Protokolle vorhanden. +
{protocol.title}
+ {new Date(protocol.meeting_date).toLocaleDateString('de-DE')} + {' · '} + {MEETING_TYPE_LABELS[protocol.meeting_type] ?? protocol.meeting_type} +
+ Keine überfälligen Aufgaben. +
{task.title}
+ Protokoll: {task.meeting_protocols.title} +
+ Alle Tagesordnungspunkte sind erledigt oder vertagt. +
+ {task.description} +
{task.meeting_protocols.title}
+ {new Date(task.meeting_protocols.meeting_date).toLocaleDateString('de-DE')} +
+ Fügen Sie Tagesordnungspunkte zu diesem Protokoll hinzu. +
{item.title}
+ {item.description} +
+ Erstellen Sie Ihr erstes Sitzungsprotokoll, um loszulegen. +
Keine Ansprechpartner vorhanden.
Keine Beitragsabrechnungen vorhanden.
Keine Notizen vorhanden.
{String(note.content)}
+ Erstellen Sie Ihren ersten Verein, um loszulegen. +
+ Vereine, Beiträge, Kontakte und Aufgaben verwalten +
Aktive Vereine
{stats.activeClubsCount}
Gesamtmitglieder
{stats.totalMembers.toLocaleString('de-DE')}
Offene Beiträge
+ {stats.unpaidAmount.toLocaleString('de-DE', { + style: 'currency', + currency: 'EUR', + })} +
{stats.unpaidBillingsCount} Rechnungen
{stats.openNotesCount}
Vereinstypen
{stats.typesCount}
Archivierte Vereine
{stats.archivedClubsCount}
+ Alle Vereine haben mindestens einen Ansprechpartner. +