import { useMemo, useState } from 'react'; import { AlertTriangle, CalendarDays, Camera, ClipboardList, Package, Sparkles, TrendingUp, UserRound, Key } from 'lucide-react'; import { Head, Link, router, usePage } from '@inertiajs/react'; import AppLayout from '@/layouts/app-layout'; import { dashboard } from '@/routes'; import { edit as passwordSettings } from '@/routes/password'; import profileRoutes from '@/routes/profile'; import { send as resendVerificationRoute } from '@/routes/verification'; import { type BreadcrumbItem, type SharedData } from '@/types'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Progress } from '@/components/ui/progress'; import { Separator } from '@/components/ui/separator'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; type DashboardMetrics = { total_events: number; active_events: number; published_events: number; events_with_tasks: number; upcoming_events: number; new_photos: number; task_progress: number; credit_balance: number | null; active_package: { name: string; expires_at: string | null; remaining_events: number | null; } | null; }; type DashboardEvent = { id: number; name: string; slug: string | null; status: string | null; isActive: boolean; date: string | null; photosCount: number; tasksCount: number; joinTokensCount: number; }; type DashboardPurchase = { id: number; packageName: string; price: number | null; purchasedAt: string | null; type: string | null; provider: string | null; }; type TenantSummary = { id: number; name: string; eventCreditsBalance: number | null; subscriptionStatus: string | null; subscriptionExpiresAt: string | null; activePackage: { name: string; price: number | null; expiresAt: string | null; remainingEvents: number | null; } | null; } | null; type DashboardPageProps = { metrics: DashboardMetrics | null; upcomingEvents: DashboardEvent[]; recentPurchases: DashboardPurchase[]; latestPurchase: DashboardPurchase | null; tenant: TenantSummary; emailVerification: { mustVerify: boolean; verified: boolean; }; }; const breadcrumbs: BreadcrumbItem[] = [ { title: 'Dashboard', href: dashboard().url, }, ]; export default function Dashboard() { const { metrics, upcomingEvents, recentPurchases, latestPurchase, tenant, emailVerification, locale } = usePage().props; const [verificationSent, setVerificationSent] = useState(false); const [sendingVerification, setSendingVerification] = useState(false); const needsEmailVerification = emailVerification.mustVerify && !emailVerification.verified; const dateFormatter = useMemo(() => new Intl.DateTimeFormat(locale ?? 'de-DE', { day: '2-digit', month: 'short', year: 'numeric', }), [locale]); const currencyFormatter = useMemo(() => new Intl.NumberFormat(locale ?? 'de-DE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 2, }), [locale]); const taskProgress = metrics?.task_progress ?? 0; const checklistItems = [ { key: 'verify-email', title: 'E-Mail-Adresse bestätigen', description: 'Bestätige deine Adresse, um Einladungen zu versenden und Benachrichtigungen zu erhalten.', done: !needsEmailVerification, }, { key: 'create-event', title: 'Dein erstes Event erstellen', description: 'Starte mit einem Event-Blueprint und passe Agenda, Uploadregeln und Branding an.', done: (metrics?.total_events ?? 0) > 0, }, { key: 'publish-event', title: 'Event veröffentlichen', description: 'Schalte dein Event frei, damit Gäste über den Link Fotos hochladen können.', done: (metrics?.published_events ?? 0) > 0, }, { key: 'invite-guests', title: 'Gästelink teilen', description: 'Nutze QR-Code oder Link, um Gäste einzuladen und erste Uploads zu sammeln.', done: upcomingEvents.some((event) => event.joinTokensCount > 0), }, { key: 'collect-photos', title: 'Fotos sammeln', description: 'Spare Zeit bei der Nachbereitung: neue Uploads erscheinen direkt in deinem Event.', done: (metrics?.new_photos ?? 0) > 0, }, ]; const quickActions = [ { key: 'tenant-admin', label: 'Event-Admin öffnen', description: 'Detaillierte Eventverwaltung, Moderation und Live-Features.', href: '/event-admin', icon: Sparkles, }, { key: 'profile', label: 'Profil verwalten', description: 'Kontaktinformationen, Sprache und E-Mail-Adresse anpassen.', href: profileRoutes.index().url, icon: UserRound, }, { key: 'password', label: 'Passwort aktualisieren', description: 'Sichere dein Konto mit einem aktuellen Passwort.', href: passwordSettings().url, icon: Key, }, { key: 'packages', label: 'Pakete entdecken', description: 'Mehr Events oder Speicher buchen – du bleibst flexibel.', href: `/${locale ?? 'de'}/packages`, icon: Package, }, ]; const stats = [ { key: 'active-events', label: 'Aktive Events', value: metrics?.active_events ?? 0, description: metrics?.active_events ? 'Events sind live und für Gäste sichtbar.' : 'Noch kein Event veröffentlicht – starte heute!', icon: CalendarDays, }, { key: 'upcoming-events', label: 'Bevorstehende Events', value: metrics?.upcoming_events ?? 0, description: metrics?.upcoming_events ? 'Planung läuft – behalte Checklisten und Aufgaben im Blick.' : 'Lass dich vom Assistenten beim Planen unterstützen.', icon: TrendingUp, }, { key: 'new-photos', label: 'Neue Fotos (7 Tage)', value: metrics?.new_photos ?? 0, description: metrics && metrics.new_photos > 0 ? 'Frisch eingetroffene Erinnerungen deiner Gäste.' : 'Sammle erste Uploads über QR-Code oder Direktlink.', icon: Camera, }, { key: 'credit-balance', label: 'Event Credits', value: tenant?.eventCreditsBalance ?? 0, description: tenant?.eventCreditsBalance ? 'Verfügbare Credits für neue Events.' : 'Buche Pakete oder Credits, um weitere Events zu planen.', icon: Package, extra: metrics?.active_package?.remaining_events ?? null, }, { key: 'task-progress', label: 'Event-Checkliste', value: `${taskProgress}%`, description: taskProgress > 0 ? 'Starker Fortschritt! Halte deine Aufgabenliste aktuell.' : 'Nutze Aufgaben und Vorlagen für einen strukturierten Ablauf.', icon: ClipboardList, }, ]; const handleResendVerification = () => { setSendingVerification(true); setVerificationSent(false); router.post(resendVerificationRoute(), {}, { preserveScroll: true, onSuccess: () => setVerificationSent(true), onFinish: () => setSendingVerification(false), }); }; const renderPrice = (price: number | null) => { if (price === null) { return '—'; } try { return currencyFormatter.format(price); } catch (error) { return `${price.toFixed(2)} €`; } }; const formatDate = (value: string | null) => (value ? dateFormatter.format(new Date(value)) : '—'); return (
{needsEmailVerification && (
Bitte bestätige deine E-Mail-Adresse Du kannst alle Funktionen erst vollständig nutzen, sobald deine E-Mail-Adresse bestätigt ist. Prüfe dein Postfach oder fordere einen neuen Link an.
{verificationSent && Wir haben dir gerade einen neuen Bestätigungslink geschickt.}
)}
{stats.map((stat) => (

{stat.label}

{stat.value}

{stat.description}

{stat.key === 'credit-balance' && stat.extra !== null && (
{stat.extra} weitere Events im aktuellen Paket enthalten.
)} {stat.key === 'task-progress' && (
{taskProgress}% deiner Event-Checkliste erledigt.
)}
))}
Bevorstehende Events

Status, Uploads und Aufgaben deiner nächsten Events im Überblick.

0 ? 'secondary' : 'outline'}> {upcomingEvents.length > 0 ? `${upcomingEvents.length} geplant` : 'Noch kein Event geplant'}
{upcomingEvents.length === 0 && (

Plane dein erstes Event und begleite den gesamten Ablauf – vom Briefing bis zur Nachbereitung – direkt hier im Dashboard.

)} {upcomingEvents.map((event) => (

{event.name}

{formatDate(event.date)} · {event.status === 'published' || event.isActive ? 'Live' : 'In Vorbereitung'}

{event.photosCount} Fotos {event.tasksCount} Aufgaben {event.joinTokensCount} Links
))}
Nächstes Paket & Credits

Behalte Laufzeiten und verfügbaren Umfang stets im Blick.

{tenant?.activePackage ? (
{tenant.activePackage.name} {tenant.activePackage.remainingEvents ?? 0} Events übrig
Läuft ab {formatDate(tenant.activePackage.expiresAt)}
Preis {renderPrice(tenant.activePackage.price)}
{latestPurchase && (
Zuletzt gebucht am {formatDate(latestPurchase.purchasedAt)} via {latestPurchase.provider?.toUpperCase() ?? 'Checkout'}.
)}
) : (
Noch kein aktives Paket. Jetzt Paket auswählen und direkt Events planen.
)}
Event Credits insgesamt {tenant?.eventCreditsBalance ?? 0}
Credits werden bei neuen Events automatisch verbraucht. Zusätzliche Kontingente kannst du jederzeit buchen.
Dein Start in 5 Schritten

Folge den wichtigsten Schritten, um dein Event reibungslos aufzusetzen.

{checklistItems.map((item) => (

{item.title}

{item.description}

))}
Aktuelle Buchungen

Verfolge deine gebuchten Pakete und Erweiterungen.

{recentPurchases.length} Einträge
{recentPurchases.length === 0 ? (
Noch keine Buchungen sichtbar. Starte jetzt und sichere dir dein erstes Kontingent.
) : ( Paket Typ Anbieter Datum Preis {recentPurchases.map((purchase) => ( {purchase.packageName} {purchase.type ?? '—'} {purchase.provider ?? 'Checkout'} {formatDate(purchase.purchasedAt)} {renderPrice(purchase.price)} ))}
)}
Schnellzugriff

Alles Wichtige für den nächsten Schritt nur einen Klick entfernt.

{quickActions.map((action) => (

{action.label}

{action.description}

))}
); }