import React, { useEffect, useState, useRef, useMemo } from 'react'; import { Check, Plus, Calendar, Trash2, RotateCcw, FileText, LayoutList, Columns, ArrowRight, ArrowLeft, Edit3, X, Layers, Share, Share2, Users, CheckSquare, User as UserIcon, Lock, Hash, AlertTriangle, Flame, Settings, ChevronDown, Folder, } from 'lucide-react'; import { TaskList, Thought, SubTask, Team, User, TaskListShare, KanbanColumn } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; import { UnifiedShareModal } from '../../components/UnifiedShareModal'; import { TaskDetailModal } from './TaskDetailModal'; import { ManageColumnsModal, DEFAULT_KANBAN_COLUMNS } from './ManageColumnsModal'; import { UserAutocomplete } from '../UserAutocomplete'; const getInitials = (name?: string): string => { if (!name) return '?'; const parts = name.trim().split(/\s+/); if (parts.length >= 2) { return (parts[0][0] + parts[1][0]).toUpperCase(); } return name.slice(0, 2).toUpperCase(); }; interface TasksViewProps { currentUser?: User | null; onRefreshStats: () => void; onOpenDocument: (docId: string) => void; refreshTrigger?: number; } export const TasksView: React.FC = ({ currentUser, onRefreshStats, onOpenDocument, refreshTrigger = 0, }) => { const [viewMode, setViewMode] = useState<'list' | 'kanban'>('list'); const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open'); const [tasks, setTasks] = useState([]); const [taskLists, setTaskLists] = useState([]); const [selectedListId, setSelectedListId] = useState('all'); const [teams, setTeams] = useState([]); const [collaborators, setCollaborators] = useState([]); const [discarded, setDiscarded] = useState([]); const [loading, setLoading] = useState(true); // Quick Task Add state const [quickTitle, setQuickTitle] = useState(''); const [quickDueDate, setQuickDueDate] = useState(''); // New TaskList modal state const [showNewListModal, setShowNewListModal] = useState(false); const [newListTitle, setNewListTitle] = useState(''); const [newListIcon, setNewListIcon] = useState('📋'); const [newListShareTarget, setNewListShareTarget] = useState(''); // Share TaskList modal state const [showShareListModal, setShowShareListModal] = useState(false); const [sharingTaskList, setSharingTaskList] = useState(null); const [addShareType, setAddShareType] = useState<'team' | 'user'>('team'); const [selectedShareTargetId, setSelectedShareTargetId] = useState(''); const [selectedShareUser, setSelectedShareUser] = useState(null); const [shareLoading, setShareLoading] = useState(false); // Detail modal state const [detailTaskId, setDetailTaskId] = useState(null); const [showDetailModal, setShowDetailModal] = useState(false); // Manage columns modal state const [showManageColumnsModal, setShowManageColumnsModal] = useState(false); const [globalColumns, setGlobalColumns] = useState(() => { try { const stored = localStorage.getItem('mindsift:kanban_columns'); if (stored) { const parsed = JSON.parse(stored); if (Array.isArray(parsed) && parsed.length > 0) return parsed; } } catch {} return DEFAULT_KANBAN_COLUMNS; }); // Hashtag filtering, grouping and sorting state const [selectedTag, setSelectedTag] = useState(null); const [groupBy, setGroupBy] = useState<'none' | 'tag' | 'status' | 'priority' | 'due'>('none'); const [sortBy, setSortBy] = useState<'created' | 'due' | 'priority' | 'alpha'>('created'); const loadData = async () => { setLoading(true); try { const [allTasks, allLists, allTeams, allCollabs, allDiscarded] = await Promise.all([ api.getTasks(selectedListId === 'all' ? undefined : selectedListId).catch(() => []), api.getTaskLists().catch(() => []), api.getUserTeams().catch(() => []), api.getCollaborators().catch(() => []), api.getThoughts('discarded').catch(() => []), ]); setTasks(Array.isArray(allTasks) ? allTasks : []); setTaskLists(Array.isArray(allLists) ? allLists : []); setTeams(Array.isArray(allTeams) ? allTeams : []); setCollaborators(Array.isArray(allCollabs) ? allCollabs : []); setDiscarded(Array.isArray(allDiscarded) ? allDiscarded : []); } catch { // Prevent unhandled promise rejection from crashing view } finally { setLoading(false); } }; useEffect(() => { loadData(); }, [refreshTrigger, selectedListId]); const selectedListIdRef = useRef(selectedListId); selectedListIdRef.current = selectedListId; const onRefreshStatsRef = useRef(onRefreshStats); onRefreshStatsRef.current = onRefreshStats; // Live-Sync WebSocket listener for tasks (registered once on mount) useEffect(() => { const handleWs = (e: any) => { const msg = e.detail; if (!msg || !msg.event) return; if (msg.event === 'TASK_CREATED') { const created = msg.payload as Thought; if ( created && (created.status === 'converted' || created.kanbanStatus || created.taskListId || created.isCompleted !== undefined) ) { setTasks((prev) => { if (prev.some((t) => t.id === created.id)) return prev; if (selectedListIdRef.current !== 'all' && created.taskListId !== selectedListIdRef.current) return prev; return [created, ...prev]; }); onRefreshStatsRef.current(); } } else if (msg.event === 'TASK_UPDATED') { const updated = msg.payload as Thought; if (updated && updated.id) { setTasks((prev) => { const exists = prev.some((t) => t.id === updated.id); if (!exists) { if ( (updated.status === 'converted' || updated.kanbanStatus || updated.taskListId || updated.isCompleted !== undefined) && (selectedListIdRef.current === 'all' || updated.taskListId === selectedListIdRef.current) ) { return [updated, ...prev]; } return prev; } if (selectedListIdRef.current !== 'all' && updated.taskListId !== selectedListIdRef.current) { return prev.filter((t) => t.id !== updated.id); } return prev.map((t) => (t.id === updated.id ? updated : t)); }); onRefreshStatsRef.current(); } } else if (msg.event === 'TASK_DELETED') { const deletedId = typeof msg.payload === 'string' ? msg.payload : msg.payload?.id; if (deletedId) { setTasks((prev) => prev.filter((t) => t.id !== deletedId)); onRefreshStatsRef.current(); } } }; window.addEventListener('mindsift:ws', handleWs); return () => window.removeEventListener('mindsift:ws', handleWs); }, []); const safeTaskLists = Array.isArray(taskLists) ? taskLists : []; const currentSelectedList = safeTaskLists.find((l) => l.id === selectedListId); // Active Kanban Columns (from selected list, or global custom, or default) const activeColumns: KanbanColumn[] = useMemo(() => { if ( currentSelectedList && Array.isArray(currentSelectedList.columns) && currentSelectedList.columns.length > 0 ) { return currentSelectedList.columns; } return globalColumns; }, [currentSelectedList, globalColumns]); const safeTasks = useMemo(() => { if (!Array.isArray(tasks)) return []; const seen = new Set(); const unique: Thought[] = []; for (const t of tasks) { if (t && t.id && !seen.has(t.id)) { seen.add(t.id); unique.push(t); } } return unique; }, [tasks]); const detailTask = useMemo(() => { if (!detailTaskId) return null; return tasks.find((t) => t.id === detailTaskId) || null; }, [detailTaskId, tasks]); const safeTeams = Array.isArray(teams) ? teams : []; const safeCollaborators = Array.isArray(collaborators) ? collaborators : []; const safeDiscarded = Array.isArray(discarded) ? discarded : []; // Available unique hashtags from current tasks const allAvailableTags = useMemo(() => { const set = new Set(); for (const t of safeTasks) { if (Array.isArray(t.tags)) { for (const tg of t.tags) { if (tg) set.add(tg); } } } return Array.from(set).sort(); }, [safeTasks]); // Filtered tasks for List view const openTasks = safeTasks.filter((t) => !t.isCompleted); const doneTasks = safeTasks.filter((t) => t.isCompleted); const filteredTasks = useMemo(() => { let result = safeTasks; if (filter === 'open') { result = result.filter((t) => !t.isCompleted); } else if (filter === 'done') { result = result.filter((t) => t.isCompleted); } if (selectedTag) { result = result.filter((t) => Array.isArray(t.tags) && t.tags.includes(selectedTag)); } return [...result].sort((a, b) => { if (sortBy === 'due') { if (!a.dueDate && !b.dueDate) return 0; if (!a.dueDate) return 1; if (!b.dueDate) return -1; return a.dueDate.localeCompare(b.dueDate); } if (sortBy === 'priority') { const pScore: Record = { urgent: 3, high: 2, normal: 1, low: 0 }; const scoreA = pScore[a.priority] || 1; const scoreB = pScore[b.priority] || 1; return scoreB - scoreA; } if (sortBy === 'alpha') { return a.content.localeCompare(b.content); } return b.createdAt.localeCompare(a.createdAt); }); }, [safeTasks, filter, selectedTag, sortBy]); // Tasks for Kanban board (shows all non-discarded matching tag) const kanbanTasks = useMemo(() => { let result = safeTasks; if (selectedTag) { result = result.filter((t) => Array.isArray(t.tags) && t.tags.includes(selectedTag)); } return result; }, [safeTasks, selectedTag]); // Grouped tasks calculation const groupedTasks = useMemo(() => { if (groupBy === 'none') return null; const groups: { key: string; title: string; count: number; tasks: Thought[] }[] = []; if (groupBy === 'tag') { const map = new Map(); const withoutTag: Thought[] = []; for (const t of filteredTasks) { if (!t.tags || t.tags.length === 0) { withoutTag.push(t); } else { for (const tg of t.tags) { if (!map.has(tg)) map.set(tg, []); map.get(tg)!.push(t); } } } Array.from(map.keys()).sort().forEach((tg) => { const list = map.get(tg)!; groups.push({ key: `tag:${tg}`, title: `#${tg}`, count: list.length, tasks: list }); }); if (withoutTag.length > 0) { groups.push({ key: 'tag:none', title: 'Ohne Hashtag', count: withoutTag.length, tasks: withoutTag }); } } else if (groupBy === 'status') { activeColumns.forEach((col) => { const list = filteredTasks.filter((t) => { if (col.isDone) return t.isCompleted || t.kanbanStatus === col.id; return !t.isCompleted && (t.kanbanStatus === col.id || (!t.kanbanStatus && col.id === 'todo')); }); groups.push({ key: `status:${col.id}`, title: col.name, count: list.length, tasks: list }); }); } else if (groupBy === 'priority') { const urgent = filteredTasks.filter((t) => t.priority === 'urgent'); const high = filteredTasks.filter((t) => t.priority === 'high'); const normal = filteredTasks.filter((t) => t.priority !== 'urgent' && t.priority !== 'high'); if (urgent.length > 0) groups.push({ key: 'p:urgent', title: '🔥 Dringend', count: urgent.length, tasks: urgent }); if (high.length > 0) groups.push({ key: 'p:high', title: '⚠️ Wichtig', count: high.length, tasks: high }); if (normal.length > 0) groups.push({ key: 'p:normal', title: 'Normal', count: normal.length, tasks: normal }); } else if (groupBy === 'due') { const today = new Date().toISOString().split('T')[0]; const overdue = filteredTasks.filter((t) => t.dueDate && t.dueDate < today); const dueToday = filteredTasks.filter((t) => t.dueDate === today); const upcoming = filteredTasks.filter((t) => t.dueDate && t.dueDate > today); const noDue = filteredTasks.filter((t) => !t.dueDate); if (overdue.length > 0) groups.push({ key: 'due:overdue', title: '⚠️ Überfällig', count: overdue.length, tasks: overdue }); if (dueToday.length > 0) groups.push({ key: 'due:today', title: '📅 Heute fällig', count: dueToday.length, tasks: dueToday }); if (upcoming.length > 0) groups.push({ key: 'due:upcoming', title: 'Demnächst', count: upcoming.length, tasks: upcoming }); if (noDue.length > 0) groups.push({ key: 'due:none', title: 'Ohne Fälligkeitsdatum', count: noDue.length, tasks: noDue }); } return groups; }, [filteredTasks, groupBy, activeColumns]); // --- TASK ACTIONS --- const handleToggleDone = async (task: Thought) => { triggerHaptic(15); const nextState = !task.isCompleted; const doneCol = activeColumns.find((c) => c.isDone) || activeColumns[activeColumns.length - 1]; const todoCol = activeColumns.find((c) => !c.isDone) || activeColumns[0]; const nextKanban = nextState ? (doneCol ? doneCol.id : 'done') : (todoCol ? todoCol.id : 'todo'); setTasks((prev) => prev.map((t) => t.id === task.id ? { ...t, isCompleted: nextState, kanbanStatus: nextKanban } : t ) ); await api.updateThought(task.id, { isCompleted: nextState, kanbanStatus: nextKanban }); onRefreshStats(); }; const handleSetKanbanStatus = async (task: Thought, nextStatus: string) => { triggerHaptic(15); const targetCol = activeColumns.find((c) => c.id === nextStatus); const isCompleted = targetCol ? !!targetCol.isDone : nextStatus === 'done'; setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, kanbanStatus: nextStatus, isCompleted } : t)) ); await api.updateThought(task.id, { kanbanStatus: nextStatus, isCompleted }); onRefreshStats(); }; const handleSetPriority = async (task: Thought, nextPriority: string) => { triggerHaptic(10); const normalized = nextPriority === 'none' || nextPriority === '' ? 'normal' : nextPriority; setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, priority: normalized as any } : t)) ); await api.updateThought(task.id, { priority: normalized }); onRefreshStats(); }; const handleCyclePriority = async (task: Thought) => { triggerHaptic(10); const nextPriority = task.priority === 'urgent' ? 'normal' : task.priority === 'high' ? 'urgent' : 'high'; await handleSetPriority(task, nextPriority); }; const handleUpdateTaskTitle = async (task: Thought, newTitle: string) => { setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, content: newTitle } : t))); await api.updateThought(task.id, { content: newTitle }); onRefreshStats(); }; const handleUpdateTaskDueDate = async (task: Thought, newDate: string) => { setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, dueDate: newDate } : t))); await api.updateThought(task.id, { dueDate: newDate }); }; const handleUpdateTaskTaskList = async (task: Thought, newListId: string) => { setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, taskListId: newListId } : t))); await api.updateThought(task.id, { taskListId: newListId }); if (selectedListId !== 'all' && selectedListId !== newListId) { setTasks((prev) => prev.filter((t) => t.id !== task.id)); } onRefreshStats(); }; const handleUpdateTaskNotes = async (task: Thought, newNotes: string) => { setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, notes: newNotes } : t))); await api.updateThought(task.id, { notes: newNotes }); }; const handleUpdateTaskTags = async (task: Thought, newTags: string[]) => { setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, tags: newTags } : t))); await api.updateThought(task.id, { tags: newTags }); onRefreshStats(); }; const handleToggleSubtask = async (task: Thought, subtaskId: string) => { triggerHaptic(10); const updatedSubtasks = (task.subtasks || []).map((s) => s.id === subtaskId ? { ...s, isCompleted: !s.isCompleted } : s ); setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, subtasks: updatedSubtasks } : t)) ); await api.updateThought(task.id, { subtasks: updatedSubtasks }); }; const handleAddSubtask = async (task: Thought, title: string) => { triggerHaptic(10); const newSub: SubTask = { id: `st_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, title: title.trim(), isCompleted: false, createdAt: new Date().toISOString(), }; const updatedSubtasks = [...(task.subtasks || []), newSub]; setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, subtasks: updatedSubtasks } : t)) ); await api.updateThought(task.id, { subtasks: updatedSubtasks }); }; const handleDeleteSubtask = async (task: Thought, subtaskId: string) => { triggerHaptic(10); const updatedSubtasks = (task.subtasks || []).filter((s) => s.id !== subtaskId); setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, subtasks: updatedSubtasks } : t)) ); await api.updateThought(task.id, { subtasks: updatedSubtasks }); }; const handleConvertToDoc = async (task: Thought) => { triggerHaptic(20); const updated = await api.triageAction(task.id, 'keep', { convertTo: 'document' }); onRefreshStats(); if (updated.convertedRefId) { onOpenDocument(updated.convertedRefId); } else { loadData(); } }; const handleRestore = async (item: Thought) => { triggerHaptic(15); await api.triageAction(item.id, 'restore'); await loadData(); onRefreshStats(); }; const handleDeletePermanent = async (id: string) => { triggerHaptic(20); await api.deleteThought(id); await loadData(); onRefreshStats(); }; const handlePurgeAllDiscarded = async () => { if (!window.confirm('Alle verworfenen Gedanken endgültig löschen?')) return; triggerHaptic(20); await api.purgeDiscarded(); await loadData(); onRefreshStats(); }; const handleQuickAddTask = async (e: React.FormEvent) => { e.preventDefault(); const title = quickTitle.trim(); if (!title) return; triggerHaptic(10); setQuickTitle(''); try { const created = await api.captureThought(title, 'normal', 'manual', quickDueDate, { target: 'task', taskListId: selectedListId === 'all' ? '' : selectedListId, }); setTasks((prev) => { if (prev.some((t) => t.id === created.id)) return prev; return [created, ...prev]; }); onRefreshStats(); } catch { loadData(); } }; const handleQuickAddTaskToColumn = async (columnId: string) => { const title = prompt('Neue Aufgabe für diese Spalte:'); if (!title || !title.trim()) return; triggerHaptic(10); const targetCol = activeColumns.find((c) => c.id === columnId); const isCompleted = targetCol ? !!targetCol.isDone : columnId === 'done'; try { const created = await api.captureThought(title.trim(), 'normal', 'manual', undefined, { target: 'task', taskListId: selectedListId === 'all' ? '' : selectedListId, }); if (created && created.id) { const updated = await api.updateThought(created.id, { kanbanStatus: columnId, isCompleted }); setTasks((prev) => [updated, ...prev]); onRefreshStats(); } } catch { loadData(); } }; const handleCreateTaskList = async (e: React.FormEvent) => { e.preventDefault(); const title = newListTitle.trim(); if (!title) return; try { let teamId: string | undefined = undefined; let userId: string | undefined = undefined; if (newListShareTarget.startsWith('team:')) { teamId = newListShareTarget.replace('team:', ''); } else if (newListShareTarget.startsWith('user:')) { userId = newListShareTarget.replace('user:', ''); } const created = await api.createTaskList({ title, icon: newListIcon, teamId: teamId, }); if (userId && created?.id) { await api.addTaskListShare(created.id, { targetType: 'user', targetId: userId }); } const freshLists = await api.getTaskLists(); setTaskLists(freshLists); setSelectedListId(created.id); setShowNewListModal(false); setNewListTitle(''); setNewListShareTarget(''); } catch (err: any) { alert(err.message || 'Fehler beim Erstellen der Aufgabenliste'); } }; const handleDeleteTaskList = async (listId: string) => { if ( !window.confirm( 'Möchten Sie diese Aufgabenliste wirklich löschen? Die enthaltenen Aufgaben bleiben erhalten.' ) ) return; try { await api.deleteTaskList(listId); setTaskLists((prev) => prev.filter((l) => l.id !== listId)); setSelectedListId('all'); } catch (err: any) { alert(err.message); } }; const handleOpenShareModal = async (tl: TaskList) => { setSharingTaskList(tl); setSelectedShareTargetId(''); setSelectedShareUser(null); setShowShareListModal(true); try { const [freshTeams, freshUsers, freshShares] = await Promise.all([ api.getUserTeams().catch(() => []), api.getUsersDirectory ? api.getUsersDirectory().catch(() => api.getCollaborators().catch(() => [])) : api.getCollaborators().catch(() => []), api.getTaskListShares(tl.id).catch(() => []), ]); setTeams(freshTeams); setCollaborators(freshUsers); setSharingTaskList((prev) => (prev && prev.id === tl.id ? { ...prev, shares: freshShares } : prev)); } catch { // ignore } }; const handleAddShare = async () => { if (!sharingTaskList) return; const targetId = addShareType === 'team' ? selectedShareTargetId : selectedShareUser?.id; if (!targetId) return; setShareLoading(true); triggerHaptic(15); try { await api.addTaskListShare(sharingTaskList.id, { targetType: addShareType, targetId: targetId, }); const [freshLists, freshShares] = await Promise.all([ api.getTaskLists(), api.getTaskListShares(sharingTaskList.id).catch(() => []), ]); setTaskLists(freshLists); const updated = freshLists.find((l) => l.id === sharingTaskList.id); if (updated) { setSharingTaskList({ ...updated, shares: freshShares }); } setSelectedShareTargetId(''); setSelectedShareUser(null); await loadData(); } catch (err: any) { alert(err.message || 'Fehler beim Hinzufügen der Freigabe'); } finally { setShareLoading(false); } }; const handleRemoveShare = async (shareId: string) => { if (!sharingTaskList) return; if (!window.confirm('Freigabe wirklich aufheben?')) return; setShareLoading(true); triggerHaptic(15); try { await api.removeTaskListShare(sharingTaskList.id, shareId); const [freshLists, freshShares] = await Promise.all([ api.getTaskLists(), api.getTaskListShares(sharingTaskList.id).catch(() => []), ]); setTaskLists(freshLists); const updated = freshLists.find((l) => l.id === sharingTaskList.id); if (updated) { setSharingTaskList({ ...updated, shares: freshShares }); } await loadData(); } catch (err: any) { alert(err.message || 'Fehler beim Entfernen der Freigabe'); } finally { setShareLoading(false); } }; const handleSaveColumns = async (newColumns: KanbanColumn[]) => { triggerHaptic(15); if (currentSelectedList && selectedListId !== 'all') { const updated = await api.updateTaskList(currentSelectedList.id, { columns: newColumns }); setTaskLists((prev) => prev.map((l) => (l.id === updated.id ? updated : l))); } else { localStorage.setItem('mindsift:kanban_columns', JSON.stringify(newColumns)); setGlobalColumns(newColumns); } }; const renderTaskProgressBar = (task: Thought) => { if (!task.subtasks || task.subtasks.length === 0) return null; const total = task.subtasks.length; const done = task.subtasks.filter((s) => s.isCompleted).length; const percent = Math.round((done / total) * 100); return (
{done}/{total} Unteraufgaben {percent}%
); }; // Clean Task Row in List View const renderTaskRow = (task: Thought) => { const isCompleted = task.isCompleted; return (
{/* Checkbox */} {/* Clickable Title & Metadata */}
{ setDetailTaskId(task.id); setShowDetailModal(true); }} className="min-w-0 flex-1 cursor-pointer space-y-1" >

