import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ArrowLeft, Camera, CheckCircle2, Circle, Loader2, MessageSquare, RefreshCw, Send, Sparkles, ThumbsDown, ThumbsUp, } 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 { Textarea } from '@/components/ui/textarea'; import { AdminLayout } from '../components/AdminLayout'; import { ADMIN_EVENT_PHOTOS_PATH, ADMIN_EVENT_TASKS_PATH, ADMIN_EVENT_VIEW_PATH, } from '../constants'; import { EventToolkit, EventToolkitTask, getEventToolkit, submitTenantFeedback, TenantPhoto, TenantEvent, } from '../api'; import { isAuthError } from '../auth/tokens'; interface ToolkitState { loading: boolean; error: string | null; data: EventToolkit | null; } export default function EventToolkitPage(): JSX.Element { const { slug } = useParams<{ slug?: string }>(); const navigate = useNavigate(); const { t, i18n } = useTranslation('management'); const [state, setState] = React.useState({ loading: true, error: null, data: null }); const [feedbackSentiment, setFeedbackSentiment] = React.useState<'positive' | 'neutral' | 'negative' | null>(null); const [feedbackMessage, setFeedbackMessage] = React.useState(''); const [feedbackSubmitting, setFeedbackSubmitting] = React.useState(false); const [feedbackSubmitted, setFeedbackSubmitted] = React.useState(false); const load = React.useCallback(async () => { if (!slug) { setState({ loading: false, error: t('toolkit.errors.missingSlug', 'Kein Event-Slug angegeben.'), data: null }); return; } setState((prev) => ({ ...prev, loading: true, error: null })); try { const toolkit = await getEventToolkit(slug); setState({ loading: false, error: null, data: toolkit }); } catch (error) { if (!isAuthError(error)) { setState({ loading: false, error: t('toolkit.errors.loadFailed', 'Toolkit konnte nicht geladen werden.'), data: null }); } } }, [slug, t]); React.useEffect(() => { void load(); }, [load]); const { data, loading } = state; const eventName = data?.event ? resolveEventName(data.event.name, i18n.language) : ''; const actions = (
); return ( {state.error && ( {t('toolkit.alerts.errorTitle', 'Fehler')} {state.error} )} {loading ? ( ) : data ? (
navigate(ADMIN_EVENT_PHOTOS_PATH(slug ?? ''))} /> navigate(ADMIN_EVENT_VIEW_PATH(slug ?? ''))} />
navigate(ADMIN_EVENT_TASKS_PATH(slug ?? ''))} />
{ if (!slug) return; setFeedbackSubmitting(true); try { await submitTenantFeedback({ category: 'event_toolkit', sentiment: feedbackSentiment ?? undefined, message: feedbackMessage ? feedbackMessage.trim() : undefined, event_slug: slug, }); setFeedbackSentiment(null); setFeedbackMessage(''); setFeedbackSubmitted(true); } catch (error) { if (!isAuthError(error)) { setState((prev) => ({ ...prev, error: t('toolkit.errors.feedbackFailed', 'Feedback konnte nicht gesendet werden.'), })); } } finally { setFeedbackSubmitting(false); } }} />
) : null}
); } function resolveEventName(name: TenantEvent['name'], locale?: string): string { if (typeof name === 'string') { return name; } if (name && typeof name === 'object') { if (locale && name[locale]) { return name[locale]; } const short = locale && locale.includes('-') ? locale.split('-')[0] : null; if (short && name[short]) { return name[short]; } return name.de ?? name.en ?? Object.values(name)[0] ?? 'Event'; } return 'Event'; } function AlertList({ alerts }: { alerts: string[] }) { const { t } = useTranslation('management'); if (!alerts.length) { return null; } const alertMap: Record = { no_tasks: t('toolkit.alerts.noTasks', 'Noch keine Tasks zugeordnet.'), no_invites: t('toolkit.alerts.noInvites', 'Es gibt keine aktiven QR-Einladungen.'), pending_photos: t('toolkit.alerts.pendingPhotos', 'Es warten Fotos auf Moderation.'), }; return (
{alerts.map((code) => ( {t('toolkit.alerts.attention', 'Achtung')} {alertMap[code] ?? code} ))}
); } function MetricsGrid({ metrics, }: { metrics: EventToolkit['metrics']; }) { const { t } = useTranslation('management'); const cards = [ { label: t('toolkit.metrics.uploadsTotal', 'Uploads gesamt'), value: metrics.uploads_total, }, { label: t('toolkit.metrics.uploads24h', 'Uploads (24h)'), value: metrics.uploads_24h, }, { label: t('toolkit.metrics.pendingPhotos', 'Unmoderierte Fotos'), value: metrics.pending_photos, }, { label: t('toolkit.metrics.activeInvites', 'Aktive Einladungen'), value: metrics.active_invites, }, { label: t('toolkit.metrics.engagementMode', 'Modus'), value: metrics.engagement_mode === 'photo_only' ? t('toolkit.metrics.modePhotoOnly', 'Foto-Modus') : t('toolkit.metrics.modeTasks', 'Aufgaben'), }, ]; return (
{cards.map((card) => (

{card.label}

{card.value}

))}
); } function PendingPhotosCard({ photos, navigateToModeration, }: { photos: TenantPhoto[]; navigateToModeration: () => void; }) { const { t } = useTranslation('management'); return (
{t('toolkit.pending.title', 'Wartende Fotos')} {t('toolkit.pending.subtitle', 'Moderationsempfehlung für neue Uploads.')}
{photos.length === 0 ? (

{t('toolkit.pending.empty', 'Aktuell warten keine Fotos auf Freigabe.')}

) : (
{photos.map((photo) => (
{photo.filename}

{photo.uploader_name ?? t('toolkit.pending.unknownUploader', 'Unbekannter Gast')}

{t('toolkit.pending.uploadedAt', 'Hochgeladen:')} {formatDateTime(photo.uploaded_at)}

{t('toolkit.pending.statusPending', 'Status: Prüfung ausstehend')}

))}
)}
); } function InviteSummary({ invites, navigateToEvent, }: { invites: EventToolkit['invites']; navigateToEvent: () => void; }) { const { t } = useTranslation('management'); return ( {t('toolkit.invites.title', 'QR-Einladungen')} {t('toolkit.invites.subtitle', 'Aktive Links und Layouts im Blick behalten.')}
{t('toolkit.invites.activeCount', { defaultValue: '{{count}} aktiv', count: invites.summary.active })} {t('toolkit.invites.totalCount', { defaultValue: '{{count}} gesamt', count: invites.summary.total })}
{invites.items.length === 0 ? (

{t('toolkit.invites.empty', 'Noch keine QR-Einladungen erstellt.')}

) : (
    {invites.items.map((invite) => (
  • {invite.label ?? invite.url}

    {invite.url}

    {invite.is_active ? t('toolkit.invites.statusActive', 'Aktiv') : t('toolkit.invites.statusInactive', 'Inaktiv')}

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

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

) : (
{tasks.items.map((task) => ( ))}
)}
); } function TaskRow({ task }: { task: EventToolkitTask }) { const { t } = useTranslation('management'); return (

{task.title}

{task.description ?

{task.description}

: null}
{task.is_completed ? : } {task.is_completed ? t('toolkit.tasks.completed', 'Erledigt') : t('toolkit.tasks.open', 'Offen')}
); } function RecentUploadsCard({ photos }: { photos: TenantPhoto[] }) { const { t } = useTranslation('management'); return ( {t('toolkit.recent.title', 'Neueste Uploads')} {t('toolkit.recent.subtitle', 'Ein Blick auf die letzten Fotos der Gäste.')} {photos.length === 0 ? (

{t('toolkit.recent.empty', 'Noch keine freigegebenen Fotos vorhanden.')}

) : (
{photos.map((photo) => ( {photo.filename} ))}
)}
); } function FeedbackCard({ submitting, submitted, sentiment, message, onSelectSentiment, onMessageChange, onSubmit, }: { submitting: boolean; submitted: boolean; sentiment: 'positive' | 'neutral' | 'negative' | null; message: string; onSelectSentiment: (value: 'positive' | 'neutral' | 'negative') => void; onMessageChange: (value: string) => void; onSubmit: () => Promise; }) { const { t } = useTranslation('management'); return ( {t('toolkit.feedback.title', 'Wie hilfreich ist dieses Toolkit?')} {t('toolkit.feedback.subtitle', 'Dein Feedback hilft uns, den Eventtag noch besser zu begleiten.')} {submitted ? ( {t('toolkit.feedback.thanksTitle', 'Danke!')} {t('toolkit.feedback.thanksDescription', 'Wir haben dein Feedback erhalten.')} ) : ( <>