import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { RefreshCcw, Plus, Folder, Pencil, Trash2, MoreHorizontal } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { ListItem } from '@tamagui/list-item';
import { Pressable } from '@tamagui/react-native-web-lite';
import { MobileScaffold } from './components/Scaffold';
import { MobileCard, CTAButton } from './components/Primitives';
import { BottomNav } from './components/BottomNav';
import {
getEvent,
getEventTasks,
updateTask,
TenantTask,
assignTasksToEvent,
getTasks,
getTaskCollections,
importTaskCollection,
createTask,
TenantTaskCollection,
getEmotions,
TenantEmotion,
detachTasksFromEvent,
createEmotion,
updateEmotion as updateEmotionApi,
deleteEmotion as deleteEmotionApi,
} from '../api';
import { adminPath } from '../constants';
import { isAuthError } from '../auth/tokens';
import { getApiErrorMessage } from '../lib/apiError';
import toast from 'react-hot-toast';
import { MobileSheet } from './components/Sheet';
import { Tag } from './components/Tag';
import { useMobileNav } from './hooks/useMobileNav';
const inputStyle: React.CSSProperties = {
width: '100%',
height: 40,
borderRadius: 10,
border: '1px solid #e5e7eb',
padding: '0 12px',
fontSize: 13,
background: 'white',
};
function InlineSeparator() {
return ;
}
export default function MobileEventTasksPage() {
const { slug: slugParam } = useParams<{ slug?: string }>();
const slug = slugParam ?? null;
const navigate = useNavigate();
const { t } = useTranslation('management');
const [tasks, setTasks] = React.useState([]);
const [library, setLibrary] = React.useState([]);
const [collections, setCollections] = React.useState([]);
const [emotions, setEmotions] = React.useState([]);
const [showCollectionSheet, setShowCollectionSheet] = React.useState(false);
const [showTaskSheet, setShowTaskSheet] = React.useState(false);
const [newTask, setNewTask] = React.useState({ id: null as number | null, title: '', description: '', emotion_id: '' as string | '' });
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [busyId, setBusyId] = React.useState(null);
const [assigningId, setAssigningId] = React.useState(null);
const [eventId, setEventId] = React.useState(null);
const { go } = useMobileNav(slug);
const [searchTerm, setSearchTerm] = React.useState('');
const [emotionFilter, setEmotionFilter] = React.useState('');
const [expandedLibrary, setExpandedLibrary] = React.useState(false);
const [expandedCollections, setExpandedCollections] = React.useState(false);
const [showActionsSheet, setShowActionsSheet] = React.useState(false);
const [showBulkSheet, setShowBulkSheet] = React.useState(false);
const [bulkLines, setBulkLines] = React.useState('');
const [showEmotionSheet, setShowEmotionSheet] = React.useState(false);
const [editingEmotion, setEditingEmotion] = React.useState(null);
const [emotionForm, setEmotionForm] = React.useState({ name: '', color: '#e5e7eb' });
const [savingEmotion, setSavingEmotion] = React.useState(false);
const load = React.useCallback(async () => {
if (!slug) {
setError(t('events.errors.missingSlug', 'Kein Event-Slug angegeben.'));
setLoading(false);
return;
}
setLoading(true);
setError(null);
try {
const event = await getEvent(slug);
setEventId(event.id);
const result = await getEventTasks(event.id, 1);
const libraryTasks = await getTasks({ per_page: 50 });
const collectionList = await getTaskCollections({ per_page: 50 });
const emotionList = await getEmotions();
setTasks(result.data);
setLibrary(libraryTasks.data.filter((task) => !result.data.find((t) => t.id === task.id)));
setCollections(collectionList.data ?? []);
setEmotions(emotionList ?? []);
} catch (err) {
if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('events.errors.loadFailed', 'Tasks konnten nicht geladen werden.')));
}
} finally {
setLoading(false);
}
}, [slug, t]);
React.useEffect(() => {
void load();
}, [load]);
async function quickAssign(taskId: number) {
if (!eventId) return;
setAssigningId(taskId);
try {
await assignTasksToEvent(eventId, [taskId]);
const result = await getEventTasks(eventId, 1);
setTasks(result.data);
setLibrary((prev) => prev.filter((t) => t.id !== taskId));
toast.success(t('events.tasks.assigned', 'Task hinzugefügt'));
} catch (err) {
if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Task konnte nicht zugewiesen werden.')));
toast.error(t('events.tasks.updateFailed', 'Task konnte nicht zugewiesen werden.'));
}
} finally {
setAssigningId(null);
}
}
async function importCollection(collectionId: number) {
if (!eventId) return;
try {
await importTaskCollection(collectionId, eventId);
const result = await getEventTasks(eventId, 1);
setTasks(result.data);
toast.success(t('events.tasks.imported', 'Aufgabenpaket importiert'));
} catch (err) {
if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Paket konnte nicht importiert werden.')));
toast.error(t('events.errors.saveFailed', 'Paket konnte nicht importiert werden.'));
}
}
}
async function createNewTask() {
if (!eventId || !newTask.title.trim()) return;
try {
if (newTask.id) {
await updateTask(newTask.id, {
title: newTask.title.trim(),
description: newTask.description.trim() || null,
emotion_id: newTask.emotion_id ? Number(newTask.emotion_id) : undefined,
} as any);
} else {
const created = await createTask({
title: newTask.title.trim(),
description: newTask.description.trim() || null,
emotion_id: newTask.emotion_id ? Number(newTask.emotion_id) : undefined,
} as any);
await assignTasksToEvent(eventId, [created.id]);
}
const result = await getEventTasks(eventId, 1);
setTasks(result.data);
setShowTaskSheet(false);
setNewTask({ id: null, title: '', description: '', emotion_id: '' });
toast.success(t('events.tasks.created', 'Aufgabe gespeichert'));
} catch (err) {
if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Aufgabe konnte nicht erstellt werden.')));
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht erstellt werden.'));
}
}
}
async function detachTask(taskId: number) {
if (!eventId) return;
setBusyId(taskId);
try {
await detachTasksFromEvent(eventId, [taskId]);
setTasks((prev) => prev.filter((task) => task.id !== taskId));
toast.success(t('events.tasks.removed', 'Aufgabe entfernt'));
} catch (err) {
if (!isAuthError(err)) {
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Aufgabe konnte nicht entfernt werden.')));
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht entfernt werden.'));
}
} finally {
setBusyId(null);
}
}
const startEdit = (task: TenantTask) => {
setNewTask({
id: task.id,
title: task.title,
description: task.description ?? '',
emotion_id: task.emotion?.id ? String(task.emotion.id) : '',
});
setShowTaskSheet(true);
};
const filteredTasks = tasks.filter((task) => {
const matchText =
!searchTerm ||
task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
(task.description ?? '').toLowerCase().includes(searchTerm.toLowerCase());
const matchEmotion = !emotionFilter || task.emotion?.id === Number(emotionFilter);
return matchText && matchEmotion;
});
async function handleBulkAdd() {
if (!eventId || !bulkLines.trim()) return;
const lines = bulkLines
.split('\n')
.map((l) => l.trim())
.filter(Boolean);
if (!lines.length) return;
try {
for (const line of lines) {
const created = await createTask({ title: line } as any);
await assignTasksToEvent(eventId, [created.id]);
}
const result = await getEventTasks(eventId, 1);
setTasks(result.data);
setBulkLines('');
setShowBulkSheet(false);
toast.success(t('events.tasks.created', 'Aufgabe gespeichert'));
} catch (err) {
if (!isAuthError(err)) {
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht erstellt werden.'));
}
}
}
async function saveEmotion() {
if (!emotionForm.name.trim()) return;
setSavingEmotion(true);
try {
if (editingEmotion) {
const updated = await updateEmotionApi(editingEmotion.id, { name: emotionForm.name.trim(), color: emotionForm.color });
setEmotions((prev) => prev.map((em) => (em.id === editingEmotion.id ? updated : em)));
} else {
const created = await createEmotion({ name: emotionForm.name.trim(), color: emotionForm.color });
setEmotions((prev) => [...prev, created]);
}
setShowEmotionSheet(false);
setEditingEmotion(null);
setEmotionForm({ name: '', color: '#e5e7eb' });
toast.success(t('events.tasks.emotionSaved', 'Emotion gespeichert'));
} catch (err) {
if (!isAuthError(err)) {
toast.error(getApiErrorMessage(err, t('events.errors.saveFailed', 'Konnte nicht gespeichert werden.')));
}
} finally {
setSavingEmotion(false);
}
}
async function removeEmotion(emotionId: number) {
try {
await deleteEmotionApi(emotionId);
setEmotions((prev) => prev.filter((em) => em.id !== emotionId));
toast.success(t('events.tasks.emotionRemoved', 'Emotion entfernt'));
} catch (err) {
if (!isAuthError(err)) {
toast.error(getApiErrorMessage(err, t('events.errors.saveFailed', 'Konnte nicht gespeichert werden.')));
}
}
}
return (
navigate(-1)}
rightSlot={
load()}>
setShowActionsSheet(true)}>
}
footer={
}
>
{error ? (
{error}
) : null}
{loading ? (
{Array.from({ length: 4 }).map((_, idx) => (
))}
) : tasks.length === 0 ? (
{t('events.tasks.empty', 'Noch keine Aufgaben.')}
) : (
setSearchTerm(e.target.value)}
placeholder={t('events.tasks.search', 'Search tasks')}
style={{ ...inputStyle, height: 38 }}
/>
setEmotionFilter('')}
/>
{emotions.map((emotion) => (
setEmotionFilter(String(emotion.id))}
/>
))}
{t('events.tasks.count', '{{count}} Tasks', { count: filteredTasks.length })}
{filteredTasks.map((task, idx) => (
{task.title}
}
subTitle={
task.description ? (
{task.description}
) : null
}
iconAfter={
startEdit(task)}>
detachTask(task.id)}>
}
paddingVertical="$2"
paddingHorizontal="$3"
>
{task.emotion ? (
) : null}
{idx < tasks.length - 1 ? : null}
))}
{t('events.tasks.library', 'Weitere Aufgaben')}
setShowCollectionSheet(true)}>
{t('events.tasks.import', 'Import Pack')}
setExpandedLibrary((prev) => !prev)}>
{expandedLibrary ? t('events.tasks.hideLibrary', 'Hide library') : t('events.tasks.viewAllLibrary', 'View all')}
{library.length === 0 ? (
{t('events.tasks.libraryEmpty', 'Keine weiteren Aufgaben verfügbar.')}
) : (
{(expandedLibrary ? library : library.slice(0, 6)).map((task, idx, arr) => (
{task.title}
}
subTitle={
task.description ? (
{task.description}
) : null
}
iconAfter={
quickAssign(task.id)}>
{assigningId === task.id ? t('common.processing', '...') : t('events.tasks.add', 'Add')}
}
paddingVertical="$2"
paddingHorizontal="$3"
/>
{idx < arr.length - 1 ? : null}
))}
)}
)}
setShowCollectionSheet(false)}
title={t('events.tasks.import', 'Aufgabenpaket importieren')}
footer={null}
>
setExpandedCollections((prev) => !prev)}>
{expandedCollections ? t('events.tasks.hideCollections', 'Hide collections') : t('events.tasks.showCollections', 'Show all')}
{collections.length === 0 ? (
{t('events.tasks.collectionsEmpty', 'Keine Pakete vorhanden.')}
) : (
{(expandedCollections ? collections : collections.slice(0, 6)).map((collection, idx, arr) => (
{collection.title}
}
subTitle={
collection.description ? (
{collection.description}
) : null
}
iconAfter={
importCollection(collection.id)}>
{t('events.tasks.import', 'Import')}
}
paddingVertical="$2"
paddingHorizontal="$3"
/>
{idx < arr.length - 1 ? : null}
))}
)}
setShowTaskSheet(false)}
title={t('events.tasks.addTask', 'Aufgabe hinzufügen')}
footer={
createNewTask()} />
}
>
setNewTask((prev) => ({ ...prev, title: e.target.value }))}
placeholder={t('events.tasks.titlePlaceholder', 'z.B. Erstes Gruppenfoto')}
style={inputStyle}
/>
setShowActionsSheet(false)}
title={t('events.tasks.moreActions', 'Mehr Aktionen')}
footer={null}
>
{t('events.tasks.bulkAdd', 'Bulk add')}
}
onPress={() => {
setShowActionsSheet(false);
setShowBulkSheet(true);
}}
paddingVertical="$2"
paddingHorizontal="$3"
/>
{t('events.tasks.manageEmotions', 'Manage emotions')}
}
subTitle={
{t('events.tasks.manageEmotionsHint', 'Filter and keep your taxonomy tidy.')}
}
onPress={() => {
setShowActionsSheet(false);
setShowEmotionSheet(true);
}}
paddingVertical="$2"
paddingHorizontal="$3"
/>
setShowBulkSheet(false)}
title={t('events.tasks.bulkAdd', 'Bulk add')}
footer={ handleBulkAdd()} />}
>
{t('events.tasks.bulkHint', 'One task per line. These will be created and added to the event.')}
{
setShowEmotionSheet(false);
setEditingEmotion(null);
setEmotionForm({ name: '', color: '#e5e7eb' });
}}
title={t('events.tasks.manageEmotions', 'Manage emotions')}
footer={
saveEmotion()}
/>
}
>
setEmotionForm((prev) => ({ ...prev, name: e.target.value }))}
placeholder={t('events.tasks.emotionNamePlaceholder', 'z.B. Joy')}
style={inputStyle}
/>
setEmotionForm((prev) => ({ ...prev, color: e.target.value }))}
style={{ width: '100%', height: 44, borderRadius: 10, border: '1px solid #e5e7eb', background: 'white' }}
/>
{emotions.map((em) => (
}
iconAfter={
{
setEditingEmotion(em);
setEmotionForm({ name: em.name ?? '', color: em.color ?? '#e5e7eb' });
}}
>
removeEmotion(em.id)}>
}
/>
))}
{
setShowEmotionSheet(false);
setEditingEmotion(null);
setEmotionForm({ name: '', color: '#e5e7eb' });
}}
title={t('events.tasks.manageEmotions', 'Manage emotions')}
footer={
{
void saveEmotion();
}}
/>
}
>
setEmotionForm((prev) => ({ ...prev, name: e.target.value }))}
placeholder={t('events.tasks.emotionNamePlaceholder', 'z.B. Joy')}
style={inputStyle}
/>
setEmotionForm((prev) => ({ ...prev, color: e.target.value }))}
style={{ width: '100%', height: 44, borderRadius: 10, border: '1px solid #e5e7eb', background: 'white' }}
/>
{emotions.map((em) => (
}
iconAfter={
{
setEditingEmotion(em);
setEmotionForm({ name: em.name ?? '', color: em.color ?? '#e5e7eb' });
}}
>
removeEmotion(em.id)}>
}
/>
))}
setShowTaskSheet(true)}
style={{
position: 'fixed',
right: 20,
bottom: 90,
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: '#007AFF',
justifyContent: 'center',
alignItems: 'center',
boxShadow: '0 10px 25px rgba(0,122,255,0.35)',
zIndex: 60,
}}
>
);
}
function Field({ label, children }: { label: string; children: React.ReactNode }) {
return (
{label}
{children}
);
}
function Chip({ label, onPress, active, color }: { label: string; onPress: () => void; active: boolean; color?: string }) {
return (
{label}
);
}