diff --git a/apps/todo/apps/backend/src/task/dto/query-tasks.dto.ts b/apps/todo/apps/backend/src/task/dto/query-tasks.dto.ts index fb7e0067c..cea00a0a3 100644 --- a/apps/todo/apps/backend/src/task/dto/query-tasks.dto.ts +++ b/apps/todo/apps/backend/src/task/dto/query-tasks.dto.ts @@ -28,7 +28,11 @@ export class QueryTasksDto { status?: TaskStatus; @IsOptional() - @Transform(({ value }) => value === 'true' || value === true) + @Transform(({ value }) => { + if (value === 'true' || value === true) return true; + if (value === 'false' || value === false) return false; + return undefined; + }) @IsBoolean() isCompleted?: boolean; diff --git a/apps/todo/apps/web/src/lib/components/SubtaskList.svelte b/apps/todo/apps/web/src/lib/components/SubtaskList.svelte new file mode 100644 index 000000000..b545e79ee --- /dev/null +++ b/apps/todo/apps/web/src/lib/components/SubtaskList.svelte @@ -0,0 +1,404 @@ + + +
+ {#if items.length > 0} +
+ {#each items as subtask (subtask.id)} +
+ +
+ + + +
+ + + + + + {#if editingId === subtask.id} + + {:else} + + {/if} + + + +
+ {/each} +
+ {/if} + + +
+
+ + + +
+ + {#if newSubtaskTitle.trim()} + + {/if} +
+
+ + diff --git a/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte b/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte new file mode 100644 index 000000000..2ed79e38b --- /dev/null +++ b/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte @@ -0,0 +1,1220 @@ + + + + +{#if open} + +{/if} + + diff --git a/apps/todo/apps/web/src/lib/components/TaskItem.svelte b/apps/todo/apps/web/src/lib/components/TaskItem.svelte index a1520f4fd..3ddaabe60 100644 --- a/apps/todo/apps/web/src/lib/components/TaskItem.svelte +++ b/apps/todo/apps/web/src/lib/components/TaskItem.svelte @@ -9,9 +9,16 @@ showCompleted?: boolean; onToggleComplete: () => void; onDelete: () => void; + onEdit?: () => void; } - let { task, showCompleted = false, onToggleComplete, onDelete }: Props = $props(); + let { task, showCompleted = false, onToggleComplete, onDelete, onEdit }: Props = $props(); + + function handleContentClick() { + if (onEdit) { + onEdit(); + } + } // Priority colors const priorityColors: Record = { @@ -67,8 +74,8 @@ {/if} - -
+ +
{/if} - + {#if projectColor()} @@ -232,6 +239,11 @@ display: flex; flex-direction: column; gap: 0.25rem; + background: none; + border: none; + padding: 0; + text-align: left; + cursor: pointer; } .task-title { diff --git a/apps/todo/apps/web/src/lib/components/TaskList.svelte b/apps/todo/apps/web/src/lib/components/TaskList.svelte index 51a8b7a94..0e7c66c65 100644 --- a/apps/todo/apps/web/src/lib/components/TaskList.svelte +++ b/apps/todo/apps/web/src/lib/components/TaskList.svelte @@ -6,9 +6,10 @@ interface Props { tasks: Task[]; showCompleted?: boolean; + onEditTask?: (task: Task) => void; } - let { tasks, showCompleted = false }: Props = $props(); + let { tasks, showCompleted = false, onEditTask }: Props = $props(); async function handleToggleComplete(task: Task) { if (task.isCompleted) { @@ -30,6 +31,7 @@ {showCompleted} onToggleComplete={() => handleToggleComplete(task)} onDelete={() => handleDelete(task.id)} + onEdit={onEditTask ? () => onEditTask(task) : undefined} /> {/each} diff --git a/apps/todo/apps/web/src/lib/stores/tasks.svelte.ts b/apps/todo/apps/web/src/lib/stores/tasks.svelte.ts index 4cc2f8348..483b3a1c1 100644 --- a/apps/todo/apps/web/src/lib/stores/tasks.svelte.ts +++ b/apps/todo/apps/web/src/lib/stores/tasks.svelte.ts @@ -119,18 +119,10 @@ export const tasksStore = { loading = true; error = null; try { - // Fetch both incomplete and completed tasks - const [incompleteTasks, completedTasks] = await Promise.all([ - tasksApi.getTasks({ isCompleted: false }), - tasksApi.getTasks({ isCompleted: true }), - ]); - // Deduplicate tasks by ID (in case API returns duplicates) - const allTasks = [...incompleteTasks, ...completedTasks]; - const uniqueTasksMap = new Map(); - for (const task of allTasks) { - uniqueTasksMap.set(task.id, task); - } - tasks = Array.from(uniqueTasksMap.values()); + // Fetch all tasks without filter - let frontend handle filtering + const allTasks = await tasksApi.getTasks({}); + console.log('API response - all tasks:', allTasks.length); + tasks = allTasks; } catch (e) { error = e instanceof Error ? e.message : 'Failed to fetch all tasks'; console.error('Failed to fetch all tasks:', e); @@ -171,9 +163,13 @@ export const tasksStore = { * Get tasks due today */ get todayTasks(): Task[] { + const today = startOfDay(new Date()); return tasks.filter((t) => { - if (!t.dueDate || t.isCompleted) return false; - return isToday(new Date(t.dueDate)); + if (t.isCompleted) return false; + // Include tasks without dueDate as "today" tasks (inbox behavior) + if (!t.dueDate) return true; + const taskDate = startOfDay(new Date(t.dueDate)); + return taskDate.getTime() === today.getTime(); }); }, diff --git a/apps/todo/apps/web/src/routes/(app)/+page.svelte b/apps/todo/apps/web/src/routes/(app)/+page.svelte index b816ccadb..a4818a266 100644 --- a/apps/todo/apps/web/src/routes/(app)/+page.svelte +++ b/apps/todo/apps/web/src/routes/(app)/+page.svelte @@ -10,9 +10,11 @@ import TaskList from '$lib/components/TaskList.svelte'; import QuickAddTask from '$lib/components/QuickAddTask.svelte'; import CollapsibleSection from '$lib/components/CollapsibleSection.svelte'; + import TaskEditModal from '$lib/components/TaskEditModal.svelte'; import type { Task } from '@todo/shared'; let isLoading = $state(true); + let editingTask = $state(null); onMount(async () => { if (!authStore.isAuthenticated) { @@ -21,7 +23,13 @@ } viewStore.setToday(); - await tasksStore.fetchAllTasks(); + + try { + await tasksStore.fetchAllTasks(); + } catch (error) { + console.error('Failed to load tasks:', error); + } + isLoading = false; }); @@ -71,6 +79,42 @@ upcomingCount === 0 && completedTasks.length === 0 ); + + // Modal handlers + function openEditModal(task: Task) { + editingTask = task; + } + + function closeEditModal() { + editingTask = null; + } + + async function handleSaveTask(data: Partial) { + if (!editingTask) return; + + try { + // Update task + await tasksStore.updateTask(editingTask.id, data); + + // Update labels if provided + if ('labelIds' in data) { + await tasksStore.updateLabels(editingTask.id, (data as any).labelIds); + } + + closeEditModal(); + } catch (error) { + console.error('Failed to save task:', error); + } + } + + async function handleDeleteTask(taskId: string) { + try { + await tasksStore.deleteTask(taskId); + closeEditModal(); + } catch (error) { + console.error('Failed to delete task:', error); + } + } @@ -114,7 +158,7 @@ variant="warning" defaultOpen={true} > - + {/if} @@ -131,7 +175,7 @@

Keine Aufgaben für heute

{:else} - + {/if} @@ -154,7 +198,7 @@

{group.label} ({group.tasks.length})

- + {/each} @@ -174,13 +218,24 @@

Noch keine erledigten Aufgaben

{:else} - + {/if} {/if} + +{#if editingTask} + +{/if} +