weitere optimierungen für mobile

This commit is contained in:
Codex Agent
2025-12-04 13:50:58 +01:00
parent c73a3163c0
commit 428fc5fd4f

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import React from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { ArrowLeft, Layers, Loader2, PlusCircle, Search, Sparkles, Pencil, Check, X } from 'lucide-react';
import { ArrowLeft, Layers, Loader2, PlusCircle, Search, Sparkles, Pencil, Check, X, CircleOff } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import toast from 'react-hot-toast';
@@ -95,7 +95,8 @@ export default function EventTasksPage() {
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);
const [libraryOpen, setLibraryOpen] = React.useState(false);
const [librarySearch, setLibrarySearch] = React.useState('');
React.useEffect(() => {
const handle = window.setTimeout(() => setDebouncedTaskSearch(taskSearch.trim().toLowerCase()), 180);
return () => window.clearTimeout(handle);
@@ -451,7 +452,7 @@ export default function EventTasksPage() {
return mode !== 'photo_only';
}, [event?.engagement_mode, event?.settings]);
const hasSelection = selectedAssignedIds.length > 0 || selectedAvailableIds.length > 0;
const hasSelection = selectedAssignedIds.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);
@@ -471,11 +472,18 @@ export default function EventTasksPage() {
try {
const nextMode = checked ? 'tasks' : 'photo_only';
const updated = await updateEvent(slug, {
const payload = {
name: event.name,
slug: event.slug,
event_type_id: event.event_type_id ?? event.event_type?.id,
event_date: event.event_date ?? undefined,
settings: {
...(event.settings ?? {}),
engagement_mode: nextMode,
},
};
const updated = await updateEvent(slug, {
...payload,
});
setEvent((prev) => ({
...(prev ?? updated),
@@ -513,6 +521,7 @@ export default function EventTasksPage() {
setAssignedTasks((prev) => [...prev, ...move]);
setAvailableTasks((prev) => prev.filter((task) => !nextAvailableSet.has(task.id)));
setSelectedAvailableIds([]);
setLibraryOpen(false);
setBatchSaving(true);
try {
await assignTasksToEvent(event.id, ids);
@@ -557,7 +566,7 @@ export default function EventTasksPage() {
}, [event, selectedAssignedIds, assignedTasks, availableTasks, t]);
const handleInlineUpdate = React.useCallback(
async (taskId: number, payload: { title?: string; difficulty?: TenantTask['difficulty'] | '' }) => {
async (taskId: number, payload: { title?: string; difficulty?: TenantTask['difficulty'] | ''; emotion_id?: number | null }) => {
if (!event) return;
const prevAssigned = assignedTasks;
@@ -573,13 +582,25 @@ export default function EventTasksPage() {
...payload,
title: payload.title ?? optimistic.title,
difficulty: payload.difficulty || null,
emotion_id: payload.emotion_id ?? optimistic.emotion_id ?? null,
} as Partial<TenantTask>;
const nextEmotion =
typeof payload.emotion_id === 'number'
? emotions.find((emotion) => emotion.id === payload.emotion_id) ?? null
: payload.emotion_id === null
? null
: optimistic.emotion ?? null;
if (existingAssigned) {
setAssignedTasks((prev) => prev.map((task) => (task.id === taskId ? { ...task, ...patch } : task)));
setAssignedTasks((prev) =>
prev.map((task) => (task.id === taskId ? { ...task, ...patch, emotion: nextEmotion } : task)),
);
}
if (existingAvailable) {
setAvailableTasks((prev) => prev.map((task) => (task.id === taskId ? { ...task, ...patch } : task)));
setAvailableTasks((prev) =>
prev.map((task) => (task.id === taskId ? { ...task, ...patch, emotion: nextEmotion } : task)),
);
}
setInlineSavingId(taskId);
@@ -610,14 +631,22 @@ export default function EventTasksPage() {
difficulty: payload.difficulty || undefined,
description: optimistic.description ?? undefined,
priority: optimistic.priority ?? undefined,
emotion_id: optimistic.emotion_id ?? undefined,
emotion_id: payload.emotion_id ?? optimistic.emotion_id ?? undefined,
});
if (existingAssigned) {
setAssignedTasks((prev) => prev.map((task) => (task.id === taskId ? { ...task, ...updated } : task)));
setAssignedTasks((prev) =>
prev.map((task) =>
task.id === taskId ? { ...task, ...updated, emotion: nextEmotion ?? updated.emotion ?? null } : task,
),
);
}
if (existingAvailable) {
setAvailableTasks((prev) => prev.map((task) => (task.id === taskId ? { ...task, ...updated } : task)));
setAvailableTasks((prev) =>
prev.map((task) =>
task.id === taskId ? { ...task, ...updated, emotion: nextEmotion ?? updated.emotion ?? null } : task,
),
);
}
toast.success(t('actions.updated', 'Task aktualisiert.'));
}
@@ -666,25 +695,29 @@ export default function EventTasksPage() {
) : (
<>
<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 className="flex flex-col gap-2">
<div className="flex justify-end">
<Button
variant="outline"
className="border-emerald-200 text-emerald-700"
onClick={() => navigate(buildEngagementTabPath('tasks'))}
>
{t('library.open', 'Aufgaben-Bibliothek öffnen')}
</Button>
</div>
<div className="overflow-x-auto">
<TabsList className="inline-flex min-w-fit gap-2 rounded-2xl bg-slate-100/80 p-1">
{tabOrder.map((key) => (
<TabsTrigger key={key} value={key} className="px-3 py-1.5 text-sm sm:text-base">
{key === 'packs'
? t('tabs.packs', 'Aufgaben-Sets')
: key === 'tasks'
? t('tabs.tasks', 'Aufgaben')
: t('tabs.emotions', 'Emotionen')}
</TabsTrigger>
))}
</TabsList>
</div>
</div>
<TabsContent value="tasks" className="space-y-6">
<Card className="border-0 bg-white/85 shadow-xl shadow-pink-100/60">
@@ -747,6 +780,34 @@ export default function EventTasksPage() {
{t('actions.addCustom', 'Eigene Aufgabe hinzufügen')}
</Button>
</div>
{!tasksEnabled ? (
<div className="flex flex-col gap-2 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
<div className="flex items-center gap-3">
<CircleOff className="h-5 w-5" />
<div>
<p className="font-semibold">{t('modes.disabledTitle', 'Aufgabenmodus ist aus')}</p>
<p className="text-xs text-amber-800">
{t('modes.disabledCopy', 'Gäste sehen keine Mission Cards. Aktivieren, um Aufgaben sichtbar zu machen.')}
</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
size="sm"
className="bg-amber-600 text-white hover:bg-amber-700"
onClick={() => void handleModeChange(true)}
disabled={modeSaving || assignedTasks.length === 0}
>
{modeSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : t('modes.enable', 'Aufgaben aktivieren')}
</Button>
{assignedTasks.length === 0 ? (
<p className="text-[11px] text-amber-800">
{t('modes.needTasks', 'Aktiviere Aufgaben, sobald mindestens eine Aufgabe zugewiesen ist.')}
</p>
) : null}
</div>
</div>
) : null}
</div>
</CardHeader>
<DndContext
@@ -755,7 +816,26 @@ export default function EventTasksPage() {
onDragStart={(event) => setDraggingId(Number(event.active.id))}
onDragEnd={handleDragEnd}
>
<CardContent className="grid gap-4 lg:grid-cols-2">
<CardContent className="relative grid gap-4">
{!tasksEnabled ? (
<div className="absolute inset-0 z-20 flex items-center justify-center rounded-2xl bg-white/80 backdrop-blur-sm">
<div className="flex flex-col items-center gap-2 text-center text-sm text-slate-700">
<CircleOff className="h-5 w-5 text-amber-700" />
<p className="font-semibold">{t('modes.disabledTitle', 'Aufgabenmodus ist aus')}</p>
<p className="text-xs text-slate-600">
{t('modes.disabledOverlay', 'Aktiviere Aufgaben, um Listen und Aktionen zu nutzen.')}
</p>
<Button
size="sm"
className="bg-amber-600 text-white hover:bg-amber-700"
onClick={() => void handleModeChange(true)}
disabled={modeSaving || assignedTasks.length === 0}
>
{t('modes.enable', 'Aufgaben aktivieren')}
</Button>
</div>
</div>
) : null}
<section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex flex-wrap items-center gap-3">
@@ -866,17 +946,18 @@ export default function EventTasksPage() {
) : null}
<Button
size="sm"
className="border-emerald-200 text-emerald-700"
variant="outline"
onClick={() => void handleDetachSelected()}
disabled={selectedAssignedIds.length === 0 || batchSaving || saving}
onClick={() => setLibraryOpen(true)}
>
{t('actions.removeSelected', 'Auswahl entfernen')}
<PlusCircle className="mr-2 h-4 w-4" />
{t('actions.openLibrary', 'Aus Bibliothek hinzufügen')}
</Button>
</div>
</div>
{emotionChips.length > 0 && emotionFilterOpen ? (
<div className="flex flex-wrap gap-2 rounded-xl border border-slate-200 bg-white/80 p-2">
{emotionChips.length > 0 && emotionFilterOpen ? (
<div className="flex flex-wrap gap-2 rounded-xl border border-slate-200 bg-white/80 p-2">
<Button
size="sm"
variant={emotionFilter.length === 0 ? 'default' : 'outline'}
@@ -943,95 +1024,13 @@ export default function EventTasksPage() {
disabled={batchSaving || saving}
onInlineUpdate={handleInlineUpdate}
inlineSaving={inlineSavingId === task.id}
emotions={relevantEmotions}
/>
))}
</div>
)}
</DropZone>
</section>
<section className="space-y-3">
<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-80 overflow-y-auto">
{availableTasks.length === 0 ? (
<EmptyState message={t('sections.library.empty', 'Keine Tasks in der Bibliothek gefunden.')} />
) : (
<div className="space-y-2">
<div className="flex items-center justify-between gap-2 pb-1">
<div className="flex items-center gap-2 text-xs text-slate-700">
<Checkbox
checked={
availableTasks.length > 0 && availableTasks.every((task) => selectedAvailableIds.includes(task.id))
? true
: selectedAvailableIds.some((id) => availableTasks.some((task) => task.id === id))
? 'indeterminate'
: false
}
onCheckedChange={(checked) => {
if (checked) {
setSelectedAvailableIds((prev) => {
const next = new Set(prev);
availableTasks.forEach((task) => next.add(task.id));
return Array.from(next);
});
} else {
setSelectedAvailableIds([]);
}
}}
aria-label={t('sections.library.selectAll', 'Alle Bibliotheks-Tasks auswählen')}
/>
<span>
{t('sections.library.selectedCount', {
defaultValue: '{{count}} ausgewählt',
count: selectedAvailableIds.length,
})}
</span>
</div>
<Button
size="sm"
variant="outline"
onClick={() => void handleAssignSelected()}
disabled={selectedAvailableIds.length === 0 || !tasksEnabled || batchSaving || saving}
>
{t('actions.assignSelected', 'Auswahl zuweisen')}
</Button>
</div>
{availableTasks.map((task) => (
<DraggableTaskCard
key={task.id}
task={task}
origin="library"
onAdd={() => void handleAssignSingle(task.id)}
disabled={!tasksEnabled || saving || batchSaving}
showCheckbox
checked={selectedAvailableIds.includes(task.id)}
onCheckedChange={(checked) => {
setSelectedAvailableIds((prev) => {
if (checked) {
if (prev.includes(task.id)) return prev;
return [...prev, task.id];
}
return prev.filter((id) => id !== task.id);
});
}}
onInlineUpdate={handleInlineUpdate}
inlineSaving={inlineSavingId === task.id}
/>
))}
</div>
)}
</div>
</DropZone>
</section>
</CardContent>
<DragOverlay>
{draggingId ? (
@@ -1053,36 +1052,21 @@ export default function EventTasksPage() {
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')}
{t('actions.removeSelected', 'Ausgewählte Aufgaben entfernen')}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => {
setSelectedAssignedIds([]);
setSelectedAvailableIds([]);
}}
>
{t('sections.bulk.clear', 'Auswahl aufheben')}
@@ -1114,11 +1098,11 @@ export default function EventTasksPage() {
</>
)}
<Dialog open={quickAddOpen} onOpenChange={setQuickAddOpen}>
<DialogContent className="max-w-xl">
<DialogHeader>
<DialogTitle>{t('sections.library.quickCreate', 'Eigene Aufgabe hinzufügen')}</DialogTitle>
</DialogHeader>
<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.')}
@@ -1190,6 +1174,104 @@ export default function EventTasksPage() {
</div>
</div>
</DialogContent>
</Dialog>
<Dialog open={libraryOpen} onOpenChange={(open) => {
setLibraryOpen(open);
if (!open) {
setSelectedAvailableIds([]);
setLibrarySearch('');
}
}}>
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{t('sections.library.title', 'Tasks aus Bibliothek hinzufügen')}</DialogTitle>
</DialogHeader>
<div className="space-y-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="flex w-full items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2">
<Search className="h-4 w-4 text-slate-500" />
<Input
value={librarySearch}
onChange={(e) => setLibrarySearch(e.target.value)}
placeholder={t('sections.library.search', 'Aufgaben suchen...')}
className="h-8 border-0 bg-transparent p-0 text-sm focus-visible:ring-0"
/>
</div>
<div className="flex flex-wrap gap-2 text-xs text-slate-600">
<Badge variant="outline" className="border-slate-200">
{t('sections.library.selectedCount', { defaultValue: '{{count}} ausgewählt', count: selectedAvailableIds.length })}
</Badge>
<Badge variant="outline" className="border-slate-200">
{t('summary.library', 'Bibliothek')} · {availableTasks.length}
</Badge>
</div>
</div>
<div className="space-y-2 max-h-[55vh] overflow-y-auto">
{availableTasks.length === 0 ? (
<EmptyState message={t('sections.library.empty', 'Keine Tasks in der Bibliothek gefunden.')} />
) : (
availableTasks
.filter((task) =>
librarySearch.trim()
? `${task.title ?? ''} ${task.description ?? ''}`.toLowerCase().includes(librarySearch.toLowerCase().trim())
: true,
)
.map((task) => (
<DraggableTaskCard
key={task.id}
task={task}
origin="library"
onAdd={() => void handleAssignSingle(task.id)}
disabled={!tasksEnabled || saving || batchSaving}
showCheckbox
checked={selectedAvailableIds.includes(task.id)}
onCheckedChange={(checked) => {
setSelectedAvailableIds((prev) => {
if (checked) {
if (prev.includes(task.id)) return prev;
return [...prev, task.id];
}
return prev.filter((id) => id !== task.id);
});
}}
onInlineUpdate={handleInlineUpdate}
inlineSaving={inlineSavingId === task.id}
emotions={relevantEmotions}
/>
))
)}
</div>
{selectedAvailableIds.length > 0 ? (
<div className="sticky bottom-0 flex flex-wrap items-center gap-2 rounded-2xl border border-slate-200 bg-white/95 p-3 shadow-lg">
<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.library.selectedCount', { defaultValue: '{{count}} ausgewählt', 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="ghost"
onClick={() => setSelectedAvailableIds([])}
>
{t('sections.bulk.clear', 'Auswahl aufheben')}
</Button>
</div>
</div>
) : null}
</div>
</DialogContent>
</Dialog>
<Dialog open={emotionsModalOpen} onOpenChange={setEmotionsModalOpen}>
@@ -1247,6 +1329,7 @@ function DraggableTaskCard({
onCheckedChange,
onInlineUpdate,
inlineSaving,
emotions,
}: {
task: TenantTask;
origin: 'assigned' | 'library';
@@ -1256,39 +1339,30 @@ function DraggableTaskCard({
showCheckbox?: boolean;
checked?: boolean;
onCheckedChange?: (checked: boolean) => void;
onInlineUpdate?: (taskId: number, payload: { title?: string; difficulty?: TenantTask['difficulty'] | '' }) => void;
onInlineUpdate?: (taskId: number, payload: { title?: string; difficulty?: TenantTask['difficulty'] | ''; emotion_id?: number | null }) => void;
inlineSaving?: boolean;
emotions?: TenantEmotion[];
}) {
const { t } = useTranslation('management');
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useDraggable({
id: task.id,
data: { list: origin },
});
const [editing, setEditing] = React.useState(false);
const [titleValue, setTitleValue] = React.useState(task.title ?? '');
const [difficultyValue, setDifficultyValue] = React.useState<TenantTask['difficulty'] | ''>(task.difficulty ?? '');
const [emotionIdValue, setEmotionIdValue] = React.useState<number | ''>(task.emotion_id ?? '');
React.useEffect(() => {
setTitleValue(task.title ?? '');
setDifficultyValue(task.difficulty ?? '');
}, [task.title, task.difficulty]);
const style = {
transform: transform ? CSS.Translate.toString(transform) : undefined,
transition: transition || undefined,
opacity: isDragging ? 0.8 : 1,
};
setEmotionIdValue(task.emotion_id ?? '');
}, [task.title, task.difficulty, task.emotion_id]);
return (
<div
ref={setNodeRef}
style={style}
className="rounded-2xl border border-slate-200 bg-white/90 px-4 py-3 shadow-sm"
className="rounded-xl border border-slate-200 bg-white/90 px-3 py-2 shadow-sm"
onDoubleClick={() => setEditing(true)}
>
<div className="flex items-start gap-3">
<div className="flex items-center gap-2 pt-1">
<div className="flex items-start gap-2">
<div className="flex min-w-[32px] flex-col items-center gap-1 pt-1">
{showCheckbox ? (
<Checkbox
checked={checked ?? false}
@@ -1296,17 +1370,43 @@ function DraggableTaskCard({
aria-label={t('actions.selectTask', 'Task auswählen')}
/>
) : null}
<button
className="h-7 w-7 rounded-md border border-slate-200 bg-white text-slate-500 transition hover:bg-slate-50 disabled:opacity-50"
{...listeners}
{...attributes}
disabled={disabled}
aria-label={t('library.dragHandle', 'Task verschieben')}
>
</button>
<div className="flex flex-col items-center gap-1">
<Button
size="icon"
variant="ghost"
onClick={() => setEditing((prev) => !prev)}
aria-label={t('actions.edit', 'Bearbeiten')}
disabled={inlineSaving}
className="h-8 w-8"
>
{editing ? <X className="h-4 w-4" /> : <Pencil className="h-4 w-4 text-slate-500" />}
</Button>
{origin === 'assigned' ? (
<Button
size="icon"
variant="ghost"
onClick={onRemove}
disabled={disabled}
aria-label={t('actions.remove', 'Vom Event entfernen')}
className="h-8 w-8"
>
<Trash2 className="h-4 w-4 text-slate-500" />
</Button>
) : (
<Button
size="icon"
variant="ghost"
onClick={onAdd}
disabled={disabled}
aria-label={t('actions.assign', 'Zum Event hinzufügen')}
className="h-8 w-8"
>
<PlusCircle className="h-4 w-4 text-emerald-600" />
</Button>
)}
</div>
</div>
<div className="flex-1 space-y-2 min-w-0">
<div className="flex-1 space-y-1 min-w-0">
<div className="space-y-1 min-w-0 max-w-full">
{editing ? (
<div className="space-y-2">
@@ -1327,6 +1427,21 @@ function DraggableTaskCard({
<option value="medium">{t('sections.library.difficulty.medium', 'Mittel')}</option>
<option value="hard">{t('sections.library.difficulty.hard', 'Schwer')}</option>
</select>
{emotions && emotions.length > 0 ? (
<select
className="h-9 w-full rounded-lg border border-slate-200 bg-white px-2 text-sm"
value={emotionIdValue}
onChange={(e) => setEmotionIdValue(e.target.value ? Number(e.target.value) : '')}
disabled={inlineSaving}
>
<option value="">{t('sections.library.quickEmotionNone', 'Keine')}</option>
{emotions.map((emotion) => (
<option key={emotion.id} value={emotion.id}>
{emotion.name}
</option>
))}
</select>
) : null}
<div className="flex items-center gap-2">
<Button
size="icon"
@@ -1335,7 +1450,11 @@ function DraggableTaskCard({
disabled={!titleValue.trim() || inlineSaving}
onClick={() => {
if (!onInlineUpdate) return;
onInlineUpdate(task.id, { title: titleValue.trim(), difficulty: difficultyValue });
onInlineUpdate(task.id, {
title: titleValue.trim(),
difficulty: difficultyValue,
emotion_id: emotionIdValue === '' ? null : Number(emotionIdValue),
});
setEditing(false);
}}
>
@@ -1363,7 +1482,7 @@ function DraggableTaskCard({
</div>
)}
</div>
<div className="flex flex-wrap items-center gap-2">
<div className="flex flex-wrap items-center gap-2 text-xs">
{!editing && task.emotion ? (
<Badge
variant="outline"
@@ -1387,36 +1506,6 @@ function DraggableTaskCard({
{t('tasks.customBadge', 'Eigene Aufgabe')}
</Badge>
) : null}
<Button
size="icon"
variant="ghost"
onClick={() => setEditing((prev) => !prev)}
aria-label={t('actions.edit', 'Bearbeiten')}
disabled={inlineSaving}
>
{editing ? <X className="h-4 w-4" /> : <Pencil className="h-4 w-4 text-slate-500" />}
</Button>
{origin === 'assigned' ? (
<Button
size="icon"
variant="ghost"
onClick={onRemove}
disabled={disabled}
aria-label={t('actions.remove', 'Vom Event entfernen')}
>
<Trash2 className="h-4 w-4 text-slate-500" />
</Button>
) : (
<Button
size="icon"
variant="ghost"
onClick={onAdd}
disabled={disabled}
aria-label={t('actions.assign', 'Zum Event hinzufügen')}
>
<PlusCircle className="h-4 w-4 text-emerald-600" />
</Button>
)}
</div>
</div>
</div>