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

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

View File

@@ -170,7 +170,34 @@
"holidayPasses": "Ferienpässe",
"eventDate": "Datum",
"eventLocation": "Ort",
"capacity": "Plätze"
"capacity": "Plätze",
"allEvents": "Alle Veranstaltungen",
"locations": "Orte",
"totalCapacity": "Kapazität gesamt",
"noEvents": "Keine Veranstaltungen vorhanden",
"noEventsDescription": "Erstellen Sie Ihre erste Veranstaltung, um loszulegen.",
"name": "Name",
"status": "Status",
"paginationPage": "Seite {page} von {totalPages}",
"paginationPrevious": "Vorherige",
"paginationNext": "Nächste",
"registrationsOverview": "Anmeldungen aller Veranstaltungen im Überblick",
"totalRegistrations": "Anmeldungen gesamt",
"withRegistrations": "Mit Anmeldungen",
"overviewByEvent": "Übersicht nach Veranstaltung",
"noEventsForRegistrations": "Erstellen Sie eine Veranstaltung, um Anmeldungen zu erhalten.",
"utilization": "Auslastung",
"event": "Veranstaltung",
"holidayPassesDescription": "Ferienpässe und Ferienprogramme verwalten",
"newHolidayPass": "Neuer Ferienpass",
"noHolidayPasses": "Keine Ferienpässe vorhanden",
"noHolidayPassesDescription": "Erstellen Sie Ihren ersten Ferienpass.",
"allHolidayPasses": "Alle Ferienpässe",
"year": "Jahr",
"price": "Preis",
"validFrom": "Gültig von",
"validUntil": "Gültig bis",
"newEventDescription": "Veranstaltung oder Ferienprogramm anlegen"
},
"finance": {
"title": "Finanzen",
@@ -259,7 +286,15 @@
"finance.write": "Finanzen bearbeiten",
"finance.sepa": "SEPA-Einzüge ausführen",
"documents.generate": "Dokumente generieren",
"newsletter.send": "Newsletter versenden"
"newsletter.send": "Newsletter versenden",
"fischerei.read": "Fischerei lesen",
"fischerei.write": "Fischerei bearbeiten",
"meetings.read": "Sitzungsprotokolle lesen",
"meetings.write": "Sitzungsprotokolle bearbeiten",
"meetings.delete": "Sitzungsprotokolle löschen",
"verband.read": "Verbandsverwaltung lesen",
"verband.write": "Verbandsverwaltung bearbeiten",
"verband.delete": "Verbandsverwaltung löschen"
},
"status": {
"active": "Aktiv",
@@ -267,5 +302,477 @@
"archived": "Archiviert",
"locked": "Gesperrt",
"deleted": "Gelöscht"
},
"fischerei": {
"title": "Fischerei",
"description": "Gewässer, Fischarten, Besatz, Pachten, Fangbücher und Wettbewerbe verwalten",
"dashboard": {
"title": "Übersicht",
"watersCount": "Gewässer",
"speciesCount": "Fischarten",
"activeLeases": "Aktive Pachten",
"pendingCatchBooks": "Offene Fangbücher",
"upcomingCompetitions": "Kommende Wettbewerbe",
"stockingCostYtd": "Besatzkosten (lfd. Jahr)",
"recentStocking": "Letzte Besatzaktionen",
"pendingReview": "Zur Prüfung ausstehend"
},
"waters": {
"title": "Gewässer",
"newWater": "Neues Gewässer",
"editWater": "Gewässer bearbeiten",
"name": "Name",
"shortName": "Kurzname",
"waterType": "Gewässertyp",
"description": "Beschreibung",
"surfaceArea": "Fläche (ha)",
"length": "Länge (m)",
"width": "Breite (m)",
"avgDepth": "Durchschnittstiefe (m)",
"maxDepth": "Maximaltiefe (m)",
"outflow": "Abfluss",
"location": "Lage/Standort",
"county": "Landkreis",
"gpsCoordinates": "GPS-Koordinaten",
"lfvNumber": "LFV-Nummer",
"lfvName": "LFV-Name",
"costShares": "Kostenanteile",
"electrofishing": "Elektrofischerei-Genehmigung beantragt",
"costCenter": "Kostenstelle",
"archived": "Archiviert",
"showArchived": "Archivierte anzeigen",
"speciesRules": "Fischarten & Regelungen",
"stockingHistory": "Besatzhistorie",
"leases": "Pachten",
"inspectors": "Kontrolleure",
"map": "Karte",
"catchStats": "Fangstatistik",
"waterTypes": {
"fluss": "Fluss",
"bach": "Bach",
"see": "See",
"teich": "Teich",
"weiher": "Weiher",
"kanal": "Kanal",
"stausee": "Stausee",
"baggersee": "Baggersee",
"sonstige": "Sonstige"
},
"basicData": "Grunddaten",
"dimensions": "Abmessungen",
"geography": "Geografie",
"administration": "Verwaltung"
},
"species": {
"title": "Fischarten",
"newSpecies": "Neue Fischart",
"editSpecies": "Fischart bearbeiten",
"name": "Name",
"nameLatin": "Lateinischer Name",
"nameLocal": "Lokaler Name",
"maxAge": "Max. Alter (Jahre)",
"maxWeight": "Max. Gewicht (kg)",
"maxLength": "Max. Länge (cm)",
"protectedMinSize": "Schonmaß (cm)",
"protectionPeriod": "Schonzeit",
"protectionStart": "Schonzeit Beginn (MM.TT)",
"protectionEnd": "Schonzeit Ende (MM.TT)",
"spawningSeason": "Sonderschonzeit (SZG)",
"kFactor": "K-Faktor",
"kFactorAvg": "K-Faktor (Durchschnitt)",
"kFactorMin": "K-Faktor (Min)",
"kFactorMax": "K-Faktor (Max)",
"pricePerUnit": "Preis pro Einheit",
"maxCatchPerDay": "Max. Fang/Tag",
"maxCatchPerYear": "Max. Fang/Jahr",
"individualRecording": "Einzelerfassung",
"active": "Aktiv",
"biometrics": "Biometrische Daten",
"protection": "Schutzbestimmungen",
"quotas": "Fangbegrenzungen"
},
"stocking": {
"title": "Besatz",
"newStocking": "Besatz eintragen",
"date": "Besatzdatum",
"water": "Gewässer",
"species": "Fischart",
"quantity": "Anzahl (Stück)",
"weight": "Gewicht (kg)",
"ageClass": "Altersklasse",
"cost": "Kosten (EUR)",
"supplier": "Lieferant",
"remarks": "Bemerkungen",
"ageClasses": {
"brut": "Brut",
"soemmerlinge": "Sömmerlinge",
"einsoemmerig": "1-sömmrig",
"zweisoemmerig": "2-sömmrig",
"dreisoemmerig": "3-sömmrig",
"vorgestreckt": "Vorgestreckt",
"setzlinge": "Setzlinge",
"laichfische": "Laichfische",
"sonstige": "Sonstige"
},
"totalCost": "Gesamtkosten",
"totalQuantity": "Gesamtmenge"
},
"leases": {
"title": "Pachten",
"newLease": "Neue Pacht",
"editLease": "Pacht bearbeiten",
"lessor": "Verpächter",
"lessorAddress": "Adresse des Verpächters",
"startDate": "Beginn",
"endDate": "Ende",
"duration": "Laufzeit (Jahre)",
"initialAmount": "Anfangsbetrag (EUR)",
"fixedIncrease": "Feste jährl. Erhöhung (EUR)",
"percentageIncrease": "Prozentuale jährl. Erhöhung (%)",
"paymentMethod": "Zahlungsart",
"specialAgreements": "Sondervereinbarungen",
"currentAmount": "Aktueller Jahresbetrag",
"paymentMethods": {
"bar": "Bar",
"lastschrift": "Lastschrift",
"ueberweisung": "Überweisung"
}
},
"catchBooks": {
"title": "Fangbücher",
"newCatchBook": "Neues Fangbuch",
"member": "Mitglied",
"year": "Jahr",
"fishingDays": "Angeltage",
"totalCatches": "Gesamtfänge",
"status": "Status",
"verification": "Bewertung",
"submit": "Einreichen",
"review": "Prüfen",
"approve": "Akzeptieren",
"reject": "Ablehnen",
"submitted": "Eingereicht am",
"checked": "Geprüft",
"flyFisher": "Fliegenfischer",
"cardNumbers": "Erlaubnisschein-Nr.",
"statuses": {
"offen": "Offen",
"eingereicht": "Eingereicht",
"geprueft": "Geprüft",
"akzeptiert": "Akzeptiert",
"abgelehnt": "Abgelehnt"
},
"verifications": {
"sehrgut": "Sehr gut",
"gut": "Gut",
"ok": "OK",
"schlecht": "Schlecht",
"falsch": "Falsch",
"leer": "Leer"
}
},
"catches": {
"title": "Fänge",
"newCatch": "Fang eintragen",
"date": "Datum",
"species": "Fischart",
"water": "Gewässer",
"quantity": "Anzahl",
"length": "Länge (cm)",
"weight": "Gewicht (g)",
"kFactor": "K-Faktor",
"sizeCategory": "Größenkategorie",
"gender": "Geschlecht",
"permit": "Erlaubnisschein"
},
"permits": {
"title": "Erlaubnisscheine",
"newPermit": "Neuer Erlaubnisschein",
"name": "Bezeichnung",
"shortCode": "Kurzcode",
"primaryWater": "Hauptgewässer",
"totalQuantity": "Gesamtmenge",
"forSale": "Zum Verkauf",
"quotas": "Kontingente"
},
"inspectors": {
"title": "Gewässer-Kontrolleure",
"assignInspector": "Kontrolleur zuweisen",
"removeInspector": "Kontrolleur entfernen",
"assignmentStart": "Beginn",
"assignmentEnd": "Ende"
},
"competitions": {
"title": "Wettbewerbe",
"newCompetition": "Neuer Wettbewerb",
"name": "Bezeichnung",
"date": "Datum",
"water": "Gewässer",
"maxParticipants": "Max. Teilnehmer",
"scoring": "Wertung",
"scoreByCount": "Nach Anzahl",
"scoreByHeaviest": "Nach Schwerster",
"scoreByTotalWeight": "Nach Gesamtgewicht",
"scoreByLongest": "Nach Längstem",
"scoreByTotalLength": "Nach Gesamtlänge",
"participants": "Teilnehmer",
"addParticipant": "Teilnehmer hinzufügen",
"results": "Ergebnisse",
"computeResults": "Ergebnisse berechnen",
"categories": "Kategorien",
"rank": "Platz"
},
"suppliers": {
"title": "Lieferanten",
"newSupplier": "Neuer Lieferant",
"name": "Name",
"contactPerson": "Ansprechpartner",
"phone": "Telefon",
"email": "E-Mail",
"address": "Adresse"
},
"statistics": {
"title": "Statistiken",
"catchesBySpecies": "Fänge nach Fischart",
"catchesByWater": "Fänge nach Gewässer",
"catchesByYear": "Fänge nach Jahr",
"stockingOverview": "Besatzübersicht",
"totalCatches": "Gesamtfänge",
"totalWeight": "Gesamtgewicht (kg)",
"avgLength": "Durchschn. Länge (cm)",
"avgKFactor": "Durchschn. K-Faktor",
"filterYear": "Jahr filtern",
"filterWater": "Gewässer filtern"
},
"export": {
"exportStocking": "Besatz exportieren",
"exportCatches": "Fänge exportieren",
"formatCsv": "CSV",
"formatExcel": "Excel"
}
},
"meetings": {
"title": "Sitzungsprotokolle",
"description": "Sitzungen, Tagesordnungspunkte und Beschlüsse verwalten",
"dashboard": {
"title": "Übersicht",
"totalMeetings": "Sitzungen gesamt",
"openDecisions": "Offene Beschlüsse",
"upcomingMeetings": "Kommende Sitzungen",
"recentProtocols": "Letzte Protokolle"
},
"bodies": {
"title": "Gremien",
"newBody": "Neues Gremium",
"editBody": "Gremium bearbeiten",
"deleteBody": "Gremium löschen",
"name": "Name",
"shortName": "Kurzname",
"description": "Beschreibung",
"chairperson": "Vorsitzende(r)",
"members": "Mitglieder",
"meetingCycle": "Sitzungszyklus",
"active": "Aktiv",
"cycles": {
"weekly": "Wöchentlich",
"biweekly": "Zweiwöchentlich",
"monthly": "Monatlich",
"quarterly": "Vierteljährlich",
"biannual": "Halbjährlich",
"annual": "Jährlich",
"asNeeded": "Nach Bedarf"
}
},
"sessions": {
"title": "Sitzungen",
"newSession": "Neue Sitzung",
"editSession": "Sitzung bearbeiten",
"deleteSession": "Sitzung löschen",
"body": "Gremium",
"date": "Datum",
"startTime": "Beginn",
"endTime": "Ende",
"location": "Ort",
"status": "Status",
"agenda": "Tagesordnung",
"protocol": "Protokoll",
"attendees": "Anwesende",
"absentees": "Abwesende",
"guests": "Gäste",
"recorder": "Protokollführer(in)",
"statuses": {
"planned": "Geplant",
"inProgress": "Laufend",
"completed": "Abgeschlossen",
"cancelled": "Abgesagt"
}
},
"agendaItems": {
"title": "Tagesordnungspunkte",
"newItem": "Neuer TOP",
"editItem": "TOP bearbeiten",
"deleteItem": "TOP löschen",
"number": "TOP-Nr.",
"subject": "Betreff",
"description": "Beschreibung",
"presenter": "Berichterstatter(in)",
"duration": "Dauer (Min.)",
"type": "Art",
"attachments": "Anlagen",
"notes": "Notizen",
"types": {
"information": "Information",
"discussion": "Diskussion",
"decision": "Beschluss",
"election": "Wahl",
"report": "Bericht",
"miscellaneous": "Verschiedenes"
}
},
"decisions": {
"title": "Beschlüsse",
"newDecision": "Neuer Beschluss",
"editDecision": "Beschluss bearbeiten",
"deleteDecision": "Beschluss löschen",
"number": "Beschluss-Nr.",
"subject": "Betreff",
"text": "Beschlusstext",
"result": "Ergebnis",
"votesFor": "Ja-Stimmen",
"votesAgainst": "Nein-Stimmen",
"abstentions": "Enthaltungen",
"responsible": "Verantwortlich",
"deadline": "Frist",
"status": "Status",
"remarks": "Bemerkungen",
"results": {
"accepted": "Angenommen",
"rejected": "Abgelehnt",
"deferred": "Vertagt",
"withdrawn": "Zurückgezogen"
},
"statuses": {
"open": "Offen",
"inProgress": "In Bearbeitung",
"completed": "Erledigt",
"overdue": "Überfällig"
}
},
"attendance": {
"title": "Anwesenheit",
"present": "Anwesend",
"absent": "Abwesend",
"excused": "Entschuldigt",
"arrivedLate": "Verspätet erschienen",
"leftEarly": "Vorzeitig gegangen"
},
"protocol": {
"generate": "Protokoll generieren",
"preview": "Vorschau",
"export": "Exportieren",
"sign": "Unterschreiben",
"finalize": "Fertigstellen",
"formatPdf": "PDF",
"formatDocx": "Word"
}
},
"verband": {
"title": "Verbandsverwaltung",
"description": "Mitgliedsvereine, Kontaktpersonen, Beiträge und Statistiken verwalten",
"dashboard": {
"title": "Übersicht",
"totalClubs": "Mitgliedsvereine gesamt",
"totalMembers": "Mitglieder gesamt",
"pendingDues": "Ausstehende Beiträge",
"upcomingEvents": "Kommende Veranstaltungen"
},
"clubs": {
"title": "Mitgliedsvereine",
"newClub": "Neuer Verein",
"editClub": "Verein bearbeiten",
"deleteClub": "Verein löschen",
"name": "Vereinsname",
"shortName": "Kurzname",
"number": "Vereinsnummer",
"address": "Adresse",
"postalCode": "PLZ",
"city": "Ort",
"phone": "Telefon",
"email": "E-Mail",
"website": "Webseite",
"founded": "Gründungsjahr",
"memberCount": "Mitgliederzahl",
"joinedDate": "Beitrittsdatum",
"status": "Status",
"contacts": "Kontaktpersonen",
"dues": "Beiträge",
"notes": "Bemerkungen",
"statuses": {
"active": "Aktiv",
"inactive": "Inaktiv",
"suspended": "Ruhend",
"withdrawn": "Ausgetreten"
}
},
"contacts": {
"title": "Kontaktpersonen",
"newContact": "Neue Kontaktperson",
"editContact": "Kontaktperson bearbeiten",
"deleteContact": "Kontaktperson löschen",
"firstName": "Vorname",
"lastName": "Nachname",
"role": "Funktion",
"phone": "Telefon",
"email": "E-Mail",
"isPrimary": "Hauptkontakt",
"roles": {
"chairman": "Vorsitzende(r)",
"viceChairman": "Stellv. Vorsitzende(r)",
"treasurer": "Kassenwart(in)",
"secretary": "Schriftführer(in)",
"youthLeader": "Jugendleiter(in)",
"boardMember": "Vorstandsmitglied",
"delegate": "Delegierte(r)",
"other": "Sonstige"
}
},
"dues": {
"title": "Beiträge",
"newDue": "Neuer Beitrag",
"editDue": "Beitrag bearbeiten",
"year": "Jahr",
"amount": "Betrag (EUR)",
"dueDate": "Fälligkeitsdatum",
"paidDate": "Bezahlt am",
"status": "Status",
"invoiceNumber": "Rechnungsnummer",
"remarks": "Bemerkungen",
"statuses": {
"open": "Offen",
"paid": "Bezahlt",
"overdue": "Überfällig",
"waived": "Erlassen",
"partial": "Teilbezahlt"
},
"bulkCreate": "Beiträge generieren",
"bulkCreateDescription": "Beiträge für alle aktiven Vereine erstellen",
"totalOpen": "Gesamt offen",
"totalPaid": "Gesamt bezahlt"
},
"statistics": {
"title": "Statistiken",
"membersByClub": "Mitglieder nach Verein",
"membersTrend": "Mitgliederentwicklung",
"duesOverview": "Beitragsübersicht",
"clubsByRegion": "Vereine nach Region",
"filterYear": "Jahr filtern"
},
"export": {
"exportClubs": "Vereine exportieren",
"exportContacts": "Kontaktpersonen exportieren",
"exportDues": "Beiträge exportieren",
"formatCsv": "CSV",
"formatExcel": "Excel"
}
}
}