// @ts-nocheck import React from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { ArrowLeft, Layers, Loader2, PlusCircle, Search, Sparkles } from 'lucide-react'; import { useTranslation } from 'react-i18next'; 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 { Checkbox } from '@/components/ui/checkbox'; import { Switch } from '@/components/ui/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Input } from '@/components/ui/input'; import { AdminLayout } from '../components/AdminLayout'; import { assignTasksToEvent, getEvent, getEventTasks, getTasks, getTaskCollections, importTaskCollection, getEmotions, updateEvent, TenantEvent, TenantTask, TenantTaskCollection, TenantEmotion, } from '../api'; import { isAuthError } from '../auth/tokens'; import { ADMIN_EVENTS_PATH, ADMIN_EVENT_INVITES_PATH, buildEngagementTabPath } from '../constants'; import { extractBrandingPalette } from '../lib/branding'; import { filterEmotionsByEventType } from '../lib/emotions'; import { buildEventTabs } from '../lib/eventTabs'; export default function EventTasksPage() { const { t } = useTranslation(['management', 'dashboard']); const params = useParams<{ slug?: string }>(); const [searchParams] = useSearchParams(); const slug = params.slug ?? searchParams.get('slug') ?? null; const navigate = useNavigate(); const [event, setEvent] = React.useState(null); const [assignedTasks, setAssignedTasks] = React.useState([]); const [availableTasks, setAvailableTasks] = React.useState([]); const [selected, setSelected] = React.useState([]); const [loading, setLoading] = React.useState(true); const [saving, setSaving] = React.useState(false); const [modeSaving, setModeSaving] = React.useState(false); const [error, setError] = React.useState(null); const [tab, setTab] = React.useState<'tasks' | 'packs'>('tasks'); const [taskSearch, setTaskSearch] = React.useState(''); const [collections, setCollections] = React.useState([]); const [collectionsLoading, setCollectionsLoading] = React.useState(false); const [collectionsError, setCollectionsError] = React.useState(null); const [importingCollectionId, setImportingCollectionId] = React.useState(null); const [emotions, setEmotions] = React.useState([]); const [emotionsLoading, setEmotionsLoading] = React.useState(false); const [emotionsError, setEmotionsError] = React.useState(null); const hydrateTasks = React.useCallback(async (targetEvent: TenantEvent) => { try { const refreshed = await getEventTasks(targetEvent.id, 1); const assignedIds = new Set(refreshed.data.map((task) => task.id)); setAssignedTasks(refreshed.data); setAvailableTasks((prev) => prev.filter((task) => !assignedIds.has(task.id))); } catch (err) { if (!isAuthError(err)) { setError(t('management.tasks.errors.assign', 'Tasks konnten nicht geladen werden.')); } } }, [t]); const statusLabels = React.useMemo( () => ({ published: t('management.members.statuses.published', 'Veröffentlicht'), draft: t('management.members.statuses.draft', 'Entwurf'), }), [t] ); const palette = React.useMemo(() => extractBrandingPalette(event?.settings), [event?.settings]); const relevantEmotions = React.useMemo( () => filterEmotionsByEventType(emotions, event?.event_type_id ?? event?.event_type?.id ?? null), [emotions, event?.event_type_id, event?.event_type?.id], ); React.useEffect(() => { if (!slug) { setError(t('management.tasks.errors.missingSlug', 'Kein Event-Slug angegeben.')); setLoading(false); return; } let cancelled = false; (async () => { try { setLoading(true); const eventData = await getEvent(slug); const [eventTasksResponse, libraryTasks] = await Promise.all([ getEventTasks(eventData.id, 1), getTasks({ per_page: 50 }), ]); if (cancelled) return; setEvent(eventData); const assignedIds = new Set(eventTasksResponse.data.map((task) => task.id)); setAssignedTasks(eventTasksResponse.data); const eventTypeId = eventData.event_type_id ?? null; const filteredLibraryTasks = libraryTasks.data.filter((task) => { if (assignedIds.has(task.id)) { return false; } if (eventTypeId && task.event_type_id && task.event_type_id !== eventTypeId) { return false; } return true; }); setAvailableTasks(filteredLibraryTasks); setError(null); } catch (err) { if (!isAuthError(err)) { setError(t('management.tasks.errors.load', 'Event-Tasks konnten nicht geladen werden.')); } } finally { if (!cancelled) { setLoading(false); } } })(); return () => { cancelled = true; }; }, [slug, t]); async function handleAssign() { if (!event || selected.length === 0) return; setSaving(true); try { await assignTasksToEvent(event.id, selected); const refreshed = await getEventTasks(event.id, 1); const assignedIds = new Set(refreshed.data.map((task) => task.id)); setAssignedTasks(refreshed.data); setAvailableTasks((prev) => prev.filter((task) => !assignedIds.has(task.id))); setSelected([]); } catch (err) { if (!isAuthError(err)) { setError(t('management.tasks.errors.assign', 'Tasks konnten nicht zugewiesen werden.')); } } finally { setSaving(false); } } React.useEffect(() => { setSelected((current) => current.filter((taskId) => availableTasks.some((task) => task.id === taskId))); }, [availableTasks]); const filteredAssignedTasks = React.useMemo(() => { if (!taskSearch.trim()) { return assignedTasks; } const term = taskSearch.toLowerCase(); return assignedTasks.filter((task) => `${task.title ?? ''} ${task.description ?? ''}`.toLowerCase().includes(term)); }, [assignedTasks, taskSearch]); const eventTabs = React.useMemo(() => { if (!event) { return []; } const translateMenu = (key: string, fallback: string) => t(key, { defaultValue: fallback }); return buildEventTabs(event, translateMenu, { photos: event.photo_count ?? 0, tasks: assignedTasks.length, invites: event.active_invites_count ?? event.total_invites_count ?? 0, }); }, [event, assignedTasks.length, t]); React.useEffect(() => { if (!event?.event_type?.slug) { return; } let cancelled = false; setCollectionsLoading(true); setCollectionsError(null); getTaskCollections({ per_page: 6, event_type: event.event_type.slug }) .then((result) => { if (cancelled) return; setCollections(result.data); }) .catch((err) => { if (cancelled) return; if (!isAuthError(err)) { setCollectionsError(t('management.tasks.collections.error', 'Kollektionen konnten nicht geladen werden.')); } }) .finally(() => { if (!cancelled) { setCollectionsLoading(false); } }); return () => { cancelled = true; }; }, [event?.event_type?.slug, t]); React.useEffect(() => { let cancelled = false; setEmotionsLoading(true); setEmotionsError(null); getEmotions() .then((list) => { if (!cancelled) { setEmotions(list); } }) .catch((err) => { if (cancelled) { return; } if (!isAuthError(err)) { setEmotionsError(t('tasks.emotions.error', 'Emotionen konnten nicht geladen werden.')); } }) .finally(() => { if (!cancelled) { setEmotionsLoading(false); } }); return () => { cancelled = true; }; }, [t]); const handleImportCollection = React.useCallback(async (collection: TenantTaskCollection) => { if (!slug || !event) { return; } setImportingCollectionId(collection.id); try { await importTaskCollection(collection.id, slug); toast.success( t('management.tasks.collections.imported', { defaultValue: 'Mission Pack "{{name}}" importiert.', name: collection.name, }), ); await hydrateTasks(event); } catch (err) { if (!isAuthError(err)) { toast.error(t('management.tasks.collections.importFailed', 'Mission Pack konnte nicht importiert werden.')); } } finally { setImportingCollectionId(null); } }, [event, hydrateTasks, slug, t]); const isPhotoOnlyMode = event?.engagement_mode === 'photo_only'; async function handleModeChange(checked: boolean) { if (!event || !slug) return; setModeSaving(true); setError(null); try { const nextMode = checked ? 'photo_only' : 'tasks'; const updated = await updateEvent(slug, { settings: { engagement_mode: nextMode, }, }); setEvent(updated); } catch (err) { if (!isAuthError(err)) { setError( checked ? t('management.tasks.errors.photoOnlyEnable', 'Foto-Modus konnte nicht aktiviert werden.') : t('management.tasks.errors.photoOnlyDisable', 'Foto-Modus konnte nicht deaktiviert werden.'), ); } } finally { setModeSaving(false); } } const actions = ( ); return ( {error && ( {t('dashboard.alerts.errorTitle', 'Fehler')} {error} )} {loading ? ( ) : !event ? ( {t('management.tasks.alerts.notFoundTitle', 'Event nicht gefunden')} {t('management.tasks.alerts.notFoundDescription', 'Bitte kehre zur Eventliste zurück.')} ) : ( <> setTab(value as 'tasks' | 'packs')} className="space-y-6"> {t('management.tasks.tabs.tasks', 'Aufgaben')} {t('management.tasks.tabs.packs', 'Mission Packs')} {renderName(event.name, t)} {t('management.tasks.eventStatus', { status: statusLabels[event.status as keyof typeof statusLabels] ?? event.status, })}

