import React from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { format } from 'date-fns'; import type { Locale } from 'date-fns'; import { de, enGB } from 'date-fns/locale'; import { Layers, Library, Loader2, Plus } 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, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { AdminLayout } from '../components/AdminLayout'; import { getTaskCollections, importTaskCollection, getEvents, PaginationMeta, TenantEvent, TenantTaskCollection, } from '../api'; import { buildEngagementTabPath } from '../constants'; import { isAuthError } from '../auth/tokens'; import toast from 'react-hot-toast'; const DEFAULT_PAGE_SIZE = 12; type ScopeFilter = 'all' | 'global' | 'tenant'; type CollectionsState = { items: TenantTaskCollection[]; meta: PaginationMeta | null; }; export type TaskCollectionsSectionProps = { embedded?: boolean; onNavigateToTasks?: () => void; }; export function TaskCollectionsSection({ embedded = false, onNavigateToTasks }: TaskCollectionsSectionProps) { const navigate = useNavigate(); const { t, i18n } = useTranslation('management'); const [collectionsState, setCollectionsState] = React.useState({ items: [], meta: null }); const [page, setPage] = React.useState(1); const [search, setSearch] = React.useState(''); const [scope, setScope] = React.useState('all'); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [dialogOpen, setDialogOpen] = React.useState(false); const [selectedCollection, setSelectedCollection] = React.useState(null); const [events, setEvents] = React.useState([]); const [selectedEventSlug, setSelectedEventSlug] = React.useState(''); const [importing, setImporting] = React.useState(false); const [eventsLoading, setEventsLoading] = React.useState(false); const [eventError, setEventError] = React.useState(null); const [reloadToken, setReloadToken] = React.useState(0); const scopeParam = React.useMemo(() => { if (scope === 'global') return 'global'; if (scope === 'tenant') return 'tenant'; return undefined; }, [scope]); React.useEffect(() => { let cancelled = false; async function loadCollections() { setLoading(true); setError(null); try { const result = await getTaskCollections({ page, per_page: DEFAULT_PAGE_SIZE, search: search.trim() || undefined, scope: scopeParam, }); if (cancelled) return; setCollectionsState({ items: result.data, meta: result.meta }); } catch (err) { if (cancelled) return; if (!isAuthError(err)) { setError(t('collections.notifications.error')); toast.error(t('collections.notifications.error')); } } finally { if (!cancelled) { setLoading(false); } } } void loadCollections(); return () => { cancelled = true; }; }, [page, search, scopeParam, reloadToken, t]); async function ensureEventsLoaded() { if (events.length > 0 || eventsLoading) { return; } setEventsLoading(true); setEventError(null); try { const result = await getEvents(); setEvents(result); } catch (err) { if (!isAuthError(err)) { setEventError(t('collections.errors.eventsLoad')); } } finally { setEventsLoading(false); } } function openImportDialog(collection: TenantTaskCollection) { setSelectedCollection(collection); setSelectedEventSlug(''); setDialogOpen(true); void ensureEventsLoaded(); } async function handleImport(event: React.FormEvent) { event.preventDefault(); if (!selectedCollection || !selectedEventSlug) { setEventError(t('collections.errors.selectEvent')); return; } setImporting(true); setEventError(null); try { await importTaskCollection(selectedCollection.id, selectedEventSlug); toast.success(t('collections.notifications.imported')); setDialogOpen(false); setReloadToken((token) => token + 1); } catch (err) { if (!isAuthError(err)) { setEventError(t('collections.notifications.error')); toast.error(t('collections.notifications.error')); } } finally { setImporting(false); } } const showEmpty = !loading && collectionsState.items.length === 0; const locale = i18n.language.startsWith('en') ? enGB : de; const title = t('collections.title') ?? 'Task Collections'; const subtitle = embedded ? t('collections.subtitle') ?? '' : t('collections.subtitle') ?? ''; const navigateToTasks = React.useCallback(() => { if (onNavigateToTasks) { onNavigateToTasks(); return; } navigate(buildEngagementTabPath('tasks')); }, [navigate, onNavigateToTasks]); return (
{error && ( {t('collections.notifications.error')} {error} )}
{title} {subtitle}
{ setPage(1); setSearch(event.target.value); }} />
{loading ? ( ) : showEmpty ? ( ) : (
{collectionsState.items.map((collection) => ( openImportDialog(collection)} onNavigateToTasks={navigateToTasks} /> ))}
)} {collectionsState.meta && collectionsState.meta.last_page > 1 ? (
{t('collections.pagination.page', { current: collectionsState.meta.current_page ?? 1, total: collectionsState.meta.last_page ?? 1, })}
) : null}
); } export default function TaskCollectionsPage() { const navigate = useNavigate(); const { t } = useTranslation('management'); return ( navigate(buildEngagementTabPath('tasks'))} /> ); } function TaskCollectionCard({ collection, locale, onImport, onNavigateToTasks, }: { collection: TenantTaskCollection; locale: Locale; onImport: () => void; onNavigateToTasks: () => void; }) { const { t } = useTranslation('management'); const updatedAt = collection.updated_at ? format(new Date(collection.updated_at), 'Pp', { locale }) : null; return ( {collection.name} {collection.description ? ( {collection.description} ) : null}
{collection.is_global ? t('collections.scope.global') : t('collections.scope.tenant')} {t('collections.labels.taskCount', { count: collection.tasks_count })} {collection.event_type ? ( {collection.event_type.name} ) : null}
{updatedAt ? (

{t('collections.labels.updated', { date: updatedAt })}

) : null}
); } function ImportCollectionDialog({ open, onOpenChange, collection, events, eventsLoading, selectedEventSlug, onSelectedEventChange, onSubmit, importing, error, locale, }: { open: boolean; onOpenChange: (open: boolean) => void; collection: TenantTaskCollection | null; events: TenantEvent[]; eventsLoading: boolean; selectedEventSlug: string; onSelectedEventChange: (slug: string) => void; onSubmit: (event: React.FormEvent) => void; importing: boolean; error: string | null; locale: Locale; }) { const { t } = useTranslation('management'); return ( {t('collections.dialogs.importTitle')}

{collection?.name ?? 'Unbekannte Sammlung'}

{error ?

{error}

: null}
); } function CollectionsSkeleton() { return (
{Array.from({ length: 6 }).map((_, index) => (
))}
); } function EmptyCollectionsState({ onCreate }: { onCreate: () => void }) { const { t } = useTranslation('management'); return (

{t('collections.empty.title')}

{t('collections.empty.description')}

); }