import React from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { CalendarDays, Camera, CreditCard, Sparkles, Users, Plus, Settings } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { AdminLayout } from '../components/AdminLayout'; import { DashboardSummary, getCreditBalance, getDashboardSummary, getEvents, getTenantPackagesOverview, TenantEvent, TenantPackageSummary, } from '../api'; import { isAuthError } from '../auth/tokens'; import { useAuth } from '../auth/context'; import { adminPath, ADMIN_EVENT_VIEW_PATH, ADMIN_EVENTS_PATH, ADMIN_TASKS_PATH, ADMIN_BILLING_PATH, ADMIN_SETTINGS_PATH, ADMIN_WELCOME_BASE_PATH, ADMIN_EVENT_CREATE_PATH, } from '../constants'; import { useOnboardingProgress } from '../onboarding'; interface DashboardState { summary: DashboardSummary | null; events: TenantEvent[]; credits: number; activePackage: TenantPackageSummary | null; loading: boolean; error: string | null; } export default function DashboardPage() { const navigate = useNavigate(); const location = useLocation(); const { user } = useAuth(); const { progress, markStep } = useOnboardingProgress(); const [state, setState] = React.useState({ summary: null, events: [], credits: 0, activePackage: null, loading: true, error: null, }); React.useEffect(() => { let cancelled = false; (async () => { try { const [summary, events, credits, packages] = await Promise.all([ getDashboardSummary().catch(() => null), getEvents().catch(() => [] as TenantEvent[]), getCreditBalance().catch(() => ({ balance: 0 })), getTenantPackagesOverview().catch(() => ({ packages: [], activePackage: null })), ]); if (cancelled) { return; } const fallbackSummary = buildSummaryFallback(events, credits.balance ?? 0, packages.activePackage); setState({ summary: summary ?? fallbackSummary, events, credits: credits.balance ?? 0, activePackage: packages.activePackage, loading: false, error: null, }); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, error: 'Dashboard konnte nicht geladen werden.', loading: false, })); } } })(); return () => { cancelled = true; }; }, []); const { summary, events, credits, activePackage, loading, error } = state; React.useEffect(() => { if (loading) { return; } if (!progress.eventCreated && events.length === 0 && !location.pathname.startsWith(ADMIN_WELCOME_BASE_PATH)) { navigate(ADMIN_WELCOME_BASE_PATH, { replace: true }); return; } if (events.length > 0 && !progress.eventCreated) { markStep({ eventCreated: true }); } }, [loading, events.length, progress.eventCreated, navigate, location.pathname, markStep]); const upcomingEvents = getUpcomingEvents(events); const publishedEvents = events.filter((event) => event.status === 'published'); const actions = ( <> {events.length === 0 && ( )} ); return ( {error && ( Fehler {error} )} {loading ? ( ) : ( <> {events.length === 0 && ( Starte mit der Welcome Journey Lerne die Storytelling-Elemente kennen, wähle dein Paket und erstelle dein erstes Event mit geführten Schritten.

Wir begleiten dich durch Pakete, Aufgaben und Galerie-Konfiguration, damit dein Event glänzt.

Du kannst jederzeit zur Welcome Journey zurückkehren, auch wenn bereits Events laufen.

)}
Kurzer Ueberblick Wichtigste Kennzahlen deines Tenants auf einen Blick.
{activePackage?.package_name ?? 'Kein aktives Package'}
} /> } /> } /> } />
Schnellaktionen Starte durch mit den wichtigsten Aktionen.
} label="Event erstellen" description="Plane dein naechstes Highlight." onClick={() => navigate(ADMIN_EVENT_CREATE_PATH)} /> } label="Fotos moderieren" description="Pruefe neue Uploads." onClick={() => navigate(ADMIN_EVENTS_PATH)} /> } label="Tasks organisieren" description="Sorge fuer klare Verantwortungen." onClick={() => navigate(ADMIN_TASKS_PATH)} /> } label="Credits verwalten" description="Sieh dir Balance & Ledger an." onClick={() => navigate(ADMIN_BILLING_PATH)} />
Kommende Events Die naechsten Termine inklusive Status & Zugriff.
{upcomingEvents.length === 0 ? ( navigate(adminPath('/events/new'))} /> ) : ( upcomingEvents.map((event) => ( navigate(ADMIN_EVENT_VIEW_PATH(event.slug))} /> )) )}
)}
); } function buildSummaryFallback( events: TenantEvent[], balance: number, activePackage: TenantPackageSummary | null ): DashboardSummary { const activeEvents = events.filter((event) => Boolean(event.is_active || event.status === 'published')); const totalPhotos = events.reduce((sum, event) => sum + Number(event.photo_count ?? 0), 0); return { active_events: activeEvents.length, new_photos: totalPhotos, task_progress: 0, credit_balance: balance, upcoming_events: activeEvents.length, active_package: activePackage ? { name: activePackage.package_name, remaining_events: activePackage.remaining_events, expires_at: activePackage.expires_at, } : null, }; } function getUpcomingEvents(events: TenantEvent[]): TenantEvent[] { const now = new Date(); return events .filter((event) => { if (!event.event_date) return false; const date = new Date(event.event_date); return !Number.isNaN(date.getTime()) && date >= now; }) .sort((a, b) => { const dateA = a.event_date ? new Date(a.event_date).getTime() : 0; const dateB = b.event_date ? new Date(b.event_date).getTime() : 0; return dateA - dateB; }) .slice(0, 4); } function StatCard({ label, value, hint, icon, }: { label: string; value: string | number; hint?: string; icon: React.ReactNode; }) { return (
{label} {icon}
{value}
{hint &&

{hint}

}
); } function QuickAction({ icon, label, description, onClick, }: { icon: React.ReactNode; label: string; description: string; onClick: () => void; }) { return ( ); } function UpcomingEventRow({ event, onView }: { event: TenantEvent; onView: () => void }) { const date = event.event_date ? new Date(event.event_date) : null; const formattedDate = date ? date.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' }) : 'Kein Datum'; return (
{event.status === 'published' ? 'Live' : 'In Planung'}
); } function EmptyState({ message, ctaLabel, onCta }: { message: string; ctaLabel: string; onCta: () => void }) { return (

{message}

); } function DashboardSkeleton() { return (
{Array.from({ length: 3 }).map((_, index) => (
{Array.from({ length: 4 }).map((__ , cardIndex) => (
))}
))}
); } function renderName(name: TenantEvent['name']): string { if (typeof name === 'string') { return name; } return name?.de ?? name?.en ?? Object.values(name ?? {})[0] ?? 'Unbenanntes Event'; }