From abac22feb1924fd3329a327869e58aa5510bee11 Mon Sep 17 00:00:00 2001 From: "T. Zehetbauer" <125989630+4thTomost@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:46:44 +0200 Subject: [PATCH] feat: enhance accessibility and testing with data-test attributes and improve error handling --- .../home/[account]/bookings/guests/page.tsx | 2 +- .../[locale]/home/[account]/bookings/page.tsx | 11 +- .../home/[account]/bookings/rooms/page.tsx | 2 +- .../courses/[courseId]/attendance/page.tsx | 3 +- .../[account]/courses/[courseId]/page.tsx | 28 ++-- .../courses/[courseId]/participants/page.tsx | 5 +- .../home/[account]/courses/calendar/page.tsx | 16 ++- .../[account]/courses/categories/page.tsx | 2 +- .../[account]/courses/instructors/page.tsx | 2 +- .../home/[account]/courses/locations/page.tsx | 2 +- .../[locale]/home/[account]/courses/page.tsx | 2 +- .../_components/generate-document-form.tsx | 7 +- .../_lib/server/generate-document.ts | 55 ++++---- .../[account]/documents/templates/page.tsx | 2 +- .../home/[account]/events/[eventId]/page.tsx | 26 ++-- .../[locale]/home/[account]/events/page.tsx | 14 +- .../[account]/finance/invoices/[id]/page.tsx | 2 +- .../[account]/finance/sepa/[batchId]/page.tsx | 2 +- .../newsletter/[campaignId]/page.tsx | 5 +- .../home/[account]/newsletter/page.tsx | 2 +- .../[account]/newsletter/templates/page.tsx | 2 +- .../site-builder/[pageId]/edit/page.tsx | 2 +- .../home/[account]/site-builder/page.tsx | 2 +- .../[account]/site-builder/posts/page.tsx | 2 +- apps/web/i18n/messages/de/bookings.json | 71 +++++++++++ apps/web/i18n/messages/de/courses.json | 120 ++++++++++++++++++ apps/web/i18n/messages/de/documents.json | 81 ++++++++++++ apps/web/i18n/messages/de/events.json | 67 ++++++++++ apps/web/i18n/messages/de/finance.json | 113 +++++++++++++++++ apps/web/i18n/messages/de/meetings.json | 80 ++++++++++++ apps/web/i18n/messages/de/newsletter.json | 76 +++++++++++ apps/web/i18n/messages/de/siteBuilder.json | 53 ++++++++ apps/web/i18n/messages/en/bookings.json | 71 +++++++++++ apps/web/i18n/messages/en/courses.json | 120 ++++++++++++++++++ apps/web/i18n/messages/en/documents.json | 81 ++++++++++++ apps/web/i18n/messages/en/events.json | 67 ++++++++++ apps/web/i18n/messages/en/finance.json | 113 +++++++++++++++++ apps/web/i18n/messages/en/meetings.json | 80 ++++++++++++ apps/web/i18n/messages/en/newsletter.json | 76 +++++++++++ apps/web/i18n/messages/en/siteBuilder.json | 53 ++++++++ apps/web/i18n/request.ts | 8 ++ .../src/components/create-booking-form.tsx | 23 +++- .../src/components/create-course-form.tsx | 13 +- .../src/components/create-event-form.tsx | 13 +- .../src/components/create-invoice-form.tsx | 33 ++++- .../src/components/create-sepa-batch-form.tsx | 21 ++- .../src/components/create-newsletter-form.tsx | 23 +++- .../src/components/create-page-form.tsx | 13 +- .../src/components/create-post-form.tsx | 13 +- .../site-builder/src/config/puck-config.tsx | 8 +- .../src/components/create-protocol-form.tsx | 27 +++- .../components/meetings-tab-navigation.tsx | 12 +- .../src/components/open-tasks-view.tsx | 2 + .../src/components/protocol-items-list.tsx | 2 + .../src/components/protocols-data-table.tsx | 19 ++- 55 files changed, 1622 insertions(+), 128 deletions(-) create mode 100644 apps/web/i18n/messages/de/bookings.json create mode 100644 apps/web/i18n/messages/de/courses.json create mode 100644 apps/web/i18n/messages/de/documents.json create mode 100644 apps/web/i18n/messages/de/events.json create mode 100644 apps/web/i18n/messages/de/finance.json create mode 100644 apps/web/i18n/messages/de/meetings.json create mode 100644 apps/web/i18n/messages/de/newsletter.json create mode 100644 apps/web/i18n/messages/de/siteBuilder.json create mode 100644 apps/web/i18n/messages/en/bookings.json create mode 100644 apps/web/i18n/messages/en/courses.json create mode 100644 apps/web/i18n/messages/en/documents.json create mode 100644 apps/web/i18n/messages/en/events.json create mode 100644 apps/web/i18n/messages/en/finance.json create mode 100644 apps/web/i18n/messages/en/meetings.json create mode 100644 apps/web/i18n/messages/en/newsletter.json create mode 100644 apps/web/i18n/messages/en/siteBuilder.json 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 1a112fa48..f1f724091 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/guests/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/guests/page.tsx @@ -39,7 +39,7 @@ export default async function GuestsPage({ params }: PageProps) {

Gästeverwaltung

- diff --git a/apps/web/app/[locale]/home/[account]/bookings/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/page.tsx index ac2381305..941263e9a 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/page.tsx @@ -90,9 +90,9 @@ export default async function BookingsPage({ // Post-filter by search query (guest name or room name/number) if (searchQuery) { const q = searchQuery.toLowerCase(); - bookingsData = bookingsData.filter((b) => { - const room = b.room as Record | null; - const guest = b.guest as Record | null; + bookingsData = bookingsData.filter((booking) => { + const room = booking.room as Record | null; + const guest = booking.guest as Record | null; const roomName = (room?.name ?? '').toLowerCase(); const roomNumber = (room?.room_number ?? '').toLowerCase(); const guestFirst = (guest?.first_name ?? '').toLowerCase(); @@ -107,7 +107,8 @@ export default async function BookingsPage({ } const activeBookings = bookingsData.filter( - (b) => b.status === 'confirmed' || b.status === 'checked_in', + (booking) => + booking.status === 'confirmed' || booking.status === 'checked_in', ); const totalPages = Math.ceil(total / PAGE_SIZE); @@ -122,7 +123,7 @@ export default async function BookingsPage({

- diff --git a/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx index 853cc6f62..107b65b47 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/rooms/page.tsx @@ -40,7 +40,7 @@ export default async function RoomsPage({ params }: PageProps) {

Zimmerverwaltung

- 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 e0668428b..1ff22d5d3 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 @@ -6,6 +6,7 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; @@ -29,7 +30,7 @@ export default async function AttendancePage({ api.getParticipants(courseId), ]); - if (!course) return
Kurs nicht gefunden
; + if (!course) return ; const selectedSessionId = (search.session as string) ?? diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx index 79e20c0d2..cbc504366 100644 --- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx @@ -16,6 +16,7 @@ import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; interface PageProps { @@ -52,12 +53,12 @@ export default async function CourseDetailPage({ params }: PageProps) { api.getSessions(courseId), ]); - if (!course) return
Kurs nicht gefunden
; + if (!course) return ; - const c = course as Record; + const courseData = course as Record; return ( - +
{/* Summary Cards */}
@@ -66,7 +67,7 @@ export default async function CourseDetailPage({ params }: PageProps) {

Name

-

{String(c.name)}

+

{String(courseData.name)}

@@ -76,9 +77,12 @@ export default async function CourseDetailPage({ params }: PageProps) {

Status

- {STATUS_LABEL[String(c.status)] ?? String(c.status)} + {STATUS_LABEL[String(courseData.status)] ?? + String(courseData.status)}
@@ -89,7 +93,7 @@ export default async function CourseDetailPage({ params }: PageProps) {

Dozent

- {String(c.instructor_id ?? '—')} + {String(courseData.instructor_id ?? '—')}

@@ -100,9 +104,9 @@ export default async function CourseDetailPage({ params }: PageProps) {

Beginn – Ende

- {formatDate(c.start_date as string)} + {formatDate(courseData.start_date as string)} {' – '} - {formatDate(c.end_date as string)} + {formatDate(courseData.end_date as string)}

@@ -113,7 +117,9 @@ export default async function CourseDetailPage({ params }: PageProps) {

Gebühr

- {c.fee != null ? `${Number(c.fee).toFixed(2)} €` : '—'} + {courseData.fee != null + ? `${Number(courseData.fee).toFixed(2)} €` + : '—'}

@@ -124,7 +130,7 @@ export default async function CourseDetailPage({ params }: PageProps) {

Teilnehmer

- {participants.length} / {String(c.capacity ?? '∞')} + {participants.length} / {String(courseData.capacity ?? '∞')}

diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx index 00770f520..38c18e31d 100644 --- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/participants/page.tsx @@ -9,6 +9,7 @@ import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; @@ -43,7 +44,7 @@ export default async function ParticipantsPage({ params }: PageProps) { api.getParticipants(courseId), ]); - if (!course) return
Kurs nicht gefunden
; + if (!course) return ; return ( @@ -56,7 +57,7 @@ export default async function ParticipantsPage({ params }: PageProps) { {participants.length} Teilnehmer

- diff --git a/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx b/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx index f7af78aa1..42a3363b9 100644 --- a/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx @@ -67,10 +67,14 @@ export default async function CourseCalendarPage({ params }: PageProps) { const courseDates = new Set(); for (const course of courses.data) { - const c = course as Record; - if (c.status === 'cancelled') continue; - const startDate = c.start_date ? new Date(String(c.start_date)) : null; - const endDate = c.end_date ? new Date(String(c.end_date)) : null; + const courseItem = course as Record; + if (courseItem.status === 'cancelled') continue; + const startDate = courseItem.start_date + ? new Date(String(courseItem.start_date)) + : null; + const endDate = courseItem.end_date + ? new Date(String(courseItem.end_date)) + : null; if (!startDate) continue; @@ -112,8 +116,8 @@ export default async function CourseCalendarPage({ params }: PageProps) { } const activeCourses = courses.data.filter( - (c: Record) => - c.status === 'open' || c.status === 'running', + (courseItem: Record) => + courseItem.status === 'open' || courseItem.status === 'running', ); return ( 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 a89033e75..3217ecfd2 100644 --- a/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/categories/page.tsx @@ -33,7 +33,7 @@ export default async function CategoriesPage({ params }: PageProps) {

Kurskategorien verwalten

- 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 d839d71ab..e69776971 100644 --- a/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/instructors/page.tsx @@ -33,7 +33,7 @@ export default async function InstructorsPage({ params }: PageProps) {

Dozentenpool verwalten

- 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 fabb7c09d..25ff31448 100644 --- a/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/locations/page.tsx @@ -35,7 +35,7 @@ export default async function LocationsPage({ params }: PageProps) {

Kurs- und Veranstaltungsorte verwalten

- diff --git a/apps/web/app/[locale]/home/[account]/courses/page.tsx b/apps/web/app/[locale]/home/[account]/courses/page.tsx index 9e1d2fb3d..4fdcef09a 100644 --- a/apps/web/app/[locale]/home/[account]/courses/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/page.tsx @@ -64,7 +64,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {

Kursangebot verwalten

- diff --git a/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx b/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx index c148fe694..dd6c9828a 100644 --- a/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx +++ b/apps/web/app/[locale]/home/[account]/documents/_components/generate-document-form.tsx @@ -68,6 +68,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) { @@ -240,10 +240,19 @@ export function CreateBookingForm({ accountId, account, rooms }: Props) {
- -
diff --git a/packages/features/course-management/src/components/create-course-form.tsx b/packages/features/course-management/src/components/create-course-form.tsx index a559be60d..02841a763 100644 --- a/packages/features/course-management/src/components/create-course-form.tsx +++ b/packages/features/course-management/src/components/create-course-form.tsx @@ -296,10 +296,19 @@ export function CreateCourseForm({ accountId, account }: Props) {
- -
diff --git a/packages/features/event-management/src/components/create-event-form.tsx b/packages/features/event-management/src/components/create-event-form.tsx index d6b355c7f..e314b4dce 100644 --- a/packages/features/event-management/src/components/create-event-form.tsx +++ b/packages/features/event-management/src/components/create-event-form.tsx @@ -352,10 +352,19 @@ export function CreateEventForm({ accountId, account }: Props) {
- -
diff --git a/packages/features/finance/src/components/create-invoice-form.tsx b/packages/features/finance/src/components/create-invoice-form.tsx index 4e4a1d8d4..87da4d891 100644 --- a/packages/features/finance/src/components/create-invoice-form.tsx +++ b/packages/features/finance/src/components/create-invoice-form.tsx @@ -66,7 +66,7 @@ export function CreateInvoiceForm({ accountId, account }: Props) { onSuccess: ({ data }) => { if (data?.success) { toast.success('Rechnung erfolgreich erstellt'); - router.push(`/home/${account}/finance-cms`); + router.push(`/home/${account}/finance/invoices`); } }, onError: ({ error }) => { @@ -98,7 +98,7 @@ export function CreateInvoiceForm({ accountId, account }: Props) { Rechnungsnummer * - + @@ -111,7 +111,11 @@ export function CreateInvoiceForm({ accountId, account }: Props) { Rechnungsdatum - + @@ -124,7 +128,11 @@ export function CreateInvoiceForm({ accountId, account }: Props) { Fälligkeitsdatum * - + @@ -145,7 +153,7 @@ export function CreateInvoiceForm({ accountId, account }: Props) { Name * - + @@ -178,6 +186,7 @@ export function CreateInvoiceForm({ accountId, account }: Props) { type="button" variant="outline" size="sm" + data-test="invoice-add-item-btn" onClick={() => append({ description: '', quantity: 1, unitPrice: 0 }) } @@ -287,6 +296,7 @@ export function CreateInvoiceForm({ accountId, account }: Props) { min={0} step="0.5" className="max-w-[120px]" + data-test="invoice-tax-rate-input" {...field} onChange={(e) => field.onChange(Number(e.target.value))} /> @@ -329,10 +339,19 @@ export function CreateInvoiceForm({ accountId, account }: Props) {
- -
diff --git a/packages/features/finance/src/components/create-sepa-batch-form.tsx b/packages/features/finance/src/components/create-sepa-batch-form.tsx index ce2480740..903863b92 100644 --- a/packages/features/finance/src/components/create-sepa-batch-form.tsx +++ b/packages/features/finance/src/components/create-sepa-batch-form.tsx @@ -82,6 +82,7 @@ export function CreateSepaBatchForm({ accountId, account }: Props) { @@ -119,7 +121,11 @@ export function CreateSepaBatchForm({ accountId, account }: Props) { Ausführungsdatum * - + @@ -129,10 +135,19 @@ export function CreateSepaBatchForm({ accountId, account }: Props) {
- -
diff --git a/packages/features/newsletter/src/components/create-newsletter-form.tsx b/packages/features/newsletter/src/components/create-newsletter-form.tsx index 757d62726..6ef995dd6 100644 --- a/packages/features/newsletter/src/components/create-newsletter-form.tsx +++ b/packages/features/newsletter/src/components/create-newsletter-form.tsx @@ -70,7 +70,7 @@ export function CreateNewsletterForm({ accountId, account }: Props) { Betreff * - + @@ -88,6 +88,7 @@ export function CreateNewsletterForm({ accountId, account }: Props) { rows={12} className="border-input bg-background flex min-h-[200px] w-full rounded-md border px-3 py-2 font-mono text-sm" placeholder="

Hallo!

Ihr Newsletter-Inhalt...

" + data-test="newsletter-body-input" /> @@ -106,6 +107,7 @@ export function CreateNewsletterForm({ accountId, account }: Props) { rows={4} className="border-input bg-background flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm" placeholder="Nur-Text-Fallback für E-Mail-Clients ohne HTML-Unterstützung" + data-test="newsletter-text-input" /> @@ -127,7 +129,11 @@ export function CreateNewsletterForm({ accountId, account }: Props) { Geplanter Versand (optional) - +

Leer lassen, um den Newsletter als Entwurf zu speichern. @@ -140,10 +146,19 @@ export function CreateNewsletterForm({ accountId, account }: Props) {

- -
diff --git a/packages/features/site-builder/src/components/create-page-form.tsx b/packages/features/site-builder/src/components/create-page-form.tsx index 73b2945d1..62f565a4e 100644 --- a/packages/features/site-builder/src/components/create-page-form.tsx +++ b/packages/features/site-builder/src/components/create-page-form.tsx @@ -148,10 +148,19 @@ export function CreatePageForm({ accountId, account }: Props) {
- -
diff --git a/packages/features/site-builder/src/components/create-post-form.tsx b/packages/features/site-builder/src/components/create-post-form.tsx index 59b167e4f..6ca8ed686 100644 --- a/packages/features/site-builder/src/components/create-post-form.tsx +++ b/packages/features/site-builder/src/components/create-post-form.tsx @@ -159,10 +159,19 @@ export function CreatePostForm({ accountId, account }: Props) {
- -
diff --git a/packages/features/site-builder/src/config/puck-config.tsx b/packages/features/site-builder/src/config/puck-config.tsx index 213f9255f..5ed924350 100644 --- a/packages/features/site-builder/src/config/puck-config.tsx +++ b/packages/features/site-builder/src/config/puck-config.tsx @@ -465,15 +465,17 @@ const EventListBlock = ({

Veranstaltungen

{items.map((event) => { - const d = new Date(event.event_date); + const eventDate = new Date(event.event_date); const isExpanded = expandedId === event.id; const isSuccess = successId === event.id; return (
- {d.getDate()} - {formatMonthShort(d)} + + {eventDate.getDate()} + + {formatMonthShort(eventDate)}

{event.name}

diff --git a/packages/features/sitzungsprotokolle/src/components/create-protocol-form.tsx b/packages/features/sitzungsprotokolle/src/components/create-protocol-form.tsx index f745a1319..07fe53f61 100644 --- a/packages/features/sitzungsprotokolle/src/components/create-protocol-form.tsx +++ b/packages/features/sitzungsprotokolle/src/components/create-protocol-form.tsx @@ -83,6 +83,7 @@ export function CreateProtocolForm({ Titel * @@ -100,7 +101,11 @@ export function CreateProtocolForm({ Sitzungsdatum * - + @@ -115,6 +120,7 @@ export function CreateProtocolForm({ Sitzungsart * + @@ -223,10 +233,19 @@ export function CreateProtocolForm({ {/* Submit */}
- -
diff --git a/packages/features/sitzungsprotokolle/src/components/meetings-tab-navigation.tsx b/packages/features/sitzungsprotokolle/src/components/meetings-tab-navigation.tsx index 1957b667d..1f83fe27f 100644 --- a/packages/features/sitzungsprotokolle/src/components/meetings-tab-navigation.tsx +++ b/packages/features/sitzungsprotokolle/src/components/meetings-tab-navigation.tsx @@ -2,6 +2,7 @@ import Link from 'next/link'; +import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; interface MeetingsTabNavigationProps { @@ -10,9 +11,9 @@ interface MeetingsTabNavigationProps { } const TABS = [ - { id: 'overview', label: 'Übersicht', path: '' }, - { id: 'protocols', label: 'Protokolle', path: '/protocols' }, - { id: 'tasks', label: 'Offene Aufgaben', path: '/tasks' }, + { id: 'overview', i18nKey: 'meetings:nav.overview', path: '' }, + { id: 'protocols', i18nKey: 'meetings:nav.protocols', path: '/protocols' }, + { id: 'tasks', i18nKey: 'meetings:nav.tasks', path: '/tasks' }, ] as const; export function MeetingsTabNavigation({ @@ -25,7 +26,7 @@ export function MeetingsTabNavigation({