feat: add data-test attributes for improved testing in various components
This commit is contained in:
171
apps/web/i18n/messages/de/fischerei.json
Normal file
171
apps/web/i18n/messages/de/fischerei.json
Normal file
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"nav": {
|
||||
"overview": "Übersicht",
|
||||
"waters": "Gewässer",
|
||||
"species": "Fischarten",
|
||||
"stocking": "Besatz",
|
||||
"leases": "Pachten",
|
||||
"catchBooks": "Fangbücher",
|
||||
"permits": "Erlaubnisscheine",
|
||||
"competitions": "Wettbewerbe",
|
||||
"statistics": "Statistiken"
|
||||
},
|
||||
"pages": {
|
||||
"overviewTitle": "Fischerei",
|
||||
"watersTitle": "Fischerei - Gewässer",
|
||||
"speciesTitle": "Fischerei - Fischarten",
|
||||
"stockingTitle": "Fischerei - Besatz",
|
||||
"leasesTitle": "Fischerei - Pachten",
|
||||
"catchBooksTitle": "Fischerei - Fangbücher",
|
||||
"permitsTitle": "Fischerei - Erlaubnisscheine",
|
||||
"competitionsTitle": "Fischerei - Wettbewerbe",
|
||||
"statisticsTitle": "Fischerei - Statistiken"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Fischerei – Übersicht",
|
||||
"subtitle": "Gewässer, Fischarten, Besatz, Fangbücher und mehr verwalten",
|
||||
"waters": "Gewässer",
|
||||
"species": "Fischarten",
|
||||
"activeLeases": "Aktive Pachten",
|
||||
"openCatchBooks": "Offene Fangbücher",
|
||||
"upcomingCompetitions": "Kommende Wettbewerbe",
|
||||
"stockingCostsYtd": "Besatzkosten (lfd. Jahr)",
|
||||
"recentStocking": "Letzte Besatzaktionen",
|
||||
"noRecentStocking": "Noch keine Besatzaktionen vorhanden.",
|
||||
"pendingCatchBooks": "Offene Fangbücher",
|
||||
"noPendingCatchBooks": "Keine Fangbücher zur Prüfung ausstehend."
|
||||
},
|
||||
"waters": {
|
||||
"searchPlaceholder": "Gewässer suchen...",
|
||||
"newWater": "Neues Gewässer",
|
||||
"title": "Gewässer ({count})",
|
||||
"noWaters": "Keine Gewässer vorhanden",
|
||||
"createFirst": "Erstellen Sie Ihr erstes Gewässer, um loszulegen.",
|
||||
"shortName": "Kurzname",
|
||||
"surfaceArea": "Fläche (ha)"
|
||||
},
|
||||
"waterForm": {
|
||||
"basicData": "Grunddaten",
|
||||
"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)",
|
||||
"geography": "Geografie",
|
||||
"drainage": "Abfluss",
|
||||
"location": "Lage/Standort",
|
||||
"district": "Landkreis",
|
||||
"latitude": "Breitengrad",
|
||||
"longitude": "Längengrad",
|
||||
"administration": "Verwaltung",
|
||||
"lfvNumber": "LFV-Nummer",
|
||||
"costSharePercent": "Kostenanteil DS (%)",
|
||||
"waterUpdated": "Gewässer aktualisiert",
|
||||
"waterCreated": "Gewässer erstellt",
|
||||
"errorSaving": "Fehler beim Speichern",
|
||||
"waterTypes": {
|
||||
"still": "Stillgewässer",
|
||||
"flowing": "Fließgewässer",
|
||||
"pond": "Teich/Weiher",
|
||||
"lake": "See",
|
||||
"river": "Fluss",
|
||||
"stream": "Bach",
|
||||
"canal": "Kanal",
|
||||
"reservoir": "Stausee"
|
||||
}
|
||||
},
|
||||
"species": {
|
||||
"searchPlaceholder": "Fischart suchen...",
|
||||
"newSpecies": "Neue Fischart",
|
||||
"title": "Fischarten ({count})",
|
||||
"noSpecies": "Keine Fischarten vorhanden",
|
||||
"createFirst": "Erstellen Sie Ihre erste Fischart.",
|
||||
"latinName": "Lateinischer Name",
|
||||
"localName": "Lokaler Name"
|
||||
},
|
||||
"speciesForm": {
|
||||
"name": "Name *",
|
||||
"latinName": "Lateinischer Name",
|
||||
"localName": "Lokaler Name",
|
||||
"protectionRules": "Schutzbestimmungen",
|
||||
"minimumSize": "Schonmaß (cm)",
|
||||
"closedSeasonStart": "Schonzeit Beginn (MM.TT)",
|
||||
"closedSeasonEnd": "Schonzeit Ende (MM.TT)",
|
||||
"datePlaceholder": "z.B. {example}",
|
||||
"catchLimits": "Fangbegrenzungen",
|
||||
"maxCatchPerDay": "Max. Fang/Tag",
|
||||
"maxCatchPerYear": "Max. Fang/Jahr",
|
||||
"speciesUpdated": "Fischart aktualisiert",
|
||||
"speciesCreated": "Fischart erstellt",
|
||||
"errorSaving": "Fehler beim Speichern"
|
||||
},
|
||||
"stocking": {
|
||||
"newStocking": "Besatz eintragen",
|
||||
"title": "Besatzeinträge ({count})",
|
||||
"noStocking": "Keine Besatzeinträge vorhanden",
|
||||
"createFirst": "Tragen Sie den ersten Besatz ein.",
|
||||
"date": "Datum",
|
||||
"water": "Gewässer",
|
||||
"fishSpecies": "Fischart",
|
||||
"quantity": "Anzahl",
|
||||
"weight": "Gewicht (kg)",
|
||||
"ageClass": "Altersklasse",
|
||||
"cost": "Kosten (€)"
|
||||
},
|
||||
"stockingForm": {
|
||||
"title": "Besatzdaten",
|
||||
"water": "Gewässer *",
|
||||
"selectWater": "— Gewässer wählen —",
|
||||
"species": "Fischart *",
|
||||
"selectSpecies": "— Fischart wählen —",
|
||||
"date": "Besatzdatum *",
|
||||
"quantity": "Anzahl (Stück) *",
|
||||
"weight": "Gewicht (kg)",
|
||||
"ageClass": "Altersklasse",
|
||||
"cost": "Kosten (EUR)",
|
||||
"remarks": "Bemerkungen",
|
||||
"created": "Besatz eingetragen",
|
||||
"errorSaving": "Fehler beim Speichern"
|
||||
},
|
||||
"catchBooks": {
|
||||
"title": "Fangbücher ({count})",
|
||||
"noCatchBooks": "Keine Fangbücher vorhanden",
|
||||
"createFirst": "Erstellen Sie Ihr erstes Fangbuch.",
|
||||
"year": "Jahr",
|
||||
"allYears": "Alle Jahre",
|
||||
"catchBookStatus": {
|
||||
"open": "Offen",
|
||||
"submitted": "Eingereicht",
|
||||
"approved": "Genehmigt",
|
||||
"rejected": "Abgelehnt",
|
||||
"archived": "Archiviert"
|
||||
}
|
||||
},
|
||||
"competitions": {
|
||||
"title": "Wettbewerbe ({count})",
|
||||
"newCompetition": "Neuer Wettbewerb",
|
||||
"noCompetitions": "Keine Wettbewerbe vorhanden",
|
||||
"createFirst": "Erstellen Sie Ihren ersten Wettbewerb."
|
||||
},
|
||||
"leases": {
|
||||
"title": "Pachten",
|
||||
"startDate": "Beginn",
|
||||
"endDate": "Ende",
|
||||
"indefinite": "unbefristet",
|
||||
"cost": "Pachtkosten"
|
||||
},
|
||||
"permits": {
|
||||
"title": "Erlaubnisscheine"
|
||||
},
|
||||
"common": {
|
||||
"search": "Suchen",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"update": "Aktualisieren",
|
||||
"create": "Erstellen"
|
||||
}
|
||||
}
|
||||
168
apps/web/i18n/messages/de/members.json
Normal file
168
apps/web/i18n/messages/de/members.json
Normal file
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"nav": {
|
||||
"members": "Mitglieder",
|
||||
"newMember": "Neues Mitglied",
|
||||
"applications": "Aufnahmeanträge",
|
||||
"dues": "Beitragskategorien",
|
||||
"departments": "Abteilungen",
|
||||
"cards": "Mitgliedsausweise",
|
||||
"import": "Import",
|
||||
"statistics": "Statistiken"
|
||||
},
|
||||
"list": {
|
||||
"searchPlaceholder": "Name, E-Mail oder Mitgliedsnr. suchen...",
|
||||
"title": "Mitglieder ({count})",
|
||||
"noMembers": "Keine Mitglieder gefunden",
|
||||
"createFirst": "Erstellen Sie Ihr erstes Mitglied, um loszulegen.",
|
||||
"newMember": "Neues Mitglied"
|
||||
},
|
||||
"detail": {
|
||||
"personalData": "Persönliche Daten",
|
||||
"firstName": "Vorname",
|
||||
"lastName": "Nachname",
|
||||
"dateOfBirth": "Geburtsdatum",
|
||||
"gender": "Geschlecht",
|
||||
"salutation": "Anrede",
|
||||
"age": "{age} Jahre",
|
||||
"contactData": "Kontaktdaten",
|
||||
"email": "E-Mail",
|
||||
"phone": "Telefon",
|
||||
"mobile": "Mobil",
|
||||
"address": "Adresse",
|
||||
"street": "Straße",
|
||||
"houseNumber": "Hausnummer",
|
||||
"postalCode": "PLZ",
|
||||
"city": "Ort",
|
||||
"country": "Land",
|
||||
"membership": "Mitgliedschaft",
|
||||
"memberNumber": "Mitgliedsnr.",
|
||||
"status": "Status",
|
||||
"entryDate": "Eintrittsdatum",
|
||||
"exitDate": "Austrittsdatum",
|
||||
"exitReason": "Austrittsgrund",
|
||||
"membershipYears": "{years} Jahre",
|
||||
"bankData": "Bankdaten",
|
||||
"iban": "IBAN",
|
||||
"bic": "BIC",
|
||||
"accountHolder": "Kontoinhaber",
|
||||
"editMember": "Bearbeiten",
|
||||
"terminateMember": "Kündigen",
|
||||
"terminateConfirm": "Möchten Sie {name} wirklich kündigen?",
|
||||
"terminated": "Mitglied wurde gekündigt",
|
||||
"errorTerminating": "Fehler beim Kündigen",
|
||||
"reactivated": "Mitglied wurde reaktiviert",
|
||||
"errorReactivating": "Fehler beim Reaktivieren",
|
||||
"notFound": "Mitglied nicht gefunden"
|
||||
},
|
||||
"form": {
|
||||
"createTitle": "Neues Mitglied anlegen",
|
||||
"editTitle": "Mitglied bearbeiten",
|
||||
"created": "Mitglied erfolgreich erstellt",
|
||||
"updated": "Mitglied aktualisiert",
|
||||
"errorCreating": "Fehler beim Erstellen",
|
||||
"errorUpdating": "Fehler beim Aktualisieren",
|
||||
"gdprConsent": "DSGVO-Einwilligung",
|
||||
"notes": "Notizen"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"pending": "Ausstehend",
|
||||
"resigned": "Ausgetreten",
|
||||
"excluded": "Ausgeschlossen",
|
||||
"deceased": "Verstorben"
|
||||
},
|
||||
"applications": {
|
||||
"title": "Aufnahmeanträge ({count})",
|
||||
"noApplications": "Keine offenen Aufnahmeanträge",
|
||||
"approve": "Genehmigen",
|
||||
"reject": "Ablehnen",
|
||||
"approved": "Antrag genehmigt – Mitglied erstellt",
|
||||
"rejected": "Antrag abgelehnt",
|
||||
"errorApproving": "Fehler bei der Genehmigung",
|
||||
"errorRejecting": "Fehler bei der Ablehnung",
|
||||
"approveConfirm": "Antrag von {name} genehmigen?",
|
||||
"rejectConfirm": "Antrag von {name} ablehnen? Bitte Grund angeben:",
|
||||
"submitted": "Eingereicht"
|
||||
},
|
||||
"dues": {
|
||||
"title": "Beitragskategorien",
|
||||
"name": "Name",
|
||||
"description": "Beschreibung",
|
||||
"amount": "Betrag",
|
||||
"interval": "Intervall",
|
||||
"default": "Standard",
|
||||
"monthly": "Monatlich",
|
||||
"quarterly": "Vierteljährlich",
|
||||
"semiannual": "Halbjährlich",
|
||||
"annual": "Jährlich",
|
||||
"create": "Erstellen",
|
||||
"created": "Beitragskategorie erstellt",
|
||||
"deleted": "Beitragskategorie gelöscht",
|
||||
"errorCreating": "Fehler beim Erstellen",
|
||||
"errorDeleting": "Fehler beim Löschen",
|
||||
"deleteConfirm": "Beitragskategorie \"{name}\" wirklich löschen?",
|
||||
"noCategories": "Keine Beitragskategorien vorhanden."
|
||||
},
|
||||
"mandates": {
|
||||
"title": "SEPA-Mandate",
|
||||
"iban": "IBAN *",
|
||||
"bic": "BIC",
|
||||
"accountHolder": "Kontoinhaber *",
|
||||
"mandateDate": "Mandatsdatum",
|
||||
"primary": "Primär",
|
||||
"createMandate": "Mandat anlegen",
|
||||
"revoke": "Widerrufen",
|
||||
"revokeConfirm": "Mandat \"{reference}\" wirklich widerrufen?",
|
||||
"created": "SEPA-Mandat erstellt",
|
||||
"revoked": "Mandat widerrufen",
|
||||
"errorCreating": "Fehler beim Erstellen",
|
||||
"errorRevoking": "Fehler beim Widerrufen"
|
||||
},
|
||||
"departments": {
|
||||
"title": "Abteilungen",
|
||||
"noDepartments": "Keine Abteilungen vorhanden.",
|
||||
"createFirst": "Erstellen Sie Ihre erste Abteilung.",
|
||||
"newDepartment": "Neue Abteilung"
|
||||
},
|
||||
"cards": {
|
||||
"title": "Mitgliedsausweise",
|
||||
"memberCard": "MITGLIEDSAUSWEIS",
|
||||
"memberSince": "Mitglied seit",
|
||||
"validUntil": "Gültig bis",
|
||||
"generate": "Ausweise generieren",
|
||||
"download": "Herunterladen"
|
||||
},
|
||||
"import": {
|
||||
"title": "Mitglieder importieren",
|
||||
"selectFile": "CSV-Datei auswählen",
|
||||
"mapColumns": "Spalten zuordnen",
|
||||
"preview": "Vorschau",
|
||||
"importing": "Wird importiert...",
|
||||
"imported": "{count} Mitglieder erfolgreich importiert",
|
||||
"errorImporting": "Fehler beim Import"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Mitglieder-Statistiken",
|
||||
"totalMembers": "Gesamtmitglieder",
|
||||
"activeMembers": "Aktive Mitglieder",
|
||||
"newThisYear": "Neue dieses Jahr",
|
||||
"resignedThisYear": "Ausgetreten dieses Jahr"
|
||||
},
|
||||
"export": {
|
||||
"csv": "CSV exportieren",
|
||||
"excel": "Excel exportieren",
|
||||
"memberNumber": "Mitgliedsnr.",
|
||||
"firstName": "Vorname",
|
||||
"lastName": "Nachname",
|
||||
"email": "E-Mail",
|
||||
"phone": "Telefon",
|
||||
"postalCode": "PLZ",
|
||||
"city": "Ort",
|
||||
"status": "Status",
|
||||
"entryDate": "Eintrittsdatum",
|
||||
"iban": "IBAN",
|
||||
"bic": "BIC",
|
||||
"accountHolder": "Kontoinhaber"
|
||||
}
|
||||
}
|
||||
171
apps/web/i18n/messages/en/fischerei.json
Normal file
171
apps/web/i18n/messages/en/fischerei.json
Normal file
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"nav": {
|
||||
"overview": "Overview",
|
||||
"waters": "Waters",
|
||||
"species": "Fish Species",
|
||||
"stocking": "Stocking",
|
||||
"leases": "Leases",
|
||||
"catchBooks": "Catch Books",
|
||||
"permits": "Permits",
|
||||
"competitions": "Competitions",
|
||||
"statistics": "Statistics"
|
||||
},
|
||||
"pages": {
|
||||
"overviewTitle": "Fisheries",
|
||||
"watersTitle": "Fisheries - Waters",
|
||||
"speciesTitle": "Fisheries - Fish Species",
|
||||
"stockingTitle": "Fisheries - Stocking",
|
||||
"leasesTitle": "Fisheries - Leases",
|
||||
"catchBooksTitle": "Fisheries - Catch Books",
|
||||
"permitsTitle": "Fisheries - Permits",
|
||||
"competitionsTitle": "Fisheries - Competitions",
|
||||
"statisticsTitle": "Fisheries - Statistics"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Fisheries – Overview",
|
||||
"subtitle": "Manage waters, fish species, stocking, catch books, and more",
|
||||
"waters": "Waters",
|
||||
"species": "Fish Species",
|
||||
"activeLeases": "Active Leases",
|
||||
"openCatchBooks": "Open Catch Books",
|
||||
"upcomingCompetitions": "Upcoming Competitions",
|
||||
"stockingCostsYtd": "Stocking Costs (YTD)",
|
||||
"recentStocking": "Recent Stocking Activities",
|
||||
"noRecentStocking": "No stocking activities yet.",
|
||||
"pendingCatchBooks": "Pending Catch Books",
|
||||
"noPendingCatchBooks": "No catch books pending review."
|
||||
},
|
||||
"waters": {
|
||||
"searchPlaceholder": "Search waters...",
|
||||
"newWater": "New Water",
|
||||
"title": "Waters ({count})",
|
||||
"noWaters": "No waters found",
|
||||
"createFirst": "Create your first water body to get started.",
|
||||
"shortName": "Short Name",
|
||||
"surfaceArea": "Surface Area (ha)"
|
||||
},
|
||||
"waterForm": {
|
||||
"basicData": "Basic Data",
|
||||
"name": "Name *",
|
||||
"shortName": "Short Name",
|
||||
"waterType": "Water Type",
|
||||
"description": "Description",
|
||||
"surfaceArea": "Surface Area (ha)",
|
||||
"length": "Length (m)",
|
||||
"width": "Width (m)",
|
||||
"avgDepth": "Average Depth (m)",
|
||||
"maxDepth": "Maximum Depth (m)",
|
||||
"geography": "Geography",
|
||||
"drainage": "Drainage",
|
||||
"location": "Location",
|
||||
"district": "District",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"administration": "Administration",
|
||||
"lfvNumber": "LFV Number",
|
||||
"costSharePercent": "Cost Share (%)",
|
||||
"waterUpdated": "Water updated",
|
||||
"waterCreated": "Water created",
|
||||
"errorSaving": "Error saving",
|
||||
"waterTypes": {
|
||||
"still": "Still Water",
|
||||
"flowing": "Flowing Water",
|
||||
"pond": "Pond",
|
||||
"lake": "Lake",
|
||||
"river": "River",
|
||||
"stream": "Stream",
|
||||
"canal": "Canal",
|
||||
"reservoir": "Reservoir"
|
||||
}
|
||||
},
|
||||
"species": {
|
||||
"searchPlaceholder": "Search species...",
|
||||
"newSpecies": "New Species",
|
||||
"title": "Fish Species ({count})",
|
||||
"noSpecies": "No fish species found",
|
||||
"createFirst": "Create your first fish species.",
|
||||
"latinName": "Latin Name",
|
||||
"localName": "Local Name"
|
||||
},
|
||||
"speciesForm": {
|
||||
"name": "Name *",
|
||||
"latinName": "Latin Name",
|
||||
"localName": "Local Name",
|
||||
"protectionRules": "Protection Rules",
|
||||
"minimumSize": "Minimum Size (cm)",
|
||||
"closedSeasonStart": "Closed Season Start (MM.DD)",
|
||||
"closedSeasonEnd": "Closed Season End (MM.DD)",
|
||||
"datePlaceholder": "e.g. {example}",
|
||||
"catchLimits": "Catch Limits",
|
||||
"maxCatchPerDay": "Max Catch/Day",
|
||||
"maxCatchPerYear": "Max Catch/Year",
|
||||
"speciesUpdated": "Species updated",
|
||||
"speciesCreated": "Species created",
|
||||
"errorSaving": "Error saving"
|
||||
},
|
||||
"stocking": {
|
||||
"newStocking": "Record Stocking",
|
||||
"title": "Stocking Records ({count})",
|
||||
"noStocking": "No stocking records found",
|
||||
"createFirst": "Record your first stocking activity.",
|
||||
"date": "Date",
|
||||
"water": "Water",
|
||||
"fishSpecies": "Fish Species",
|
||||
"quantity": "Quantity",
|
||||
"weight": "Weight (kg)",
|
||||
"ageClass": "Age Class",
|
||||
"cost": "Cost (€)"
|
||||
},
|
||||
"stockingForm": {
|
||||
"title": "Stocking Data",
|
||||
"water": "Water *",
|
||||
"selectWater": "— Select water —",
|
||||
"species": "Species *",
|
||||
"selectSpecies": "— Select species —",
|
||||
"date": "Stocking Date *",
|
||||
"quantity": "Quantity (pcs) *",
|
||||
"weight": "Weight (kg)",
|
||||
"ageClass": "Age Class",
|
||||
"cost": "Cost (EUR)",
|
||||
"remarks": "Remarks",
|
||||
"created": "Stocking recorded",
|
||||
"errorSaving": "Error saving"
|
||||
},
|
||||
"catchBooks": {
|
||||
"title": "Catch Books ({count})",
|
||||
"noCatchBooks": "No catch books found",
|
||||
"createFirst": "Create your first catch book.",
|
||||
"year": "Year",
|
||||
"allYears": "All Years",
|
||||
"catchBookStatus": {
|
||||
"open": "Open",
|
||||
"submitted": "Submitted",
|
||||
"approved": "Approved",
|
||||
"rejected": "Rejected",
|
||||
"archived": "Archived"
|
||||
}
|
||||
},
|
||||
"competitions": {
|
||||
"title": "Competitions ({count})",
|
||||
"newCompetition": "New Competition",
|
||||
"noCompetitions": "No competitions found",
|
||||
"createFirst": "Create your first competition."
|
||||
},
|
||||
"leases": {
|
||||
"title": "Leases",
|
||||
"startDate": "Start",
|
||||
"endDate": "End",
|
||||
"indefinite": "indefinite",
|
||||
"cost": "Lease Cost"
|
||||
},
|
||||
"permits": {
|
||||
"title": "Permits"
|
||||
},
|
||||
"common": {
|
||||
"search": "Search",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"update": "Update",
|
||||
"create": "Create"
|
||||
}
|
||||
}
|
||||
168
apps/web/i18n/messages/en/members.json
Normal file
168
apps/web/i18n/messages/en/members.json
Normal file
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"nav": {
|
||||
"members": "Members",
|
||||
"newMember": "New Member",
|
||||
"applications": "Applications",
|
||||
"dues": "Dues Categories",
|
||||
"departments": "Departments",
|
||||
"cards": "Member Cards",
|
||||
"import": "Import",
|
||||
"statistics": "Statistics"
|
||||
},
|
||||
"list": {
|
||||
"searchPlaceholder": "Search name, email, or member no...",
|
||||
"title": "Members ({count})",
|
||||
"noMembers": "No members found",
|
||||
"createFirst": "Create your first member to get started.",
|
||||
"newMember": "New Member"
|
||||
},
|
||||
"detail": {
|
||||
"personalData": "Personal Data",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"dateOfBirth": "Date of Birth",
|
||||
"gender": "Gender",
|
||||
"salutation": "Salutation",
|
||||
"age": "{age} years",
|
||||
"contactData": "Contact Information",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"mobile": "Mobile",
|
||||
"address": "Address",
|
||||
"street": "Street",
|
||||
"houseNumber": "House No.",
|
||||
"postalCode": "ZIP",
|
||||
"city": "City",
|
||||
"country": "Country",
|
||||
"membership": "Membership",
|
||||
"memberNumber": "Member No.",
|
||||
"status": "Status",
|
||||
"entryDate": "Entry Date",
|
||||
"exitDate": "Exit Date",
|
||||
"exitReason": "Exit Reason",
|
||||
"membershipYears": "{years} years",
|
||||
"bankData": "Bank Details",
|
||||
"iban": "IBAN",
|
||||
"bic": "BIC",
|
||||
"accountHolder": "Account Holder",
|
||||
"editMember": "Edit",
|
||||
"terminateMember": "Terminate",
|
||||
"terminateConfirm": "Are you sure you want to terminate {name}?",
|
||||
"terminated": "Member terminated",
|
||||
"errorTerminating": "Error terminating member",
|
||||
"reactivated": "Member reactivated",
|
||||
"errorReactivating": "Error reactivating member",
|
||||
"notFound": "Member not found"
|
||||
},
|
||||
"form": {
|
||||
"createTitle": "Create New Member",
|
||||
"editTitle": "Edit Member",
|
||||
"created": "Member created successfully",
|
||||
"updated": "Member updated",
|
||||
"errorCreating": "Error creating member",
|
||||
"errorUpdating": "Error updating member",
|
||||
"gdprConsent": "GDPR Consent",
|
||||
"notes": "Notes"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"pending": "Pending",
|
||||
"resigned": "Resigned",
|
||||
"excluded": "Excluded",
|
||||
"deceased": "Deceased"
|
||||
},
|
||||
"applications": {
|
||||
"title": "Membership Applications ({count})",
|
||||
"noApplications": "No pending applications",
|
||||
"approve": "Approve",
|
||||
"reject": "Reject",
|
||||
"approved": "Application approved — member created",
|
||||
"rejected": "Application rejected",
|
||||
"errorApproving": "Error approving application",
|
||||
"errorRejecting": "Error rejecting application",
|
||||
"approveConfirm": "Approve application from {name}?",
|
||||
"rejectConfirm": "Reject application from {name}? Please provide a reason:",
|
||||
"submitted": "Submitted"
|
||||
},
|
||||
"dues": {
|
||||
"title": "Dues Categories",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"amount": "Amount",
|
||||
"interval": "Interval",
|
||||
"default": "Default",
|
||||
"monthly": "Monthly",
|
||||
"quarterly": "Quarterly",
|
||||
"semiannual": "Semi-annual",
|
||||
"annual": "Annual",
|
||||
"create": "Create",
|
||||
"created": "Dues category created",
|
||||
"deleted": "Dues category deleted",
|
||||
"errorCreating": "Error creating category",
|
||||
"errorDeleting": "Error deleting category",
|
||||
"deleteConfirm": "Delete dues category \"{name}\"?",
|
||||
"noCategories": "No dues categories found."
|
||||
},
|
||||
"mandates": {
|
||||
"title": "SEPA Mandates",
|
||||
"iban": "IBAN *",
|
||||
"bic": "BIC",
|
||||
"accountHolder": "Account Holder *",
|
||||
"mandateDate": "Mandate Date",
|
||||
"primary": "Primary",
|
||||
"createMandate": "Create Mandate",
|
||||
"revoke": "Revoke",
|
||||
"revokeConfirm": "Revoke mandate \"{reference}\"?",
|
||||
"created": "SEPA mandate created",
|
||||
"revoked": "Mandate revoked",
|
||||
"errorCreating": "Error creating mandate",
|
||||
"errorRevoking": "Error revoking mandate"
|
||||
},
|
||||
"departments": {
|
||||
"title": "Departments",
|
||||
"noDepartments": "No departments found.",
|
||||
"createFirst": "Create your first department.",
|
||||
"newDepartment": "New Department"
|
||||
},
|
||||
"cards": {
|
||||
"title": "Member Cards",
|
||||
"memberCard": "MEMBER CARD",
|
||||
"memberSince": "Member since",
|
||||
"validUntil": "Valid until",
|
||||
"generate": "Generate Cards",
|
||||
"download": "Download"
|
||||
},
|
||||
"import": {
|
||||
"title": "Import Members",
|
||||
"selectFile": "Select CSV file",
|
||||
"mapColumns": "Map columns",
|
||||
"preview": "Preview",
|
||||
"importing": "Importing...",
|
||||
"imported": "{count} members imported successfully",
|
||||
"errorImporting": "Error importing"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Member Statistics",
|
||||
"totalMembers": "Total Members",
|
||||
"activeMembers": "Active Members",
|
||||
"newThisYear": "New This Year",
|
||||
"resignedThisYear": "Resigned This Year"
|
||||
},
|
||||
"export": {
|
||||
"csv": "Export CSV",
|
||||
"excel": "Export Excel",
|
||||
"memberNumber": "Member No.",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"postalCode": "ZIP",
|
||||
"city": "City",
|
||||
"status": "Status",
|
||||
"entryDate": "Entry Date",
|
||||
"iban": "IBAN",
|
||||
"bic": "BIC",
|
||||
"accountHolder": "Account Holder"
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ const namespaces = [
|
||||
'marketing',
|
||||
'cms',
|
||||
'verband',
|
||||
'members',
|
||||
'fischerei',
|
||||
] as const;
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
@@ -95,6 +95,7 @@ export function CatchBooksDataTable({
|
||||
<select
|
||||
value={currentYear}
|
||||
onChange={handleYearChange}
|
||||
data-test="catchbooks-year-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
<option value="">Alle Jahre</option>
|
||||
@@ -108,6 +109,7 @@ export function CatchBooksDataTable({
|
||||
<select
|
||||
value={currentStatus}
|
||||
onChange={handleStatusChange}
|
||||
data-test="catchbooks-status-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
{STATUS_OPTIONS.map((opt) => (
|
||||
|
||||
@@ -59,7 +59,7 @@ export function CompetitionsDataTable({
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Wettbewerbe ({total})</h2>
|
||||
<Link href={`/home/${account}/fischerei/competitions/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="competitions-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Wettbewerb
|
||||
</Button>
|
||||
|
||||
@@ -247,10 +247,19 @@ export function CreateSpeciesForm({
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
data-test="species-cancel-btn"
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="species-submit-btn"
|
||||
>
|
||||
{isPending
|
||||
? 'Wird gespeichert...'
|
||||
: isEdit
|
||||
|
||||
@@ -86,6 +86,7 @@ export function CreateStockingForm({
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
data-test="stocking-water-select"
|
||||
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="">— Gewässer wählen —</option>
|
||||
@@ -109,6 +110,7 @@ export function CreateStockingForm({
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
data-test="stocking-species-select"
|
||||
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="">— Fischart wählen —</option>
|
||||
@@ -253,7 +255,11 @@ export function CreateStockingForm({
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="stocking-submit-btn"
|
||||
>
|
||||
{isPending ? 'Wird gespeichert...' : 'Besatz eintragen'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -435,10 +435,19 @@ export function CreateWaterForm({
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
data-test="water-cancel-btn"
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="water-submit-btn"
|
||||
>
|
||||
{isPending
|
||||
? 'Wird gespeichert...'
|
||||
: isEdit
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
interface FischereiTabNavigationProps {
|
||||
@@ -11,15 +11,27 @@ interface FischereiTabNavigationProps {
|
||||
}
|
||||
|
||||
const TABS = [
|
||||
{ id: 'overview', label: 'Übersicht', path: '' },
|
||||
{ id: 'waters', label: 'Gewässer', path: '/waters' },
|
||||
{ id: 'species', label: 'Fischarten', path: '/species' },
|
||||
{ id: 'stocking', label: 'Besatz', path: '/stocking' },
|
||||
{ id: 'leases', label: 'Pachten', path: '/leases' },
|
||||
{ id: 'catch-books', label: 'Fangbücher', path: '/catch-books' },
|
||||
{ id: 'permits', label: 'Erlaubnisscheine', path: '/permits' },
|
||||
{ id: 'competitions', label: 'Wettbewerbe', path: '/competitions' },
|
||||
{ id: 'statistics', label: 'Statistiken', path: '/statistics' },
|
||||
{ id: 'overview', i18nKey: 'fischerei:nav.overview', path: '' },
|
||||
{ id: 'waters', i18nKey: 'fischerei:nav.waters', path: '/waters' },
|
||||
{ id: 'species', i18nKey: 'fischerei:nav.species', path: '/species' },
|
||||
{ id: 'stocking', i18nKey: 'fischerei:nav.stocking', path: '/stocking' },
|
||||
{ id: 'leases', i18nKey: 'fischerei:nav.leases', path: '/leases' },
|
||||
{
|
||||
id: 'catch-books',
|
||||
i18nKey: 'fischerei:nav.catchBooks',
|
||||
path: '/catch-books',
|
||||
},
|
||||
{ id: 'permits', i18nKey: 'fischerei:nav.permits', path: '/permits' },
|
||||
{
|
||||
id: 'competitions',
|
||||
i18nKey: 'fischerei:nav.competitions',
|
||||
path: '/competitions',
|
||||
},
|
||||
{
|
||||
id: 'statistics',
|
||||
i18nKey: 'fischerei:nav.statistics',
|
||||
path: '/statistics',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function FischereiTabNavigation({
|
||||
@@ -32,7 +44,7 @@ export function FischereiTabNavigation({
|
||||
<div className="mb-6 border-b">
|
||||
<nav
|
||||
className="-mb-px flex space-x-1 overflow-x-auto"
|
||||
aria-label="Fischerei Navigation"
|
||||
aria-label="Fisheries Navigation"
|
||||
>
|
||||
{TABS.map((tab) => {
|
||||
const isActive = tab.id === activeTab;
|
||||
@@ -41,6 +53,7 @@ export function FischereiTabNavigation({
|
||||
<Link
|
||||
key={tab.id}
|
||||
href={`${basePath}${tab.path}`}
|
||||
data-test={`fischerei-tab-${tab.id}`}
|
||||
className={cn(
|
||||
'border-b-2 px-4 py-2.5 text-sm font-medium whitespace-nowrap transition-colors',
|
||||
isActive
|
||||
@@ -48,7 +61,7 @@ export function FischereiTabNavigation({
|
||||
: 'text-muted-foreground hover:border-muted-foreground/30 hover:text-foreground border-transparent',
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
<Trans i18nKey={tab.i18nKey} />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -76,15 +76,21 @@ export function SpeciesDataTable({
|
||||
<Input
|
||||
placeholder="Fischart suchen..."
|
||||
className="w-64"
|
||||
data-test="species-search-input"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="species-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Link href={`/home/${account}/fischerei/species/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="species-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Fischart
|
||||
</Button>
|
||||
|
||||
@@ -65,7 +65,7 @@ export function StockingDataTable({
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Besatz ({total})</h2>
|
||||
<Link href={`/home/${account}/fischerei/stocking/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="stocking-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Besatz eintragen
|
||||
</Button>
|
||||
|
||||
@@ -103,9 +103,15 @@ export function WatersDataTable({
|
||||
<Input
|
||||
placeholder="Gewässer suchen..."
|
||||
className="w-64"
|
||||
data-test="waters-search-input"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="waters-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
@@ -114,6 +120,7 @@ export function WatersDataTable({
|
||||
<select
|
||||
value={currentType}
|
||||
onChange={handleTypeChange}
|
||||
data-test="waters-type-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
{WATER_TYPE_OPTIONS.map((opt) => (
|
||||
@@ -124,7 +131,7 @@ export function WatersDataTable({
|
||||
</select>
|
||||
|
||||
<Link href={`/home/${account}/fischerei/waters/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="waters-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neues Gewässer
|
||||
</Button>
|
||||
|
||||
@@ -155,6 +155,7 @@ export function ApplicationWorkflow({
|
||||
size="sm"
|
||||
variant="default"
|
||||
disabled={isPending}
|
||||
data-test="application-approve-btn"
|
||||
onClick={() => handleApprove(appId)}
|
||||
>
|
||||
Genehmigen
|
||||
@@ -163,6 +164,7 @@ export function ApplicationWorkflow({
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={isPending}
|
||||
data-test="application-reject-btn"
|
||||
onClick={() => handleReject(appId)}
|
||||
>
|
||||
Ablehnen
|
||||
|
||||
@@ -145,6 +145,7 @@ export function DuesCategoryManager({
|
||||
<label className="text-sm font-medium">Name *</label>
|
||||
<Input
|
||||
placeholder="z.B. Standardbeitrag"
|
||||
data-test="dues-name-input"
|
||||
{...form.register('name', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
@@ -155,6 +156,7 @@ export function DuesCategoryManager({
|
||||
step="0.01"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
data-test="dues-amount-input"
|
||||
{...form.register('amount', {
|
||||
required: true,
|
||||
valueAsNumber: true,
|
||||
@@ -184,7 +186,12 @@ export function DuesCategoryManager({
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<Button type="submit" disabled={isCreating} className="w-full">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isCreating}
|
||||
className="w-full"
|
||||
data-test="dues-create-btn"
|
||||
>
|
||||
{isCreating ? 'Erstelle...' : 'Erstellen'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -244,6 +251,7 @@ export function DuesCategoryManager({
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={isDeletePending}
|
||||
data-test="dues-delete-btn"
|
||||
onClick={() => handleDelete(catId, catName)}
|
||||
>
|
||||
Löschen
|
||||
|
||||
@@ -174,6 +174,7 @@ export function MandateManager({
|
||||
<label className="text-sm font-medium">IBAN *</label>
|
||||
<Input
|
||||
placeholder="DE89 3704 0044 0532 0130 00"
|
||||
data-test="mandate-iban-input"
|
||||
{...form.register('iban', { required: true })}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
@@ -185,12 +186,17 @@ export function MandateManager({
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">BIC</label>
|
||||
<Input placeholder="COBADEFFXXX" {...form.register('bic')} />
|
||||
<Input
|
||||
placeholder="COBADEFFXXX"
|
||||
data-test="mandate-bic-input"
|
||||
{...form.register('bic')}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Kontoinhaber *</label>
|
||||
<Input
|
||||
placeholder="Max Mustermann"
|
||||
data-test="mandate-holder-input"
|
||||
{...form.register('accountHolder', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
@@ -214,7 +220,11 @@ export function MandateManager({
|
||||
</select>
|
||||
</div>
|
||||
<div className="sm:col-span-2 lg:col-span-3">
|
||||
<Button type="submit" disabled={isCreating}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isCreating}
|
||||
data-test="mandate-create-btn"
|
||||
>
|
||||
{isCreating ? 'Erstelle...' : 'Mandat erstellen'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -282,6 +292,7 @@ export function MandateManager({
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={isRevoking}
|
||||
data-test="mandate-revoke-btn"
|
||||
onClick={() => handleRevoke(mandateId, reference)}
|
||||
>
|
||||
Widerrufen
|
||||
|
||||
@@ -136,6 +136,7 @@ export function MemberDetailView({
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
data-test="member-edit-btn"
|
||||
onClick={() =>
|
||||
router.push(`/home/${account}/members-cms/${memberId}/edit`)
|
||||
}
|
||||
@@ -152,6 +153,7 @@ export function MemberDetailView({
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={isDeleting}
|
||||
data-test="member-terminate-btn"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{isDeleting ? 'Wird gekündigt...' : 'Kündigen'}
|
||||
|
||||
@@ -110,9 +110,15 @@ export function MembersDataTable({
|
||||
<Input
|
||||
placeholder="Mitglied suchen..."
|
||||
className="w-64"
|
||||
data-test="members-search-input"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="members-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
@@ -121,6 +127,7 @@ export function MembersDataTable({
|
||||
<select
|
||||
value={currentStatus}
|
||||
onChange={handleStatusChange}
|
||||
data-test="members-status-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
{STATUS_OPTIONS.map((opt) => (
|
||||
@@ -132,6 +139,7 @@ export function MembersDataTable({
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
data-test="members-new-btn"
|
||||
onClick={() => router.push(`/home/${account}/members-cms/new`)}
|
||||
>
|
||||
Neues Mitglied
|
||||
|
||||
@@ -9,8 +9,12 @@ export function computeAge(
|
||||
const birth = new Date(dateOfBirth);
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - birth.getFullYear();
|
||||
const m = today.getMonth() - birth.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
|
||||
const monthDifference = today.getMonth() - birth.getMonth();
|
||||
if (
|
||||
monthDifference < 0 ||
|
||||
(monthDifference === 0 && today.getDate() < birth.getDate())
|
||||
)
|
||||
age--;
|
||||
return age;
|
||||
}
|
||||
|
||||
@@ -21,8 +25,12 @@ export function computeMembershipYears(
|
||||
const entry = new Date(entryDate);
|
||||
const today = new Date();
|
||||
let years = today.getFullYear() - entry.getFullYear();
|
||||
const m = today.getMonth() - entry.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < entry.getDate())) years--;
|
||||
const monthDifference = today.getMonth() - entry.getMonth();
|
||||
if (
|
||||
monthDifference < 0 ||
|
||||
(monthDifference === 0 && today.getDate() < entry.getDate())
|
||||
)
|
||||
years--;
|
||||
return Math.max(0, years);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user