import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { AlertTriangle, ArrowLeft, Camera, CheckCircle2, ChevronRight, Circle, Download, Loader2, MessageSquare, Printer, QrCode, RefreshCw, Smile, Sparkles, Users, } from 'lucide-react'; import toast from 'react-hot-toast'; 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 { EventToolkit, EventToolkitTask, TenantEvent, TenantPhoto, EventStats, getEvent, getEventStats, getEventToolkit, toggleEvent, submitTenantFeedback, } from '../api'; import { buildLimitWarnings } from '../lib/limitWarnings'; import { getApiErrorMessage } from '../lib/apiError'; import { isAuthError } from '../auth/tokens'; import { ADMIN_EVENTS_PATH, ADMIN_EVENT_EDIT_PATH, ADMIN_EVENT_INVITES_PATH, ADMIN_EVENT_MEMBERS_PATH, ADMIN_EVENT_PHOTOS_PATH, ADMIN_EVENT_TASKS_PATH, } from '../constants'; type EventDetailPageProps = { mode?: 'detail' | 'toolkit'; }; type ToolkitState = { data: EventToolkit | null; loading: boolean; error: string | null; }; type WorkspaceState = { event: TenantEvent | null; stats: EventStats | null; loading: boolean; busy: boolean; error: string | null; }; export default function EventDetailPage({ mode = 'detail' }: EventDetailPageProps) { const { slug: slugParam } = useParams<{ slug?: string }>(); const navigate = useNavigate(); const { t } = useTranslation('management'); const { t: tCommon } = useTranslation('common'); const slug = slugParam ?? null; const [state, setState] = React.useState({ event: null, stats: null, loading: true, busy: false, error: null, }); const [toolkit, setToolkit] = React.useState({ data: null, loading: true, error: null }); const load = React.useCallback(async () => { if (!slug) { setState({ event: null, stats: null, loading: false, busy: false, error: t('events.errors.missingSlug', 'Kein Event ausgewählt.') }); setToolkit({ data: null, loading: false, error: null }); return; } setState((prev) => ({ ...prev, loading: true, error: null })); setToolkit((prev) => ({ ...prev, loading: true, error: null })); try { const [eventData, statsData] = await Promise.all([getEvent(slug), getEventStats(slug)]); setState((prev) => ({ ...prev, event: eventData, stats: statsData, loading: false })); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, error: getApiErrorMessage(error, t('events.errors.loadFailed', 'Event konnte nicht geladen werden.')), loading: false, })); } } try { const toolkitData = await getEventToolkit(slug); setToolkit({ data: toolkitData, loading: false, error: null }); } catch (error) { if (!isAuthError(error)) { setToolkit({ data: null, loading: false, error: getApiErrorMessage(error, t('toolkit.errors.loadFailed', 'Toolkit-Daten konnten nicht geladen werden.')), }); } } }, [slug, t]); React.useEffect(() => { void load(); }, [load]); async function handleToggle(): Promise { if (!slug) { return; } setState((prev) => ({ ...prev, busy: true, error: null })); try { const updated = await toggleEvent(slug); setState((prev) => ({ ...prev, busy: false, event: updated, stats: prev.stats ? { ...prev.stats, status: updated.status, is_active: Boolean(updated.is_active), } : prev.stats, })); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, busy: false, error: getApiErrorMessage(error, t('events.errors.toggleFailed', 'Status konnte nicht angepasst werden.')), })); } else { setState((prev) => ({ ...prev, busy: false })); } } } const { event, stats, loading, busy, error } = state; const toolkitData = toolkit.data; const eventName = event ? resolveName(event.name) : t('events.placeholders.untitled', 'Unbenanntes Event'); const subtitle = mode === 'toolkit' ? t('events.workspace.toolkitSubtitle', 'Moderation, Aufgaben und Einladungen für deinen Eventtag bündeln.') : t('events.workspace.detailSubtitle', 'Behalte Status, Aufgaben und Einladungen deines Events im Blick.'); const actions = (
{event && ( <> )}
); if (!slug) { return ( {t('events.errors.notFoundBody', 'Ohne gültige Kennung können wir keine Daten laden. Kehre zur Eventliste zurück und wähle dort ein Event aus.')} ); } const limitWarnings = React.useMemo( () => (event?.limits ? buildLimitWarnings(event.limits, (key, options) => tCommon(`limits.${key}`, options)) : []), [event?.limits, tCommon], ); const shownWarningToasts = React.useRef>(new Set()); React.useEffect(() => { limitWarnings.forEach((warning) => { const id = `${warning.id}-${warning.message}`; if (shownWarningToasts.current.has(id)) { return; } shownWarningToasts.current.add(id); toast(warning.message, { icon: warning.tone === 'danger' ? '🚨' : '⚠️', id, }); }); }, [limitWarnings]); return ( {error && ( {t('events.alerts.failedTitle', 'Aktion fehlgeschlagen')} {error} )} {limitWarnings.length > 0 && (
{limitWarnings.map((warning) => ( {warning.message} ))}
)} {toolkit.error && ( {toolkit.error} )} {loading ? ( ) : event ? (
{(toolkitData?.alerts?.length ?? 0) > 0 && }
navigate(ADMIN_EVENT_TASKS_PATH(event.slug))} /> navigate(`${ADMIN_EVENT_INVITES_PATH(event.slug)}?tab=layout`)} />
navigate(ADMIN_EVENT_PHOTOS_PATH(event.slug))} />
) : ( {t('events.errors.notFoundBody', 'Ohne gültige Kennung können wir keine Daten laden. Kehre zur Eventliste zurück und wähle dort ein Event aus.')} )}
); } function resolveName(name: TenantEvent['name']): string { if (typeof name === 'string' && name.trim().length > 0) { return name.trim(); } if (name && typeof name === 'object') { return name.de ?? name.en ?? Object.values(name)[0] ?? 'Event'; } return 'Event'; } function StatusCard({ event, stats, busy, onToggle }: { event: TenantEvent; stats: EventStats | null; busy: boolean; onToggle: () => void }) { const { t } = useTranslation('management'); const statusLabel = event.status === 'published' ? t('events.status.published', 'Veröffentlicht') : event.status === 'draft' ? t('events.status.draft', 'Entwurf') : t('events.status.archived', 'Archiviert'); return ( {t('events.workspace.sections.statusTitle', 'Eventstatus & Sichtbarkeit')} {t('events.workspace.sections.statusSubtitle', 'Aktiviere dein Event für Gäste oder verstecke es vorübergehend.')} } label={t('events.workspace.fields.status', 'Status')} value={statusLabel} /> } label={t('events.workspace.fields.active', 'Aktiv für Gäste')} value={event.is_active ? t('events.workspace.activeYes', 'Ja') : t('events.workspace.activeNo', 'Nein')} /> } label={t('events.workspace.fields.date', 'Eventdatum')} value={formatDate(event.event_date)} /> } label={t('events.workspace.fields.eventType', 'Event-Typ')} value={resolveEventType(event)} /> {stats && (

{t('events.workspace.fields.insights', 'Letzte Aktivität')}

{t('events.workspace.fields.uploadsTotal', { defaultValue: '{{count}} Uploads gesamt', count: stats.uploads_total ?? stats.total ?? 0, })} {' · '} {t('events.workspace.fields.uploadsToday', { defaultValue: '{{count}} Uploads (24h)', count: stats.uploads_24h ?? stats.recent_uploads ?? 0, })}

{t('events.workspace.fields.likesTotal', { defaultValue: '{{count}} Likes vergeben', count: stats.likes_total ?? stats.likes ?? 0, })}

)}
); } function QuickActionsCard({ slug, busy, onToggle, navigate }: { slug: string; busy: boolean; onToggle: () => void | Promise; navigate: ReturnType }) { const { t } = useTranslation('management'); const actions = [ { icon: , label: t('events.quickActions.moderate', 'Fotos moderieren'), onClick: () => navigate(ADMIN_EVENT_PHOTOS_PATH(slug)), }, { icon: , label: t('events.quickActions.tasks', 'Aufgaben bearbeiten'), onClick: () => navigate(ADMIN_EVENT_TASKS_PATH(slug)), }, { icon: , label: t('events.quickActions.invites', 'Layouts & QR verwalten'), onClick: () => navigate(`${ADMIN_EVENT_INVITES_PATH(slug)}?tab=layout`), }, { icon: , label: t('events.quickActions.roles', 'Team & Rollen anpassen'), onClick: () => navigate(ADMIN_EVENT_MEMBERS_PATH(slug)), }, { icon: , label: t('events.quickActions.print', 'Layouts als PDF drucken'), onClick: () => navigate(`${ADMIN_EVENT_INVITES_PATH(slug)}?tab=export`), }, { icon: , label: t('events.quickActions.toggle', 'Status ändern'), onClick: () => { void onToggle(); }, disabled: busy, }, ]; return ( {t('events.quickActions.title', 'Schnellaktionen')} {t('events.quickActions.subtitle', 'Nutze die wichtigsten Schritte vor und während deines Events.')} {actions.map((action, index) => ( ))} ); } function MetricsGrid({ metrics, stats }: { metrics: EventToolkit['metrics'] | null | undefined; stats: EventStats | null }) { const { t } = useTranslation('management'); const cards = [ { icon: , label: t('events.metrics.uploadsTotal', 'Uploads gesamt'), value: metrics?.uploads_total ?? stats?.uploads_total ?? 0, }, { icon: , label: t('events.metrics.uploads24h', 'Uploads (24h)'), value: metrics?.uploads_24h ?? stats?.uploads_24h ?? 0, }, { icon: , label: t('events.metrics.pending', 'Fotos in Moderation'), value: metrics?.pending_photos ?? stats?.pending_photos ?? 0, }, { icon: , label: t('events.metrics.activeInvites', 'Aktive Einladungen'), value: metrics?.active_invites ?? 0, }, ]; return (
{cards.map((card, index) => ( {card.icon}

{card.label}

{card.value}

))}
); } function InviteSummary({ invites, navigateToInvites }: { invites: EventToolkit['invites'] | undefined; navigateToInvites: () => void }) { const { t } = useTranslation('management'); return ( {t('events.invites.title', 'QR-Einladungen')} {t('events.invites.subtitle', 'Behält aktive Einladungen und Layouts im Blick.')}
{t('events.invites.activeCount', { defaultValue: '{{count}} aktiv', count: invites?.summary.active ?? 0 })} {t('events.invites.totalCount', { defaultValue: '{{count}} gesamt', count: invites?.summary.total ?? 0 })}
{invites?.items?.length ? (
    {invites.items.slice(0, 3).map((invite) => (
  • {invite.label ?? invite.url}

    {invite.url}

  • ))}
) : (

{t('events.invites.empty', 'Noch keine Einladungen erstellt.')}

)}
); } function TaskOverviewCard({ tasks, navigateToTasks }: { tasks: EventToolkit['tasks'] | undefined; navigateToTasks: () => void }) { const { t } = useTranslation('management'); return (
{t('events.tasks.title', 'Aktive Aufgaben')} {t('events.tasks.subtitle', 'Motiviere Gäste mit klaren Aufgaben & Highlights.')}
{t('events.tasks.summary', { defaultValue: '{{completed}} von {{total}} erledigt', completed: tasks?.summary.completed ?? 0, total: tasks?.summary.total ?? 0, })}
{tasks?.items?.length ? (
{tasks.items.slice(0, 4).map((task) => ( ))}
) : (

{t('events.tasks.empty', 'Noch keine Aufgaben zugewiesen.')}

)}
); } function TaskRow({ task }: { task: EventToolkitTask }) { return (

{task.title}

{task.description ?

{task.description}

: null}
{task.is_completed ? 'Erledigt' : 'Offen'}
); } function PendingPhotosCard({ photos, navigateToModeration }: { photos: TenantPhoto[]; navigateToModeration: () => void }) { const { t } = useTranslation('management'); return (
{t('events.photos.pendingTitle', 'Fotos in Moderation')} {t('events.photos.pendingSubtitle', 'Schnell prüfen, bevor Gäste live gehen.')}
{t('events.photos.pendingCount', { defaultValue: '{{count}} Fotos offen', count: photos.length })}
{photos.length ? (
{photos.slice(0, 6).map((photo) => ( {photo.caption ))}
) : (

{t('events.photos.pendingEmpty', 'Aktuell warten keine Fotos auf Freigabe.')}

)}
); } function RecentUploadsCard({ photos }: { photos: TenantPhoto[] }) { const { t } = useTranslation('management'); return ( {t('events.photos.recentTitle', 'Neueste Uploads')} {t('events.photos.recentSubtitle', 'Halte Ausschau nach Highlight-Momenten der Gäste.')} {photos.length ? (
{photos.slice(0, 6).map((photo) => ( {photo.caption ))}
) : (

{t('events.photos.recentEmpty', 'Noch keine neuen Uploads.')}

)}
); } function FeedbackCard({ slug }: { slug: string }) { const { t } = useTranslation('management'); const [sentiment, setSentiment] = React.useState<'positive' | 'neutral' | 'negative' | null>(null); const [message, setMessage] = React.useState(''); const [busy, setBusy] = React.useState(false); const [submitted, setSubmitted] = React.useState(false); const [error, setError] = React.useState(null); const copy = { positive: t('events.feedback.positive', 'Super Lauf!'), neutral: t('events.feedback.neutral', 'Läuft'), negative: t('events.feedback.negative', 'Braucht Support'), }; return ( {t('events.feedback.title', 'Wie läuft dein Event?')} {t('events.feedback.subtitle', 'Feedback hilft uns, neue Features zu priorisieren.')} {error && ( {t('events.feedback.errorTitle', 'Feedback konnte nicht gesendet werden.')} {error} )}
{(Object.keys(copy) as Array<'positive' | 'neutral' | 'negative'>).map((key) => ( ))}