behoben: ohne aufgabe kann die kamera nicht gestartet werden (offensichtlich kein fehler mit browserzugriff auf kamera!)

platz zu begrenzt im aufnahmemodus - vollbildmodus möglich? Menü und Kopfleiste ausblenden?
	Bild aus eigener galerie auswählen - Upload schlägt fehl (zu groß? evtl fehlende Rechte - aber browser hat rechte auf bilder und dateien!)
	hochgeladene bilder tauchen in der galerie nicht beim filter "Meine Bilder" auf - fotos werden auch nicht gezählt in den stats und achievements zeigen keinen fortschriftt.
	geteilte fotos: ruft man den Link auf, bekommt man die meldung "Link abgelaufen"
	der im startbildschirm gewählte name mit Umlauten (Sören) ist nach erneutem aufruf der pwa ohne umlaut (Sren).
Aufgabenseite verbessert (Zwischenstand)
This commit is contained in:
Codex Agent
2025-12-04 11:58:07 +01:00
parent 899e742c38
commit c73a3163c0
15 changed files with 776 additions and 610 deletions

View File

@@ -70,7 +70,7 @@ export default function EventTasksPage() {
const [saving, setSaving] = React.useState(false);
const [modeSaving, setModeSaving] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const [tab, setTab] = React.useState<'tasks' | 'packs'>('tasks');
const [tab, setTab] = React.useState<'tasks' | 'packs' | 'emotions'>('packs');
const [taskSearch, setTaskSearch] = React.useState('');
const [debouncedTaskSearch, setDebouncedTaskSearch] = React.useState('');
const [difficultyFilter, setDifficultyFilter] = React.useState<TenantTask['difficulty'] | ''>('');
@@ -88,12 +88,14 @@ export default function EventTasksPage() {
const [newTaskEmotionId, setNewTaskEmotionId] = React.useState<number | null>(null);
const [newTaskDifficulty, setNewTaskDifficulty] = React.useState<TenantTask['difficulty'] | ''>('');
const [creatingTask, setCreatingTask] = React.useState(false);
const [quickAddOpen, setQuickAddOpen] = React.useState(false);
const [draggingId, setDraggingId] = React.useState<number | null>(null);
const [selectedAssignedIds, setSelectedAssignedIds] = React.useState<number[]>([]);
const [selectedAvailableIds, setSelectedAvailableIds] = React.useState<number[]>([]);
const [batchSaving, setBatchSaving] = React.useState(false);
const [inlineSavingId, setInlineSavingId] = React.useState<number | null>(null);
const [emotionFilterOpen, setEmotionFilterOpen] = React.useState(false);
const libraryRef = React.useRef<HTMLDivElement | null>(null);
React.useEffect(() => {
const handle = window.setTimeout(() => setDebouncedTaskSearch(taskSearch.trim().toLowerCase()), 180);
return () => window.clearTimeout(handle);
@@ -333,6 +335,7 @@ export default function EventTasksPage() {
setNewTaskDescription('');
setNewTaskEmotionId(null);
setNewTaskDifficulty('');
setQuickAddOpen(false);
await hydrateTasks(event);
} catch (err) {
if (!isAuthError(err)) {
@@ -424,14 +427,15 @@ export default function EventTasksPage() {
await importTaskCollection(collection.id, slug);
toast.success(
t('collections.imported', {
defaultValue: 'Mission Pack "{{name}}" importiert.',
defaultValue: 'Aufgaben-Set "{{name}}" importiert.',
name: collection.name,
}),
);
setTab('tasks');
await hydrateTasks(event);
} catch (err) {
if (!isAuthError(err)) {
toast.error(t('collections.importFailed', 'Mission Pack konnte nicht importiert werden.'));
toast.error(t('collections.importFailed', 'Aufgaben-Set konnte nicht importiert werden.'));
}
} finally {
setImportingCollectionId(null);
@@ -447,30 +451,17 @@ export default function EventTasksPage() {
return mode !== 'photo_only';
}, [event?.engagement_mode, event?.settings]);
const summaryBadges = !loading && event ? (
<div className="mb-4 flex flex-wrap gap-2">
<Badge className="flex items-center gap-2 rounded-full bg-slate-900 text-white">
<span className="text-xs uppercase tracking-wide text-white/80">
{t('summary.assigned', 'Zugeordnete Tasks')}
</span>
<span className="text-sm font-semibold">{assignedTasks.length}</span>
</Badge>
<Badge className="flex items-center gap-2 rounded-full bg-emerald-600/90 text-white">
<span className="text-xs uppercase tracking-wide text-white/80">
{t('summary.library', 'Bibliothek')}
</span>
<span className="text-sm font-semibold">{availableTasks.length}</span>
</Badge>
<Badge className="flex items-center gap-2 rounded-full bg-pink-500/90 text-white">
<span className="text-xs uppercase tracking-wide text-white/80">
{t('summary.mode', 'Aktiver Modus')}
</span>
<span className="text-sm font-semibold">
{tasksEnabled ? t('summary.tasksMode', 'Mission Cards') : t('summary.photoOnly', 'Nur Fotos')}
</span>
</Badge>
</div>
) : null;
const hasSelection = selectedAssignedIds.length > 0 || selectedAvailableIds.length > 0;
const tasksFirst = assignedTasks.length > 0;
const tabOrder: Array<'tasks' | 'packs' | 'emotions'> = tasksFirst ? ['tasks', 'packs', 'emotions'] : ['packs', 'tasks', 'emotions'];
const prevAssignedRef = React.useRef(assignedTasks.length);
React.useEffect(() => {
if (prevAssignedRef.current === 0 && assignedTasks.length > 0) {
setTab('tasks');
}
prevAssignedRef.current = assignedTasks.length;
}, [assignedTasks.length, setTab]);
async function handleModeChange(checked: boolean) {
if (!event || !slug) return;
@@ -658,8 +649,6 @@ export default function EventTasksPage() {
tabs={eventTabs}
currentTabKey="tasks"
>
{summaryBadges}
{error && (
<Alert variant="destructive">
<AlertTitle>{tDashboard('alerts.errorTitle', 'Fehler')}</AlertTitle>
@@ -676,17 +665,33 @@ export default function EventTasksPage() {
</Alert>
) : (
<>
<Tabs value={tab} onValueChange={(value) => setTab(value as 'tasks' | 'packs')} className="space-y-6">
<TabsList className="grid gap-2 rounded-2xl bg-slate-100/80 p-1 sm:grid-cols-2">
<TabsTrigger value="tasks">{t('tabs.tasks', 'Aufgaben')}</TabsTrigger>
<TabsTrigger value="packs">{t('tabs.packs', 'Mission Packs')}</TabsTrigger>
</TabsList>
<Tabs value={tab} onValueChange={(value) => setTab(value as 'tasks' | 'packs' | 'emotions')} className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-3">
<TabsList className="grid flex-1 gap-2 rounded-2xl bg-slate-100/80 p-1 sm:grid-cols-3">
{tabOrder.map((key) => (
<TabsTrigger key={key} value={key}>
{key === 'packs'
? t('tabs.packs', 'Vorlagen / Aufgaben-Bundles')
: key === 'tasks'
? t('tabs.tasks', 'Aufgaben')
: t('tabs.emotions', 'Emotionen')}
</TabsTrigger>
))}
</TabsList>
<Button
variant="outline"
className="border-emerald-200 text-emerald-700"
onClick={() => navigate(buildEngagementTabPath('tasks'))}
>
{t('library.open', 'Aufgaben-Bibliothek öffnen')}
</Button>
</div>
<TabsContent value="tasks" className="space-y-6">
<Card className="border-0 bg-white/85 shadow-xl shadow-pink-100/60">
<CardHeader>
<div className="mt-4 flex flex-col gap-4 rounded-2xl border border-slate-200 bg-white/70 p-4 text-sm text-slate-700">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<CardHeader className="space-y-4">
<div className="flex flex-col gap-4 rounded-2xl border border-slate-200 bg-white/70 p-4 text-sm text-slate-700">
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div className="space-y-2">
<p className="text-sm font-semibold text-slate-900">
{t('modes.title', 'Aufgaben & Foto-Modus')}
</p>
@@ -695,48 +700,55 @@ export default function EventTasksPage() {
? t('modes.tasksHint', 'Aufgaben sind aktiv. Gäste sehen Mission Cards in der App.')
: t('modes.photoOnlyHint', 'Der Foto-Modus ist aktiv. Gäste können Fotos hochladen, sehen aber keine Aufgaben.')}
</p>
<div className="flex flex-wrap gap-2">
<Badge className="rounded-full bg-slate-900 text-white">
{t('summary.assigned', 'Zugeordnete Tasks')} · {assignedTasks.length}
</Badge>
<Badge className="rounded-full bg-emerald-600/90 text-white">
{t('summary.library', 'Bibliothek')} · {availableTasks.length}
</Badge>
<Badge className="rounded-full bg-pink-500/90 text-white">
{t('summary.mode', 'Aktiver Modus')} ·{' '}
{tasksEnabled ? t('summary.tasksMode', 'Mission Cards') : t('summary.photoOnly', 'Nur Fotos')}
</Badge>
</div>
</div>
<div className="flex items-center gap-3">
<span className="text-xs uppercase tracking-wide text-slate-500">
{tasksEnabled ? t('modes.tasks', 'Aufgaben aktiv') : t('modes.photoOnly', 'Foto-Modus')}
</span>
<Switch
checked={tasksEnabled}
onCheckedChange={handleModeChange}
disabled={modeSaving}
aria-label={t('modes.switchLabel', 'Aufgaben aktivieren/deaktivieren')}
/>
<div className="flex flex-col items-start gap-2 lg:items-end">
<div className="flex items-center gap-3">
<span className="text-xs uppercase tracking-wide text-slate-500">
{tasksEnabled ? t('modes.tasks', 'Aufgaben aktiv') : t('modes.photoOnly', 'Foto-Modus')}
</span>
<Switch
checked={tasksEnabled}
onCheckedChange={handleModeChange}
disabled={modeSaving || (!tasksEnabled && assignedTasks.length === 0)}
aria-label={t('modes.switchLabel', 'Aufgaben aktivieren/deaktivieren')}
/>
</div>
{modeSaving ? (
<div className="flex items-center gap-2 text-xs text-slate-500">
<Loader2 className="h-3.5 w-3.5 animate-spin" />
{t('modes.updating', 'Einstellung wird gespeichert ...')}
</div>
) : null}
{!tasksEnabled && assignedTasks.length === 0 ? (
<p className="text-[11px] text-slate-500">
{t('modes.needTasks', 'Aktiviere Aufgaben, sobald mindestens eine Aufgabe zugewiesen ist.')}
</p>
) : null}
</div>
</div>
{modeSaving ? (
<div className="flex items-center gap-2 text-xs text-slate-500">
<Loader2 className="h-3.5 w-3.5 animate-spin" />
{t('modes.updating', 'Einstellung wird gespeichert ...')}
</div>
) : null}
<div className="flex flex-wrap gap-2">
<Button
className="bg-emerald-600 text-white hover:bg-emerald-700"
onClick={() => setQuickAddOpen(true)}
>
<PlusCircle className="mr-2 h-4 w-4" />
{t('actions.addCustom', 'Eigene Aufgabe hinzufügen')}
</Button>
</div>
</div>
</CardHeader>
<CardContent className="pb-0">
<Alert variant="default" className="rounded-2xl border border-dashed border-emerald-200 bg-emerald-50/60 text-xs text-slate-700">
<AlertTitle className="text-sm font-semibold text-slate-900">
{t('library.hintTitle', 'Weitere Vorlagen in der Aufgaben-Bibliothek')}
</AlertTitle>
<AlertDescription className="mt-1 flex flex-wrap items-center gap-2">
<span>
{t('library.hintCopy', 'Lege eigene Aufgaben, Emotionen oder Mission Packs zentral an und nutze sie in mehreren Events.')}
</span>
<Button
type="button"
variant="outline"
size="sm"
className="mt-1 rounded-full border-emerald-300 text-emerald-700 hover:bg-emerald-100"
onClick={() => navigate(buildEngagementTabPath('tasks'))}
>
{t('library.open', 'Aufgaben-Bibliothek öffnen')}
</Button>
</AlertDescription>
</Alert>
</CardContent>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
@@ -939,73 +951,17 @@ export default function EventTasksPage() {
</section>
<section className="space-y-3">
<h3 className="flex items-center gap-2 text-sm font-semibold text-slate-900">
<PlusCircle className="h-4 w-4 text-emerald-500" />
{t('sections.library.title', 'Tasks aus Bibliothek hinzufügen')}
</h3>
<div className="rounded-2xl border border-emerald-100 bg-emerald-50/60 p-3 shadow-inner">
<p className="text-xs font-semibold text-emerald-700">{t('sections.library.quickCreate', 'Schnell neue Aufgabe anlegen')}</p>
<div className="mt-2 grid gap-2">
<Input
value={newTaskTitle}
onChange={(e) => setNewTaskTitle(e.target.value)}
placeholder={t('sections.library.quickTitle', 'Titel der Aufgabe')}
disabled={!tasksEnabled || creatingTask}
/>
<Textarea
value={newTaskDescription}
onChange={(e) => setNewTaskDescription(e.target.value)}
placeholder={t('sections.library.quickDescription', 'Beschreibung (optional)')}
disabled={!tasksEnabled || creatingTask}
className="min-h-[70px]"
/>
<div className="flex items-center gap-2">
<label className="text-xs text-slate-700">{t('sections.library.quickEmotion', 'Emotion')}</label>
<select
className="h-9 rounded-lg border border-slate-200 bg-white px-2 text-sm"
value={newTaskEmotionId ?? ''}
onChange={(e) => setNewTaskEmotionId(e.target.value ? Number(e.target.value) : null)}
disabled={!tasksEnabled || creatingTask}
>
<option value="">{t('sections.library.quickEmotionNone', 'Keine')}</option>
{relevantEmotions.map((emotion) => (
<option key={emotion.id} value={emotion.id}>
{emotion.name}
</option>
))}
</select>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-slate-700">{t('sections.library.quickDifficulty', 'Schwierigkeit')}</label>
<select
className="h-9 rounded-lg border border-slate-200 bg-white px-2 text-sm"
disabled={!tasksEnabled || creatingTask}
value={newTaskDifficulty}
onChange={(e) => setNewTaskDifficulty(e.target.value as TenantTask['difficulty'] | '')}
>
<option value="">{t('sections.library.quickDifficultyNone', 'Keine')}</option>
<option value="easy">{t('sections.library.difficulty.easy', 'Leicht')}</option>
<option value="medium">{t('sections.library.difficulty.medium', 'Mittel')}</option>
<option value="hard">{t('sections.library.difficulty.hard', 'Schwer')}</option>
</select>
</div>
<div className="flex justify-end">
<Button
size="sm"
onClick={() => void handleCreateQuickTask()}
disabled={!newTaskTitle.trim() || creatingTask || !tasksEnabled}
>
{creatingTask ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
t('sections.library.quickCreateCta', 'Erstellen & zuweisen')
)}
</Button>
</div>
</div>
<div className="flex flex-wrap items-center justify-between gap-2" ref={libraryRef} id="library-section">
<h3 className="flex items-center gap-2 text-sm font-semibold text-slate-900">
<PlusCircle className="h-4 w-4 text-emerald-500" />
{t('sections.library.title', 'Tasks aus Bibliothek hinzufügen')}
</h3>
</div>
<p className="text-xs text-slate-600">
{t('sections.library.helper', 'Suche, filtere und füge einzelne Aufgaben hinzu. Eigene Aufgaben legst du über den Dialog an.')}
</p>
<DropZone id="library-dropzone">
<div className="space-y-2 max-h-72 overflow-y-auto">
<div className="space-y-2 max-h-80 overflow-y-auto">
{availableTasks.length === 0 ? (
<EmptyState message={t('sections.library.empty', 'Keine Tasks in der Bibliothek gefunden.')} />
) : (
@@ -1087,58 +1043,54 @@ export default function EventTasksPage() {
</DragOverlay>
</DndContext>
</Card>
<div className="sticky bottom-3 z-10 flex flex-col gap-2 rounded-2xl border border-slate-200 bg-white/95 p-3 shadow-xl shadow-slate-200 lg:top-4 lg:bottom-auto">
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-700">
<span className="font-semibold text-slate-900">
{t('sections.bulk.title', 'Batch-Aktionen')}
</span>
<Badge variant="outline" className="border-slate-200 text-slate-700">
{t('sections.bulk.assignedSelected', {
defaultValue: '{{count}} ausgewählt (Zugeordnet)',
count: selectedAssignedIds.length,
})}
</Badge>
<Badge variant="outline" className="border-slate-200 text-slate-700">
{t('sections.bulk.librarySelected', {
defaultValue: '{{count}} ausgewählt (Bibliothek)',
count: selectedAvailableIds.length,
})}
</Badge>
{hasSelection ? (
<div className="fixed inset-x-0 bottom-4 z-30 flex justify-center px-4">
<div className="pointer-events-auto flex w-full max-w-4xl flex-wrap items-center gap-3 rounded-2xl border border-slate-200 bg-white/95 p-3 shadow-2xl">
<div className="flex flex-1 flex-wrap items-center gap-2 text-sm text-slate-700">
<Badge variant="outline" className="border-slate-200 text-slate-700">
{t('sections.bulk.assignedSelected', {
defaultValue: '{{count}} ausgewählt (Zugeordnet)',
count: selectedAssignedIds.length,
})}
</Badge>
<Badge variant="outline" className="border-slate-200 text-slate-700">
{t('sections.bulk.librarySelected', {
defaultValue: '{{count}} ausgewählt (Bibliothek)',
count: selectedAvailableIds.length,
})}
</Badge>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={() => void handleAssignSelected()}
disabled={selectedAvailableIds.length === 0 || !tasksEnabled || batchSaving || saving}
>
{t('actions.assignSelected', 'Auswahl zuweisen')}
</Button>
<Button
size="sm"
variant="outline"
onClick={() => void handleDetachSelected()}
disabled={selectedAssignedIds.length === 0 || batchSaving || saving}
>
{t('actions.removeSelected', 'Auswahl entfernen')}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => {
setSelectedAssignedIds([]);
setSelectedAvailableIds([]);
}}
>
{t('sections.bulk.clear', 'Auswahl aufheben')}
</Button>
</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={() => void handleAssignSelected()}
disabled={selectedAvailableIds.length === 0 || !tasksEnabled || batchSaving || saving}
>
{t('actions.assignSelected', 'Auswahl zuweisen')}
</Button>
<Button
size="sm"
variant="outline"
onClick={() => void handleDetachSelected()}
disabled={selectedAssignedIds.length === 0 || batchSaving || saving}
>
{t('actions.removeSelected', 'Auswahl entfernen')}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => navigate(buildEngagementTabPath('tasks'))}
>
{t('library.open', 'Aufgaben-Bibliothek öffnen')}
</Button>
</div>
</div>
<EmotionsCard
emotions={relevantEmotions}
emotionsLoading={emotionsLoading}
emotionsError={emotionsError}
onOpenEmotions={() => setEmotionsModalOpen(true)}
/>
) : null}
</TabsContent>
<TabsContent value="packs">
<MissionPackGrid
@@ -1150,10 +1102,96 @@ export default function EventTasksPage() {
onViewAll={() => navigate(buildEngagementTabPath('collections'))}
/>
</TabsContent>
<TabsContent value="emotions">
<EmotionsCard
emotions={relevantEmotions}
emotionsLoading={emotionsLoading}
emotionsError={emotionsError}
onOpenEmotions={() => setEmotionsModalOpen(true)}
/>
</TabsContent>
</Tabs>
</>
)}
<Dialog open={quickAddOpen} onOpenChange={setQuickAddOpen}>
<DialogContent className="max-w-xl">
<DialogHeader>
<DialogTitle>{t('sections.library.quickCreate', 'Eigene Aufgabe hinzufügen')}</DialogTitle>
</DialogHeader>
<div className="space-y-3">
<p className="text-xs text-slate-600">
{t('sections.library.quickHelper', 'Titel eingeben, optional beschreiben und sofort zum Event zuweisen.')}
</p>
<Input
value={newTaskTitle}
onChange={(e) => setNewTaskTitle(e.target.value)}
placeholder={t('sections.library.quickTitle', 'Titel der Aufgabe')}
disabled={!tasksEnabled || creatingTask}
/>
<Textarea
value={newTaskDescription}
onChange={(e) => setNewTaskDescription(e.target.value)}
placeholder={t('sections.library.quickDescription', 'Beschreibung (optional)')}
disabled={!tasksEnabled || creatingTask}
className="min-h-[90px]"
/>
<div className="grid gap-2 sm:grid-cols-2">
<div className="flex items-center gap-2">
<label className="text-xs text-slate-700">{t('sections.library.quickEmotion', 'Emotion')}</label>
<select
className="h-9 w-full rounded-lg border border-slate-200 bg-white px-2 text-sm"
value={newTaskEmotionId ?? ''}
onChange={(e) => setNewTaskEmotionId(e.target.value ? Number(e.target.value) : null)}
disabled={!tasksEnabled || creatingTask}
>
<option value="">{t('sections.library.quickEmotionNone', 'Keine')}</option>
{relevantEmotions.map((emotion) => (
<option key={emotion.id} value={emotion.id}>
{emotion.name}
</option>
))}
</select>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-slate-700">{t('sections.library.quickDifficulty', 'Schwierigkeit')}</label>
<select
className="h-9 w-full rounded-lg border border-slate-200 bg-white px-2 text-sm"
disabled={!tasksEnabled || creatingTask}
value={newTaskDifficulty}
onChange={(e) => setNewTaskDifficulty(e.target.value as TenantTask['difficulty'] | '')}
>
<option value="">{t('sections.library.quickDifficultyNone', 'Keine')}</option>
<option value="easy">{t('sections.library.difficulty.easy', 'Leicht')}</option>
<option value="medium">{t('sections.library.difficulty.medium', 'Mittel')}</option>
<option value="hard">{t('sections.library.difficulty.hard', 'Schwer')}</option>
</select>
</div>
</div>
<div className="flex justify-end gap-2">
<Button
variant="ghost"
onClick={() => {
setQuickAddOpen(false);
setNewTaskTitle('');
setNewTaskDescription('');
setNewTaskEmotionId(null);
setNewTaskDifficulty('');
}}
>
{t('actions.cancel', 'Abbrechen')}
</Button>
<Button
onClick={() => void handleCreateQuickTask()}
disabled={!newTaskTitle.trim() || creatingTask || !tasksEnabled}
>
{creatingTask ? <Loader2 className="h-4 w-4 animate-spin" /> : t('sections.library.quickCreateCta', 'Erstellen & zuweisen')}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
<Dialog open={emotionsModalOpen} onOpenChange={setEmotionsModalOpen}>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
@@ -1409,14 +1447,14 @@ function MissionPackGrid({
<div>
<CardTitle className="flex items-center gap-2 text-base text-slate-900">
<Layers className="h-5 w-5 text-pink-500" />
{t('title', 'Mission Packs')}
{t('title', 'Vorlagen / Aufgaben-Bundles')}
</CardTitle>
<CardDescription className="text-sm text-slate-600">
{t('subtitle', 'Importiere Aufgaben-Kollektionen, die zu deinem Event passen.')}
{t('subtitle', 'Importiere Aufgaben-Sets, die zu deinem Event passen.')}
</CardDescription>
</div>
<Button variant="outline" onClick={onViewAll}>
{t('viewAll', 'Alle Kollektionen ansehen')}
{t('viewAll', 'Alle Sets ansehen')}
</Button>
</CardHeader>
<CardContent className="space-y-4">
@@ -1463,7 +1501,7 @@ function MissionPackGrid({
{importingId === collection.id ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
t('importCta', 'Mission Pack importieren')
t('importCta', 'Aufgaben-Set importieren')
)}
</Button>
</div>