feat: enhance accessibility and testing with data-test attributes and improve error handling
This commit is contained in:
@@ -39,7 +39,7 @@ export default async function GuestsPage({ params }: PageProps) {
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-muted-foreground">Gästeverwaltung</p>
|
||||
<Button>
|
||||
<Button data-test="guests-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Gast
|
||||
</Button>
|
||||
|
||||
@@ -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<string, string> | null;
|
||||
const guest = b.guest as Record<string, string> | null;
|
||||
bookingsData = bookingsData.filter((booking) => {
|
||||
const room = booking.room as Record<string, string> | null;
|
||||
const guest = booking.guest as Record<string, string> | 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({
|
||||
</p>
|
||||
|
||||
<Link href={`/home/${account}/bookings/new`}>
|
||||
<Button>
|
||||
<Button data-test="bookings-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Buchung
|
||||
</Button>
|
||||
|
||||
@@ -40,7 +40,7 @@ export default async function RoomsPage({ params }: PageProps) {
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-muted-foreground">Zimmerverwaltung</p>
|
||||
<Button>
|
||||
<Button data-test="rooms-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neues Zimmer
|
||||
</Button>
|
||||
|
||||
@@ -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 <div>Kurs nicht gefunden</div>;
|
||||
if (!course) return <AccountNotFound />;
|
||||
|
||||
const selectedSessionId =
|
||||
(search.session as string) ??
|
||||
|
||||
@@ -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 <div>Kurs nicht gefunden</div>;
|
||||
if (!course) return <AccountNotFound />;
|
||||
|
||||
const c = course as Record<string, unknown>;
|
||||
const courseData = course as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
<CmsPageShell account={account} title={String(c.name)}>
|
||||
<CmsPageShell account={account} title={String(courseData.name)}>
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -66,7 +67,7 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<GraduationCap className="text-primary h-5 w-5" />
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Name</p>
|
||||
<p className="font-semibold">{String(c.name)}</p>
|
||||
<p className="font-semibold">{String(courseData.name)}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -76,9 +77,12 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Status</p>
|
||||
<Badge
|
||||
variant={STATUS_VARIANT[String(c.status)] ?? 'secondary'}
|
||||
variant={
|
||||
STATUS_VARIANT[String(courseData.status)] ?? 'secondary'
|
||||
}
|
||||
>
|
||||
{STATUS_LABEL[String(c.status)] ?? String(c.status)}
|
||||
{STATUS_LABEL[String(courseData.status)] ??
|
||||
String(courseData.status)}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -89,7 +93,7 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Dozent</p>
|
||||
<p className="font-semibold">
|
||||
{String(c.instructor_id ?? '—')}
|
||||
{String(courseData.instructor_id ?? '—')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -100,9 +104,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Beginn – Ende</p>
|
||||
<p className="font-semibold">
|
||||
{formatDate(c.start_date as string)}
|
||||
{formatDate(courseData.start_date as string)}
|
||||
{' – '}
|
||||
{formatDate(c.end_date as string)}
|
||||
{formatDate(courseData.end_date as string)}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -113,7 +117,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Gebühr</p>
|
||||
<p className="font-semibold">
|
||||
{c.fee != null ? `${Number(c.fee).toFixed(2)} €` : '—'}
|
||||
{courseData.fee != null
|
||||
? `${Number(courseData.fee).toFixed(2)} €`
|
||||
: '—'}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -124,7 +130,7 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Teilnehmer</p>
|
||||
<p className="font-semibold">
|
||||
{participants.length} / {String(c.capacity ?? '∞')}
|
||||
{participants.length} / {String(courseData.capacity ?? '∞')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -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 <div>Kurs nicht gefunden</div>;
|
||||
if (!course) return <AccountNotFound />;
|
||||
|
||||
return (
|
||||
<CmsPageShell account={account} title="Teilnehmer">
|
||||
@@ -56,7 +57,7 @@ export default async function ParticipantsPage({ params }: PageProps) {
|
||||
{participants.length} Teilnehmer
|
||||
</p>
|
||||
</div>
|
||||
<Button>
|
||||
<Button data-test="participants-add-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Teilnehmer anmelden
|
||||
</Button>
|
||||
|
||||
@@ -67,10 +67,14 @@ export default async function CourseCalendarPage({ params }: PageProps) {
|
||||
const courseDates = new Set<number>();
|
||||
|
||||
for (const course of courses.data) {
|
||||
const c = course as Record<string, unknown>;
|
||||
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<string, unknown>;
|
||||
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<string, unknown>) =>
|
||||
c.status === 'open' || c.status === 'running',
|
||||
(courseItem: Record<string, unknown>) =>
|
||||
courseItem.status === 'open' || courseItem.status === 'running',
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function CategoriesPage({ params }: PageProps) {
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-muted-foreground">Kurskategorien verwalten</p>
|
||||
<Button>
|
||||
<Button data-test="categories-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Kategorie
|
||||
</Button>
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function InstructorsPage({ params }: PageProps) {
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-muted-foreground">Dozentenpool verwalten</p>
|
||||
<Button>
|
||||
<Button data-test="instructors-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Dozent
|
||||
</Button>
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function LocationsPage({ params }: PageProps) {
|
||||
<p className="text-muted-foreground">
|
||||
Kurs- und Veranstaltungsorte verwalten
|
||||
</p>
|
||||
<Button>
|
||||
<Button data-test="locations-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Ort
|
||||
</Button>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) {
|
||||
<p className="text-muted-foreground">Kursangebot verwalten</p>
|
||||
|
||||
<Link href={`/home/${account}/courses/new`}>
|
||||
<Button>
|
||||
<Button data-test="courses-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Kurs
|
||||
</Button>
|
||||
|
||||
@@ -68,6 +68,7 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
|
||||
<select
|
||||
id="documentType"
|
||||
name="documentType"
|
||||
data-test="document-type-select"
|
||||
value={selectedType}
|
||||
onChange={(e) => {
|
||||
setSelectedType(e.target.value);
|
||||
@@ -190,7 +191,11 @@ export function GenerateDocumentForm({ accountSlug, initialType }: Props) {
|
||||
|
||||
{/* Submit button */}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={isPending || isComingSoon}>
|
||||
<Button
|
||||
type="submit"
|
||||
data-test="document-generate-btn"
|
||||
disabled={isPending || isComingSoon}
|
||||
>
|
||||
{isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
|
||||
@@ -259,10 +259,10 @@ async function generateMemberCards(
|
||||
orientation: input.orientation,
|
||||
style: s.page,
|
||||
},
|
||||
...batch.map((m) =>
|
||||
...batch.map((memberItem) =>
|
||||
React.createElement(
|
||||
View,
|
||||
{ key: m.id, style: s.card },
|
||||
{ key: memberItem.id, style: s.card },
|
||||
// Accent bar
|
||||
React.createElement(View, { style: s.accentBar }),
|
||||
|
||||
@@ -298,7 +298,7 @@ async function generateMemberCards(
|
||||
React.createElement(
|
||||
Text,
|
||||
{ style: s.memberNumber },
|
||||
`Nr. ${m.member_number ?? '–'}`,
|
||||
`Nr. ${memberItem.member_number ?? '–'}`,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -309,7 +309,7 @@ async function generateMemberCards(
|
||||
React.createElement(
|
||||
Text,
|
||||
{ style: s.memberName },
|
||||
`${m.first_name} ${m.last_name}`,
|
||||
`${memberItem.first_name} ${memberItem.last_name}`,
|
||||
),
|
||||
React.createElement(
|
||||
View,
|
||||
@@ -326,7 +326,7 @@ async function generateMemberCards(
|
||||
React.createElement(
|
||||
Text,
|
||||
{ style: s.fieldValue },
|
||||
fmtDate(m.entry_date),
|
||||
fmtDate(memberItem.entry_date),
|
||||
),
|
||||
),
|
||||
// Date of birth
|
||||
@@ -341,7 +341,7 @@ async function generateMemberCards(
|
||||
React.createElement(
|
||||
Text,
|
||||
{ style: s.fieldValue },
|
||||
fmtDate(m.date_of_birth),
|
||||
fmtDate(memberItem.date_of_birth),
|
||||
),
|
||||
),
|
||||
// Address
|
||||
@@ -356,13 +356,16 @@ async function generateMemberCards(
|
||||
React.createElement(
|
||||
Text,
|
||||
{ style: s.fieldValue },
|
||||
[m.street, m.house_number].filter(Boolean).join(' ') ||
|
||||
'–',
|
||||
[memberItem.street, memberItem.house_number]
|
||||
.filter(Boolean)
|
||||
.join(' ') || '–',
|
||||
),
|
||||
React.createElement(
|
||||
Text,
|
||||
{ style: { ...s.fieldValue, marginTop: 1 } },
|
||||
[m.postal_code, m.city].filter(Boolean).join(' ') || '',
|
||||
[memberItem.postal_code, memberItem.city]
|
||||
.filter(Boolean)
|
||||
.join(' ') || '',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -428,12 +431,20 @@ async function generateLabels(
|
||||
return { success: false, error: 'Keine aktiven Mitglieder.' };
|
||||
|
||||
const api = createDocumentGeneratorApi();
|
||||
const records = members.map((m) => ({
|
||||
line1: [m.salutation, m.title, m.first_name, m.last_name]
|
||||
const records = members.map((record) => ({
|
||||
line1: [
|
||||
record.salutation,
|
||||
record.title,
|
||||
record.first_name,
|
||||
record.last_name,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
line2: [m.street, m.house_number].filter(Boolean).join(' ') || undefined,
|
||||
line3: [m.postal_code, m.city].filter(Boolean).join(' ') || undefined,
|
||||
line2:
|
||||
[record.street, record.house_number].filter(Boolean).join(' ') ||
|
||||
undefined,
|
||||
line3:
|
||||
[record.postal_code, record.city].filter(Boolean).join(' ') || undefined,
|
||||
}));
|
||||
|
||||
const html = api.generateLabelsHtml({ labelFormat: 'avery-l7163', records });
|
||||
@@ -500,16 +511,16 @@ async function generateMemberReport(
|
||||
excluded: 'Ausgeschlossen',
|
||||
};
|
||||
|
||||
for (const m of members) {
|
||||
for (const member of members) {
|
||||
ws.addRow({
|
||||
nr: m.member_number ?? '',
|
||||
name: m.last_name,
|
||||
vorname: m.first_name,
|
||||
email: m.email ?? '',
|
||||
plz: m.postal_code ?? '',
|
||||
ort: m.city ?? '',
|
||||
status: SL[m.status] ?? m.status,
|
||||
eintritt: m.entry_date ? formatDate(m.entry_date) : '',
|
||||
nr: member.member_number ?? '',
|
||||
name: member.last_name,
|
||||
vorname: member.first_name,
|
||||
email: member.email ?? '',
|
||||
plz: member.postal_code ?? '',
|
||||
ort: member.city ?? '',
|
||||
status: SL[member.status] ?? member.status,
|
||||
eintritt: member.entry_date ? formatDate(member.entry_date) : '',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default async function DocumentTemplatesPage({ params }: PageProps) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button>
|
||||
<Button data-test="document-templates-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Vorlage
|
||||
</Button>
|
||||
|
||||
@@ -56,20 +56,21 @@ export default async function EventDetailPage({ params }: PageProps) {
|
||||
|
||||
if (!event) return <div>Veranstaltung nicht gefunden</div>;
|
||||
|
||||
const e = event as Record<string, unknown>;
|
||||
const eventData = event as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
<CmsPageShell account={account} title={String(e.name)}>
|
||||
<CmsPageShell account={account} title={String(eventData.name)}>
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{String(e.name)}</h1>
|
||||
<h1 className="text-2xl font-bold">{String(eventData.name)}</h1>
|
||||
<Badge
|
||||
variant={STATUS_VARIANT[String(e.status)] ?? 'secondary'}
|
||||
variant={STATUS_VARIANT[String(eventData.status)] ?? 'secondary'}
|
||||
className="mt-1"
|
||||
>
|
||||
{STATUS_LABEL[String(e.status)] ?? String(e.status)}
|
||||
{STATUS_LABEL[String(eventData.status)] ??
|
||||
String(eventData.status)}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button>
|
||||
@@ -86,7 +87,7 @@ export default async function EventDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Datum</p>
|
||||
<p className="font-semibold">
|
||||
{formatDate(e.event_date as string)}
|
||||
{formatDate(eventData.event_date as string)}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -97,7 +98,8 @@ export default async function EventDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Uhrzeit</p>
|
||||
<p className="font-semibold">
|
||||
{String(e.start_time ?? '—')} – {String(e.end_time ?? '—')}
|
||||
{String(eventData.start_time ?? '—')} –{' '}
|
||||
{String(eventData.end_time ?? '—')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -107,7 +109,9 @@ export default async function EventDetailPage({ params }: PageProps) {
|
||||
<MapPin className="text-primary h-5 w-5" />
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Ort</p>
|
||||
<p className="font-semibold">{String(e.location ?? '—')}</p>
|
||||
<p className="font-semibold">
|
||||
{String(eventData.location ?? '—')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -117,7 +121,7 @@ export default async function EventDetailPage({ params }: PageProps) {
|
||||
<div>
|
||||
<p className="text-muted-foreground text-xs">Anmeldungen</p>
|
||||
<p className="font-semibold">
|
||||
{registrations.length} / {String(e.capacity ?? '∞')}
|
||||
{registrations.length} / {String(eventData.capacity ?? '∞')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -125,14 +129,14 @@ export default async function EventDetailPage({ params }: PageProps) {
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{e.description ? (
|
||||
{eventData.description ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Beschreibung</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground text-sm whitespace-pre-wrap">
|
||||
{String(e.description)}
|
||||
{String(eventData.description)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -47,19 +47,21 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
|
||||
const events = await api.listEvents(acct.id, { page });
|
||||
|
||||
// Fetch registration counts for all events on this page
|
||||
const eventIds = events.data.map((e: Record<string, unknown>) =>
|
||||
String(e.id),
|
||||
const eventIds = events.data.map((eventItem: Record<string, unknown>) =>
|
||||
String(eventItem.id),
|
||||
);
|
||||
const registrationCounts = await api.getRegistrationCounts(eventIds);
|
||||
|
||||
// Pre-compute stats before rendering
|
||||
const uniqueLocationCount = new Set(
|
||||
events.data.map((e: Record<string, unknown>) => e.location).filter(Boolean),
|
||||
events.data
|
||||
.map((eventItem: Record<string, unknown>) => eventItem.location)
|
||||
.filter(Boolean),
|
||||
).size;
|
||||
|
||||
const totalCapacity = events.data.reduce(
|
||||
(sum: number, e: Record<string, unknown>) =>
|
||||
sum + (Number(e.capacity) || 0),
|
||||
(sum: number, eventItem: Record<string, unknown>) =>
|
||||
sum + (Number(eventItem.capacity) || 0),
|
||||
0,
|
||||
);
|
||||
|
||||
@@ -74,7 +76,7 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
|
||||
</div>
|
||||
|
||||
<Link href={`/home/${account}/events/new`}>
|
||||
<Button>
|
||||
<Button data-test="events-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t('newEvent')}
|
||||
</Button>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
|
||||
const api = createFinanceApi(client);
|
||||
const invoice = await api.getInvoiceWithItems(id);
|
||||
|
||||
if (!invoice) return <div>Rechnung nicht gefunden</div>;
|
||||
if (!invoice) return <AccountNotFound />;
|
||||
|
||||
const status = String(invoice.status);
|
||||
const items = (invoice.items ?? []) as Array<Record<string, unknown>>;
|
||||
|
||||
@@ -74,7 +74,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
|
||||
api.getBatchItems(batchId),
|
||||
]);
|
||||
|
||||
if (!batch) return <div>Einzug nicht gefunden</div>;
|
||||
if (!batch) return <AccountNotFound />;
|
||||
|
||||
const status = String(batch.status);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) {
|
||||
api.getRecipients(campaignId),
|
||||
]);
|
||||
|
||||
if (!newsletter) return <div>Newsletter nicht gefunden</div>;
|
||||
if (!newsletter) return <AccountNotFound />;
|
||||
|
||||
const status = String(newsletter.status);
|
||||
const sentCount = recipients.filter(
|
||||
@@ -60,6 +60,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) {
|
||||
<Link
|
||||
href={`/home/${account}/newsletter`}
|
||||
className="text-muted-foreground hover:text-foreground inline-flex items-center text-sm"
|
||||
data-test="newsletter-back-link"
|
||||
>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Zurück zu Newsletter
|
||||
@@ -98,7 +99,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) {
|
||||
{/* Actions */}
|
||||
{status === 'draft' && (
|
||||
<div className="mt-6">
|
||||
<Button>
|
||||
<Button data-test="newsletter-send-btn">
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Newsletter versenden
|
||||
</Button>
|
||||
|
||||
@@ -82,7 +82,7 @@ export default async function NewsletterPage({
|
||||
</div>
|
||||
|
||||
<Link href={`/home/${account}/newsletter/new`}>
|
||||
<Button>
|
||||
<Button data-test="newsletter-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Newsletter
|
||||
</Button>
|
||||
|
||||
@@ -43,7 +43,7 @@ export default async function NewsletterTemplatesPage({ params }: PageProps) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button>
|
||||
<Button data-test="newsletter-templates-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Vorlage
|
||||
</Button>
|
||||
|
||||
@@ -20,7 +20,7 @@ export default async function EditPageRoute({ params }: Props) {
|
||||
|
||||
const api = createSiteBuilderApi(client);
|
||||
const page = await api.getPage(pageId);
|
||||
if (!page) return <div>Seite nicht gefunden</div>;
|
||||
if (!page) return <AccountNotFound />;
|
||||
|
||||
return (
|
||||
<SiteEditor
|
||||
|
||||
@@ -69,7 +69,7 @@ export default async function SiteBuilderDashboard({ params }: Props) {
|
||||
)}
|
||||
</div>
|
||||
<Link href={`/home/${account}/site-builder/new`}>
|
||||
<Button>
|
||||
<Button data-test="site-new-page-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Seite
|
||||
</Button>
|
||||
|
||||
@@ -38,7 +38,7 @@ export default async function PostsManagerPage({ params }: Props) {
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-end">
|
||||
<Button>
|
||||
<Button data-test="site-new-post-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Beitrag
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user