{t('management.tasks.modes.title', 'Aufgaben & Foto-Modus')}

{isPhotoOnlyMode ? t( 'management.tasks.modes.photoOnlyHint', 'Der Foto-Modus ist aktiv. Gäste können Fotos hochladen, sehen aber keine Aufgaben.', ) : t( 'management.tasks.modes.tasksHint', 'Aufgaben sind aktiv. Gäste sehen Mission Cards in der App.', )}

{isPhotoOnlyMode ? t('management.tasks.modes.photoOnly', 'Foto-Modus') : t('management.tasks.modes.tasks', 'Aufgaben aktiv')}
{modeSaving ? (
{t('management.tasks.modes.updating', 'Einstellung wird gespeichert ...')}
) : null}

{t('management.tasks.sections.assigned.title', 'Zugeordnete Tasks')}

setTaskSearch(event.target.value)} placeholder={t('management.tasks.sections.assigned.search', 'Aufgaben suchen...')} className="h-8 border-0 bg-transparent text-sm focus-visible:ring-0" />
{filteredAssignedTasks.length === 0 ? ( ) : (
{filteredAssignedTasks.map((task) => ( ))}
)}

{t('management.tasks.sections.library.title', 'Tasks aus Bibliothek hinzufügen')}

{availableTasks.length === 0 ? ( ) : ( availableTasks.map((task) => ( )) )}
{ if (!slug) return; navigate(`${ADMIN_EVENT_INVITES_PATH(slug)}?tab=layout`); }} onOpenEmotions={() => navigate(buildEngagementTabPath('emotions'))} onOpenCollections={() => navigate(buildEngagementTabPath('collections'))} />
navigate(buildEngagementTabPath('collections'))} />
)}
); } function EmptyState({ message }: { message: string }) { return (

{message}

); } function TaskSkeleton() { return (
{Array.from({ length: 2 }).map((_, index) => (
))}
); } function AssignedTaskRow({ task }: { task: TenantTask }) { const { t } = useTranslation('management'); return (

{task.title}

{mapPriority(task.priority, (key, fallback) => t(key, { defaultValue: fallback }))}
{task.description &&

{task.description}

}
); } function MissionPackGrid({ collections, loading, error, onImport, importingId, onViewAll, }: { collections: TenantTaskCollection[]; loading: boolean; error: string | null; onImport: (collection: TenantTaskCollection) => void; importingId: number | null; onViewAll: () => void; }) { const { t } = useTranslation('management'); return (
{t('management.tasks.collections.title', 'Mission Packs')} {t('management.tasks.collections.subtitle', 'Importiere Aufgaben-Kollektionen, die zu deinem Event passen.')}
{error ? ( {t('management.tasks.collections.errorTitle', 'Kollektionen nicht verfügbar')} {error} ) : null} {loading ? (
{Array.from({ length: 3 }).map((_, index) => (
))}
) : collections.length === 0 ? ( ) : (
{collections.map((collection) => (

{collection.name}

{collection.description ? (

{collection.description}

) : null} {t('management.tasks.collections.tasksCount', { defaultValue: '{{count}} Aufgaben', count: collection.tasks_count, })}
{collection.event_type?.name ?? t('management.tasks.collections.genericType', 'Allgemein')} {collection.is_global ? t('management.tasks.collections.global', 'Global') : t('management.tasks.collections.custom', 'Custom')}
))}
)} ); } type BrandingStoryPanelProps = { event: TenantEvent; palette: ReturnType; emotions: TenantEmotion[]; emotionsLoading: boolean; emotionsError: string | null; collections: TenantTaskCollection[]; onOpenBranding: () => void; onOpenEmotions: () => void; onOpenCollections: () => void; }; function BrandingStoryPanel({ event, palette, emotions, emotionsLoading, emotionsError, collections, onOpenBranding, onOpenEmotions, onOpenCollections, }: BrandingStoryPanelProps) { const { t } = useTranslation('management'); const fallbackColors = palette.colors.length ? palette.colors : ['#f472b6', '#fde68a', '#312e81']; const spotlightEmotions = emotions.slice(0, 4); const recommendedCollections = React.useMemo(() => collections.slice(0, 2), [collections]); return ( {t('tasks.story.title', 'Branding & Story')} {t('tasks.story.description', 'Verbinde Farben, Emotionen und Mission Packs für ein stimmiges Gäste-Erlebnis.')}

{t('events.branding.brandingTitle', 'Branding')}

{palette.font ?? t('events.branding.brandingFallback', 'Aktuelle Auswahl')}

{t('events.branding.brandingCopy', 'Passe Farben & Schriftarten im Layout-Editor an.')}

{fallbackColors.slice(0, 4).map((color) => ( ))}

{t('tasks.story.emotionsTitle', 'Emotionen')}

{t('tasks.story.emotionsCount', { defaultValue: '{{count}} aktiviert', count: emotions.length })}
{emotionsLoading ? (
) : emotionsError ? (

{emotionsError}

) : spotlightEmotions.length ? (
{spotlightEmotions.map((emotion) => ( {emotion.icon ? {emotion.icon} : null} {emotion.name} ))}
) : (

{t('tasks.story.emotionsEmpty', 'Aktiviere Emotionen, um Aufgaben zu kategorisieren.')}

)}

{t('tasks.story.collectionsTitle', 'Mission Packs')}

{recommendedCollections.length ? (
{recommendedCollections.map((collection) => (

{collection.name}

{collection.event_type?.name ? (

{collection.event_type.name}

) : null}
{t('tasks.story.collectionsCount', { defaultValue: '{{count}} Aufgaben', count: collection.tasks_count })}
))}
) : (

{t('tasks.story.collectionsEmpty', 'Noch keine empfohlenen Mission Packs.')}

)}
); } function SummaryPill({ label, value }: { label: string; value: string | number }) { return (

{label}

{value}

); } function mapPriority(priority: TenantTask['priority'], translate: (key: string, defaultValue: string) => string): string { switch (priority) { case 'low': return translate('management.tasks.priorities.low', 'Niedrig'); case 'high': return translate('management.tasks.priorities.high', 'Hoch'); case 'urgent': return translate('management.tasks.priorities.urgent', 'Dringend'); default: return translate('management.tasks.priorities.medium', 'Mittel'); } } function renderName(name: TenantEvent['name'], translate: (key: string, defaultValue: string) => string): string { if (typeof name === 'string') { return name; } return name?.de ?? name?.en ?? Object.values(name ?? {})[0] ?? translate('management.members.events.untitled', 'Unbenanntes Event'); }