{task.content}

{/* Subtasks Progress Bar */} {renderTaskProgressBar(task)} {/* Interactive Badges Bar */}
e.stopPropagation()} > {/* Kanban status selector pill */} {/* Priority badge: click to cycle or clear */} {task.priority === 'urgent' && ( )} {task.priority === 'high' && ( )} {/* Note indicator */} {task.notes && ( )} {/* Due Date */} {task.dueDate && ( {task.dueDate} )} {/* Hashtag badges */} {task.tags?.map((tag) => ( ))}
{/* Action Controls */}
); }; return (
{/* Top Header: Task Lists Tabs & View Mode Switcher */}
{/* Task Lists selector tabs */}
{safeTaskLists.map((tl) => (
{selectedListId === tl.id && ( )}
))}
{/* Right Header Section: Sharing Badge, Icon-Only Button & View Switcher */}
{currentSelectedList && (() => { const listOwnerId = currentSelectedList.ownerId || currentSelectedList.userId; const isOwner = currentSelectedList.isOwner !== false && (!listOwnerId || listOwnerId === currentUser?.id); const sharesCount = (currentSelectedList.shares?.length || 0) > 0 ? currentSelectedList.shares.length : (currentSelectedList.members?.filter((m) => m.userId !== listOwnerId).length || 0); const isShared = sharesCount > 0 || Boolean(currentSelectedList.teamId) || ((currentSelectedList.members?.length || 0) > 1); const ownerMember = currentSelectedList.members?.find((m) => m.role === 'owner' || m.userId === listOwnerId); const ownerDisplayName = currentSelectedList.ownerName || ownerMember?.displayName || ownerMember?.email || 'EigentĂĽmer'; return (
{isShared && ( isOwner ? ( ) : ( ) )}
); })()} {/* View Switcher: List vs Kanban */}
{/* Quick Add Task Input */}
setQuickTitle(e.target.value)} placeholder={ currentSelectedList ? `Aufgabe zu "${currentSelectedList.title}" hinzufügen… (Enter)` : 'Neue Aufgabe anlegen… (Enter)' } className="flex-1 bg-transparent px-2 py-1 text-sm text-main placeholder-faint focus:outline-none" />
setQuickDueDate(e.target.value)} className="h-7 px-2 input-field text-xs" title="Fälligkeitsdatum" />
{/* Hashtag Filter & Grouping / Sorting Toolbar */}
{/* Hashtag filter chips */}
Hashtags: {allAvailableTags.map((tag) => { const count = safeTasks.filter((t) => t.tags?.includes(tag)).length; const isSelected = selectedTag === tag; return ( ); })}
{/* Group By & Sort By Dropdowns & Manage Columns button */}
{viewMode === 'kanban' ? ( ) : ( <> {/* Group By */}
Gruppe:
{/* Sort By */}
Sortierung:
)}
{/* NEW LIST MODAL */} {showNewListModal && (

Neue Aufgabenliste erstellen

setNewListIcon(e.target.value)} maxLength={2} className="w-10 h-8 text-center input-field text-sm" title="Icon (Emoji)" /> setNewListTitle(e.target.value)} placeholder="z.B. Homelab, Einkäufe, Steuern" autoFocus required className="flex-1 h-8 input-field px-2.5 text-xs" />
)} {/* UNIFIED SHARE LIST MODAL */} {showShareListModal && sharingTaskList && ( { setShowShareListModal(false); setSharingTaskList(null); }} title={`Aufgabenliste teilen: ${sharingTaskList.title}`} resourceName={sharingTaskList.title} resourceIcon={sharingTaskList.icon || 'đź“‹'} isOwner={sharingTaskList.isOwner !== false} currentUserId={currentUser?.id} shares={(sharingTaskList.shares || []).map((sh) => ({ id: sh.id, targetType: sh.targetType, targetId: sh.targetId, targetName: sh.targetName || (sh.targetType === 'team' ? 'Gruppe' : sh.email || 'Benutzer'), targetEmail: sh.email, permission: 'editor' as const, role: 'editor', createdAt: sh.createdAt, }))} teams={safeTeams} collaborators={safeCollaborators} allowRoleSelection={false} onAddShare={async (targetType, targetId) => { await api.addTaskListShare(sharingTaskList.id, { targetType, targetId, }); const [freshLists, freshShares] = await Promise.all([ api.getTaskLists(), api.getTaskListShares(sharingTaskList.id).catch(() => []), ]); setTaskLists(freshLists); const updated = freshLists.find((l) => l.id === sharingTaskList.id); if (updated) { setSharingTaskList({ ...updated, shares: freshShares }); } await loadData(); }} onRemoveShare={async (shareId) => { await api.removeTaskListShare(sharingTaskList.id, shareId); const [freshLists, freshShares] = await Promise.all([ api.getTaskLists(), api.getTaskListShares(sharingTaskList.id).catch(() => []), ]); setTaskLists(freshLists); const updated = freshLists.find((l) => l.id === sharingTaskList.id); if (updated) { setSharingTaskList({ ...updated, shares: freshShares }); } await loadData(); }} /> )} {/* MANAGE COLUMNS MODAL */} setShowManageColumnsModal(false)} initialColumns={activeColumns} onSave={handleSaveColumns} listTitle={currentSelectedList?.title || 'Alle Aufgaben'} /> {/* TASK DETAIL MODAL */} { setShowDetailModal(false); setDetailTaskId(null); }} activeColumns={activeColumns} taskLists={safeTaskLists} onUpdateTitle={handleUpdateTaskTitle} onUpdateStatus={handleSetKanbanStatus} onUpdatePriority={handleSetPriority} onUpdateDueDate={handleUpdateTaskDueDate} onUpdateTaskList={handleUpdateTaskTaskList} onUpdateNotes={handleUpdateTaskNotes} onUpdateTags={handleUpdateTaskTags} onToggleSubtask={handleToggleSubtask} onAddSubtask={handleAddSubtask} onDeleteSubtask={handleDeleteSubtask} onConvertToDoc={handleConvertToDoc} onDeleteTask={handleDeletePermanent} /> {/* VIEW MODES: LIST OR KANBAN */} {viewMode === 'kanban' ? ( /* DYNAMIC KANBAN BOARD */
{activeColumns.map((col, colIdx) => { const colTasks = kanbanTasks.filter((t) => { if (col.isDone) { return t.isCompleted || t.kanbanStatus === col.id; } if (t.isCompleted) return false; if (t.kanbanStatus) return t.kanbanStatus === col.id; return col.id === 'todo'; }); return (
{/* Column Header */}
{col.name} {colTasks.length}
{/* Tasks List in Column */}
{colTasks.map((task) => (
{ setDetailTaskId(task.id); setShowDetailModal(true); }} className="panel-surface p-2.5 space-y-2 hover:border-strong transition-all cursor-pointer shadow-sm group" >

{task.content}

e.stopPropagation()}>
{/* Subtasks progress */} {renderTaskProgressBar(task)} {/* Notes preview badge */} {task.notes && (
{task.notes}
)} {/* Badges: Priority, Status, Tags */}
e.stopPropagation()}> {task.priority === 'urgent' && ( )} {task.priority === 'high' && ( )} {task.dueDate && ( {task.dueDate} )} {task.tags?.map((tag) => ( ))}
{/* Column Navigation controls: Move Left / Move Right */}
e.stopPropagation()} >
))} {colTasks.length === 0 && (
Keine Aufgaben in dieser Spalte
)}
{/* Quick Add Task to Column */}
); })}
) : ( /* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */
{/* Subfilter tabs for List View */}
{filter === 'discarded' && safeDiscarded.length > 0 && ( )}
{loading ? (
Lade Aufgaben…
) : filter === 'discarded' ? (
{safeDiscarded.length === 0 ? (
Keine verworfenen Gedanken im Papierkorb.
) : ( safeDiscarded.map((item) => (

{item.content}

)) )}
) : (
{groupedTasks ? ( /* GROUPED LIST */ groupedTasks.map((grp) => (
{grp.title} {grp.count}
{grp.tasks.map((task) => renderTaskRow(task))}
)) ) : ( /* FLAT LIST */
{filteredTasks.length === 0 ? (

Keine Aufgaben gefunden

{selectedTag ? `Keine Aufgaben mit Hashtag #${selectedTag}.` : filter === 'done' ? 'Noch keine Aufgaben erledigt.' : 'Erstelle oben eine neue Aufgabe.'}

) : ( filteredTasks.map((task) => renderTaskRow(task)) )}
)}
)}
)}
); };