Files
myeasycms-v2/apps/web/app/[locale]/home/[account]/page.tsx
Zaid Marzguioui b26e5aaafa
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m20s
Workflow / ⚫️ Test (push) Has been skipped
feat: pre-existing local changes — fischerei, verband, modules, members, packages
Commits all remaining uncommitted local work:

- apps/web: fischerei, verband, modules, members-cms, documents,
  newsletter, meetings, site-builder, courses, bookings, events,
  finance pages and components
- apps/web: marketing page updates, layout, paths config,
  next.config.mjs, styles/makerkit.css
- apps/web/i18n: documents, fischerei, marketing, verband (de+en)
- packages/features: finance, fischerei, member-management,
  module-builder, newsletter, sitzungsprotokolle, verbandsverwaltung
  server APIs and components
- packages/ui: button.tsx updates
- pnpm-lock.yaml
2026-04-02 01:19:54 +02:00

309 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Link from 'next/link';
import {
ArrowRight,
FileText,
GraduationCap,
Mail,
Plus,
UserCheck,
UserPlus,
CalendarDays,
Activity,
BedDouble,
} from 'lucide-react';
import { getTranslations } from 'next-intl/server';
import { createBookingManagementApi } from '@kit/booking-management/api';
import { createCourseManagementApi } from '@kit/course-management/api';
import { createEventManagementApi } from '@kit/event-management/api';
import { createFinanceApi } from '@kit/finance/api';
import { createMemberManagementApi } from '@kit/member-management/api';
import { createNewsletterApi } from '@kit/newsletter/api';
import { formatDate } from '@kit/shared/dates';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import {
Card,
CardContent,
CardDescription,
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';
import { StatsCard } from '~/components/stats-card';
interface TeamAccountHomePageProps {
params: Promise<{ account: string }>;
}
export default async function TeamAccountHomePage({
params,
}: TeamAccountHomePageProps) {
const { account } = await params;
const client = getSupabaseServerClient();
const t = await getTranslations('common');
const { data: acct } = await client
.from('accounts')
.select('id, name')
.eq('slug', account)
.single();
if (!acct) return <AccountNotFound />;
// Load all stats in parallel with allSettled for resilience
const [
memberStatsResult,
courseStatsResult,
invoicesResult,
newslettersResult,
bookingsResult,
eventsResult,
] = await Promise.allSettled([
createMemberManagementApi(client).getMemberStatistics(acct.id),
createCourseManagementApi(client).getStatistics(acct.id),
createFinanceApi(client).listInvoices(acct.id, { status: 'draft' }),
createNewsletterApi(client).listNewsletters(acct.id),
createBookingManagementApi(client).listBookings(acct.id, { page: 1 }),
createEventManagementApi(client).listEvents(acct.id, { page: 1 }),
]);
const memberStats =
memberStatsResult.status === 'fulfilled'
? memberStatsResult.value
: { total: 0, active: 0, inactive: 0, pending: 0, resigned: 0 };
const courseStats =
courseStatsResult.status === 'fulfilled'
? courseStatsResult.value
: {
totalCourses: 0,
openCourses: 0,
completedCourses: 0,
totalParticipants: 0,
};
const openInvoices =
invoicesResult.status === 'fulfilled' ? invoicesResult.value.data : [];
const newsletters =
newslettersResult.status === 'fulfilled'
? newslettersResult.value.data
: [];
const bookings =
bookingsResult.status === 'fulfilled'
? bookingsResult.value
: { data: [], total: 0 };
const events =
eventsResult.status === 'fulfilled'
? eventsResult.value
: { data: [], total: 0 };
const accountName = acct.name ? String(acct.name) : 'Dashboard';
return (
<CmsPageShell account={account} title={accountName}>
<div className="flex w-full flex-col gap-6">
{/* Stats Row */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<StatsCard
title={t('dashboard.members')}
value={memberStats.active}
icon={<UserCheck className="h-5 w-5" aria-hidden="true" />}
description={t('dashboard.membersDescription', {
total: memberStats.total,
pending: memberStats.pending,
})}
/>
<StatsCard
title={t('dashboard.courses')}
value={courseStats.openCourses}
icon={<GraduationCap className="h-5 w-5" aria-hidden="true" />}
description={t('dashboard.coursesDescription', {
total: courseStats.totalCourses,
participants: courseStats.totalParticipants,
})}
/>
<StatsCard
title={t('dashboard.openInvoices')}
value={openInvoices.length}
icon={<FileText className="h-5 w-5" aria-hidden="true" />}
description={t('dashboard.openInvoicesDescription')}
/>
<StatsCard
title={t('dashboard.newsletters')}
value={newsletters.length}
icon={<Mail className="h-5 w-5" aria-hidden="true" />}
description={t('dashboard.newslettersDescription')}
/>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* Recent Activity */}
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Activity className="h-5 w-5" aria-hidden="true" />
{t('dashboard.recentActivity')}
</CardTitle>
<CardDescription>
{t('dashboard.recentActivityDescription')}
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* Recent bookings */}
{bookings.data
.slice(0, 3)
.map((booking: Record<string, unknown>) => (
<div
key={String(booking.id)}
className="flex items-center justify-between rounded-md border p-3"
>
<div className="flex items-center gap-3">
<div className="rounded-full bg-blue-500/10 p-2 text-blue-600">
<BedDouble className="h-4 w-4" aria-hidden="true" />
</div>
<div>
<Link
href={`/home/${account}/bookings/${String(booking.id)}`}
className="text-sm font-medium hover:underline"
>
{t('dashboard.bookingFrom')}{' '}
{booking.check_in
? formatDate(booking.check_in as string)
: '—'}
</Link>
<p className="text-muted-foreground text-xs">
{formatDate(booking.check_in as string)} {' '}
{formatDate(booking.check_out as string)}
</p>
</div>
</div>
<Badge variant="outline">
{String(booking.status ?? '—')}
</Badge>
</div>
))}
{/* Recent events */}
{events.data
.slice(0, 3)
.map((event: Record<string, unknown>) => (
<div
key={String(event.id)}
className="flex items-center justify-between rounded-md border p-3"
>
<div className="flex items-center gap-3">
<div className="rounded-full bg-amber-500/10 p-2 text-amber-600">
<CalendarDays
className="h-4 w-4"
aria-hidden="true"
/>
</div>
<div>
<Link
href={`/home/${account}/events/${String(event.id)}`}
className="text-sm font-medium hover:underline"
>
{String(event.name)}
</Link>
<p className="text-muted-foreground text-xs">
{formatDate(event.event_date as string)}
</p>
</div>
</div>
<Badge variant="outline">
{String(event.status ?? '—')}
</Badge>
</div>
))}
{bookings.data.length === 0 && events.data.length === 0 && (
<EmptyState
icon={<Activity className="h-8 w-8" aria-hidden="true" />}
title={t('dashboard.recentActivityEmpty')}
description={t('dashboard.recentActivityEmptyDescription')}
/>
)}
</div>
</CardContent>
</Card>
{/* Quick Actions */}
<Card>
<CardHeader>
<CardTitle>{t('dashboard.quickActions')}</CardTitle>
<CardDescription>
{t('dashboard.quickActionsDescription')}
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-2">
<Link
href={`/home/${account}/members-cms/new`}
className="border-border bg-background hover:bg-muted hover:text-foreground inline-flex w-full items-center justify-between gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-all"
>
<span className="flex items-center gap-2">
<UserPlus className="h-4 w-4" aria-hidden="true" />
{t('dashboard.newMember')}
</span>
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
<Link
href={`/home/${account}/courses/new`}
className="border-border bg-background hover:bg-muted hover:text-foreground inline-flex w-full items-center justify-between gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-all"
>
<span className="flex items-center gap-2">
<GraduationCap className="h-4 w-4" aria-hidden="true" />
{t('dashboard.newCourse')}
</span>
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
<Link
href={`/home/${account}/newsletter/new`}
className="border-border bg-background hover:bg-muted hover:text-foreground inline-flex w-full items-center justify-between gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-all"
>
<span className="flex items-center gap-2">
<Mail className="h-4 w-4" />
{t('dashboard.createNewsletter')}
</span>
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
<Link
href={`/home/${account}/bookings/new`}
className="border-border bg-background hover:bg-muted hover:text-foreground inline-flex w-full items-center justify-between gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-all"
>
<span className="flex items-center gap-2">
<BedDouble className="h-4 w-4" aria-hidden="true" />
{t('dashboard.newBooking')}
</span>
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
<Link
href={`/home/${account}/events/new`}
className="border-border bg-background hover:bg-muted hover:text-foreground inline-flex w-full items-center justify-between gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-all"
>
<span className="flex items-center gap-2">
<Plus className="h-4 w-4" aria-hidden="true" />
{t('dashboard.newEvent')}
</span>
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
</CardContent>
</Card>
</div>
</div>
</CmsPageShell>
);
}