Add account hierarchy framework with migrations, RLS policies, and UI components
This commit is contained in:
@@ -13,9 +13,15 @@ import {
|
||||
BedDouble,
|
||||
} from 'lucide-react';
|
||||
|
||||
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,
|
||||
@@ -24,17 +30,10 @@ import {
|
||||
CardTitle,
|
||||
} from '@kit/ui/card';
|
||||
|
||||
import { createMemberManagementApi } from '@kit/member-management/api';
|
||||
import { createCourseManagementApi } from '@kit/course-management/api';
|
||||
import { createFinanceApi } from '@kit/finance/api';
|
||||
import { createNewsletterApi } from '@kit/newsletter/api';
|
||||
import { createBookingManagementApi } from '@kit/booking-management/api';
|
||||
import { createEventManagementApi } from '@kit/event-management/api';
|
||||
|
||||
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';
|
||||
import { AccountNotFound } from '~/components/account-not-found';
|
||||
|
||||
interface TeamAccountHomePageProps {
|
||||
params: Promise<{ account: string }>;
|
||||
@@ -79,7 +78,12 @@ export default async function TeamAccountHomePage({
|
||||
const courseStats =
|
||||
courseStatsResult.status === 'fulfilled'
|
||||
? courseStatsResult.value
|
||||
: { totalCourses: 0, openCourses: 0, completedCourses: 0, totalParticipants: 0 };
|
||||
: {
|
||||
totalCourses: 0,
|
||||
openCourses: 0,
|
||||
completedCourses: 0,
|
||||
totalParticipants: 0,
|
||||
};
|
||||
|
||||
const openInvoices =
|
||||
invoicesResult.status === 'fulfilled' ? invoicesResult.value : [];
|
||||
@@ -145,82 +149,68 @@ export default async function TeamAccountHomePage({
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
href={`/home/${account}/bookings/${String(booking.id)}`}
|
||||
className="text-sm font-medium hover:underline"
|
||||
>
|
||||
Buchung{' '}
|
||||
{booking.check_in
|
||||
? new Date(
|
||||
String(booking.check_in),
|
||||
).toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
})
|
||||
: '—'}
|
||||
</Link>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{booking.check_in
|
||||
? new Date(
|
||||
String(booking.check_in),
|
||||
).toLocaleDateString('de-DE')
|
||||
: '—'}{' '}
|
||||
–{' '}
|
||||
{booking.check_out
|
||||
? new Date(
|
||||
String(booking.check_out),
|
||||
).toLocaleDateString('de-DE')
|
||||
: '—'}
|
||||
</p>
|
||||
{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" />
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
href={`/home/${account}/bookings/${String(booking.id)}`}
|
||||
className="text-sm font-medium hover:underline"
|
||||
>
|
||||
Buchung{' '}
|
||||
{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>
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
href={`/home/${account}/events/${String(event.id)}`}
|
||||
className="text-sm font-medium hover:underline"
|
||||
>
|
||||
{String(event.name)}
|
||||
</Link>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{event.event_date
|
||||
? new Date(
|
||||
String(event.event_date),
|
||||
).toLocaleDateString('de-DE')
|
||||
: 'Kein Datum'}
|
||||
</p>
|
||||
{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" />
|
||||
</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>
|
||||
<Badge variant="outline">
|
||||
{String(event.status ?? '—')}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
|
||||
{bookings.data.length === 0 && events.data.length === 0 && (
|
||||
<EmptyState
|
||||
@@ -242,7 +232,7 @@ export default async function TeamAccountHomePage({
|
||||
<CardContent className="flex flex-col gap-2">
|
||||
<Link
|
||||
href={`/home/${account}/members-cms/new`}
|
||||
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
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" />
|
||||
@@ -253,7 +243,7 @@ export default async function TeamAccountHomePage({
|
||||
|
||||
<Link
|
||||
href={`/home/${account}/courses/new`}
|
||||
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
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" />
|
||||
@@ -264,7 +254,7 @@ export default async function TeamAccountHomePage({
|
||||
|
||||
<Link
|
||||
href={`/home/${account}/newsletter/new`}
|
||||
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
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" />
|
||||
@@ -275,7 +265,7 @@ export default async function TeamAccountHomePage({
|
||||
|
||||
<Link
|
||||
href={`/home/${account}/bookings/new`}
|
||||
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
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" />
|
||||
@@ -286,7 +276,7 @@ export default async function TeamAccountHomePage({
|
||||
|
||||
<Link
|
||||
href={`/home/${account}/events/new`}
|
||||
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
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" />
|
||||
@@ -304,21 +294,23 @@ export default async function TeamAccountHomePage({
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm font-medium">
|
||||
Buchungen
|
||||
</p>
|
||||
<p className="text-2xl font-bold">{bookings.total}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{bookings.data.filter(
|
||||
(b: Record<string, unknown>) =>
|
||||
b.status === 'confirmed' || b.status === 'checked_in',
|
||||
).length}{' '}
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{
|
||||
bookings.data.filter(
|
||||
(b: Record<string, unknown>) =>
|
||||
b.status === 'confirmed' || b.status === 'checked_in',
|
||||
).length
|
||||
}{' '}
|
||||
aktiv
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={`/home/${account}/bookings`}
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
className="hover:bg-muted hover:text-foreground inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium transition-all"
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
@@ -330,22 +322,24 @@ export default async function TeamAccountHomePage({
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm font-medium">
|
||||
Veranstaltungen
|
||||
</p>
|
||||
<p className="text-2xl font-bold">{events.total}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{events.data.filter(
|
||||
(e: Record<string, unknown>) =>
|
||||
e.status === 'published' ||
|
||||
e.status === 'registration_open',
|
||||
).length}{' '}
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{
|
||||
events.data.filter(
|
||||
(e: Record<string, unknown>) =>
|
||||
e.status === 'published' ||
|
||||
e.status === 'registration_open',
|
||||
).length
|
||||
}{' '}
|
||||
aktiv
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={`/home/${account}/events`}
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
className="hover:bg-muted hover:text-foreground inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium transition-all"
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
@@ -357,19 +351,19 @@ export default async function TeamAccountHomePage({
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm font-medium">
|
||||
Kurse abgeschlossen
|
||||
</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{courseStats.completedCourses}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
von {courseStats.totalCourses} insgesamt
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={`/home/${account}/courses`}
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
|
||||
className="hover:bg-muted hover:text-foreground inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium transition-all"
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user