import React from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { CalendarDays, Camera, 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, 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[]; activePackage: TenantPackageSummary | null; loading: boolean; errorKey: string | null; } export default function DashboardPage() { const navigate = useNavigate(); const location = useLocation(); const { user } = useAuth(); const { progress, markStep } = useOnboardingProgress(); const { t, i18n } = useTranslation('dashboard', { keyPrefix: 'dashboard' }); const { t: tc } = useTranslation('common'); const translate = React.useCallback( (key: string, options?: Record) => { const value = t(key, options); if (value === `dashboard.${key}`) { const fallback = i18n.t(`dashboard:${key}`, options); return fallback === `dashboard:${key}` ? value : fallback; } return value; }, [t, i18n], ); const [state, setState] = React.useState({ summary: null, events: [], activePackage: null, loading: true, errorKey: null, }); React.useEffect(() => { let cancelled = false; (async () => { try { const [summary, events, packages] = await Promise.all([ getDashboardSummary().catch(() => null), getEvents().catch(() => [] as TenantEvent[]), getTenantPackagesOverview().catch(() => ({ packages: [], activePackage: null })), ]); if (cancelled) { return; } const fallbackSummary = buildSummaryFallback(events, packages.activePackage); setState({ summary: summary ?? fallbackSummary, events, activePackage: packages.activePackage, loading: false, errorKey: null, }); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, errorKey: 'loadFailed', loading: false, })); } } })(); return () => { cancelled = true; }; }, []); const { summary, events, activePackage, loading, errorKey } = 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 greetingName = user?.name ?? translate('welcome.fallbackName'); const greetingTitle = translate('welcome.greeting', { name: greetingName }); const subtitle = translate('welcome.subtitle'); const errorMessage = errorKey ? translate(`errors.${errorKey}`) : null; const dateLocale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'; const upcomingEvents = getUpcomingEvents(events); const publishedEvents = events.filter((event) => event.status === 'published'); const actions = ( <> {events.length === 0 && ( )} ); return ( {errorMessage && ( {t('dashboard.alerts.errorTitle')} {errorMessage} )} {loading ? ( ) : ( <> {events.length === 0 && ( {translate('welcomeCard.title')} {translate('welcomeCard.summary')}

{translate('welcomeCard.body1')}

{translate('welcomeCard.body2')}

)}
{translate('overview.title')} {translate('overview.description')}
{activePackage?.package_name ?? translate('overview.noPackage')}
} /> } /> } /> {activePackage ? ( } /> ) : null}
{translate('quickActions.title')} {translate('quickActions.description')}
} label={translate('quickActions.createEvent.label')} description={translate('quickActions.createEvent.description')} onClick={() => navigate(ADMIN_EVENT_CREATE_PATH)} /> } label={translate('quickActions.moderatePhotos.label')} description={translate('quickActions.moderatePhotos.description')} onClick={() => navigate(ADMIN_EVENTS_PATH)} /> } label={translate('quickActions.organiseTasks.label')} description={translate('quickActions.organiseTasks.description')} onClick={() => navigate(ADMIN_TASKS_PATH)} /> } label={translate('quickActions.managePackages.label')} description={translate('quickActions.managePackages.description')} onClick={() => navigate(ADMIN_BILLING_PATH)} />
{translate('upcoming.title')} {translate('upcoming.description')}
{upcomingEvents.length === 0 ? ( navigate(adminPath('/events/new'))} /> ) : ( upcomingEvents.map((event) => ( navigate(ADMIN_EVENT_VIEW_PATH(event.slug))} locale={dateLocale} labels={{ live: translate('upcoming.status.live'), planning: translate('upcoming.status.planning'), open: tc('actions.open'), noDate: translate('upcoming.status.noDate'), }} /> )) )}
)}
); } function buildSummaryFallback( events: TenantEvent[], 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, 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, locale, labels, }: { event: TenantEvent; onView: () => void; locale: string; labels: { live: string; planning: string; open: string; noDate: string; }; }) { const date = event.event_date ? new Date(event.event_date) : null; const formattedDate = date ? date.toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' }) : labels.noDate; return (
{formattedDate}
{event.status === 'published' ? labels.live : labels.planning}
); } 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) => (
))}
))}
); }