From f408d7046196786dcaffa1caf181ace95e6246b7 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 2 Apr 2026 11:12:57 +0200 Subject: [PATCH] feat(manacore/web): refactor todo page into modular components with i18n Extract the monolithic todo page into reusable components (TaskList, TaskItem, TaskEditModal, QuickAddTask, TodoToolbar, etc.), add new stores (reminders, contacts, settings, minimized-pages), composables (useTaskForm), board view components, skeleton loading states, and a settings page. Add todo i18n strings for de/en/es/fr. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/i18n/locales/de.json | 181 ++++++ .../apps/web/src/lib/i18n/locales/en.json | 181 ++++++ .../apps/web/src/lib/i18n/locales/es.json | 139 ++++ .../apps/web/src/lib/i18n/locales/fr.json | 139 ++++ .../todo/components/MinimizedTabs.svelte | 34 + .../todo/components/OnboardingModal.svelte | 102 +++ .../todo/components/QuickAddTask.svelte | 196 ++++++ .../todo/components/SubtaskList.svelte | 104 +++ .../todo/components/SyncIndicator.svelte | 34 + .../todo/components/SyntaxHelpOverlay.svelte | 132 ++++ .../modules/todo/components/TagStrip.svelte | 48 ++ .../todo/components/TaskEditModal.svelte | 315 +++++++++ .../modules/todo/components/TaskItem.svelte | 132 ++++ .../modules/todo/components/TaskList.svelte | 172 +++++ .../todo/components/TodoToolbar.svelte | 124 ++++ .../board-views/BoardViewRenderer.svelte | 51 ++ .../components/board-views/FokusLayout.svelte | 90 +++ .../components/board-views/GridLayout.svelte | 47 ++ .../board-views/KanbanLayout.svelte | 62 ++ .../components/board-views/ViewColumn.svelte | 100 +++ .../board-views/ViewColumnHeader.svelte | 26 + .../board-views/ViewEditorModal.svelte | 228 +++++++ .../board-views/ViewSelector.svelte | 47 ++ .../todo/components/board-views/index.ts | 8 + .../components/form/DurationPicker.svelte | 49 ++ .../components/form/FunRatingPicker.svelte | 41 ++ .../components/form/PrioritySelector.svelte | 32 + .../components/form/ReminderSelector.svelte | 37 ++ .../form/StorypointsSelector.svelte | 30 + .../todo/components/form/TagSelector.svelte | 92 +++ .../lib/modules/todo/components/form/index.ts | 6 + .../skeletons/KanbanBoardSkeleton.svelte | 15 + .../skeletons/KanbanColumnSkeleton.svelte | 16 + .../skeletons/StatisticsSkeleton.svelte | 5 + .../skeletons/TaskItemSkeleton.svelte | 10 + .../skeletons/TaskListSkeleton.svelte | 15 + .../todo/components/skeletons/index.ts | 5 + .../todo/composables/useTaskForm.svelte.ts | 233 +++++++ .../apps/web/src/lib/modules/todo/index.ts | 29 + .../modules/todo/stores/contacts.svelte.ts | 66 ++ .../todo/stores/minimized-pages.svelte.ts | 63 ++ .../modules/todo/stores/reminders.svelte.ts | 61 ++ .../lib/modules/todo/utils/time-estimator.ts | 100 +++ .../web/src/routes/(app)/todo/+page.svelte | 603 +++++++----------- .../routes/(app)/todo/settings/+page.svelte | 345 ++++++++++ 45 files changed, 4169 insertions(+), 376 deletions(-) create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/MinimizedTabs.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/OnboardingModal.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/QuickAddTask.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/SubtaskList.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/SyncIndicator.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/SyntaxHelpOverlay.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/TagStrip.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/TaskEditModal.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/TaskItem.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/TaskList.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/TodoToolbar.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/BoardViewRenderer.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/FokusLayout.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/GridLayout.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/KanbanLayout.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumn.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumnHeader.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewEditorModal.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewSelector.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/board-views/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/DurationPicker.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/FunRatingPicker.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/PrioritySelector.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/ReminderSelector.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/StorypointsSelector.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/TagSelector.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/form/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanBoardSkeleton.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanColumnSkeleton.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/StatisticsSkeleton.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskItemSkeleton.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskListSkeleton.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/stores/contacts.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/stores/minimized-pages.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/todo/utils/time-estimator.ts create mode 100644 apps/manacore/apps/web/src/routes/(app)/todo/settings/+page.svelte diff --git a/apps/manacore/apps/web/src/lib/i18n/locales/de.json b/apps/manacore/apps/web/src/lib/i18n/locales/de.json index e9b7c5d2b..aa524eb2d 100644 --- a/apps/manacore/apps/web/src/lib/i18n/locales/de.json +++ b/apps/manacore/apps/web/src/lib/i18n/locales/de.json @@ -187,6 +187,187 @@ "invoices": "Rechnungen", "no_invoices": "Noch keine Rechnungen vorhanden" }, + "todo": { + "title": "Todo", + "tasks": "Aufgaben", + "completed": "erledigt", + "overdue": "überfällig", + "today": "heute", + "inbox": "Inbox", + "todayView": "Heute", + "upcoming": "Bald fällig", + "completedView": "Erledigt", + "search": "Suche", + "newTask": "Neue Aufgabe", + "quickAddPlaceholder": "z.B. 'Meeting morgen um 14 Uhr !hoch #wichtig'", + "addTask": "Hinzufügen", + "cancel": "Abb.", + "syntaxHelp": "Syntax-Hilfe", + "noTasks": "Keine Aufgaben", + "noTasksInbox": "Inbox ist leer", + "noTasksToday": "Keine Aufgaben für heute", + "noTasksUpcoming": "Keine anstehenden Aufgaben", + "noTasksCompleted": "Noch keine Aufgaben erledigt", + "firstTaskHint": "Erstelle deine erste Aufgabe mit dem + Button oben.", + "edit": "Bearbeiten", + "markDone": "Erledigen", + "reopen": "Wieder öffnen", + "deleteConfirm": "Wirklich löschen?", + "yesDelete": "Ja, löschen", + "save": "Speichern", + "close": "Schließen", + "description": "Beschreibung", + "addDescription": "Beschreibung hinzufügen...", + "subtasks": "Teilaufgaben", + "addSubtask": "Teilaufgabe hinzufügen...", + "status": "Status", + "statusPending": "Offen", + "statusInProgress": "In Arbeit", + "statusCompleted": "Erledigt", + "statusCancelled": "Abgebrochen", + "priority": "Priorität", + "priorityUrgent": "Dringend", + "priorityHigh": "Hoch", + "priorityMedium": "Mittel", + "priorityLow": "Niedrig", + "dueDate": "Fällig", + "time": "Uhrzeit", + "startDate": "Start", + "recurrence": "Wiederholung", + "recurrenceNone": "Keine", + "recurrenceDaily": "Täglich", + "recurrenceWeekly": "Wöchentlich", + "recurrenceMonthly": "Monatlich", + "recurrenceYearly": "Jährlich", + "reminder": "Erinnerung", + "reminderNone": "Keine", + "tags": "Tags", + "storypoints": "Punkte", + "duration": "Dauer", + "fun": "Spaß", + "projects": "Projekte", + "labels": "Labels", + "sort": "Sortierung", + "sortManual": "Manuell", + "sortDueDate": "Fälligkeit", + "sortPriority": "Priorität", + "sortName": "Name", + "sortCreated": "Erstellt", + "showCompleted": "Erledigt", + "boardView": "Board", + "listView": "Liste", + "board": { + "new": "Neues Board", + "edit": "Board bearbeiten", + "create": "Board erstellen", + "name": "Board-Name...", + "groupBy": "Gruppierung", + "layout": "Layout", + "columns": "Spalten", + "addColumn": "Spalte", + "columnName": "Spaltenname...", + "delete": "Löschen", + "noTasks": "Keine Aufgaben", + "groupStatus": "Status", + "groupPriority": "Priorität", + "groupDueDate": "Fälligkeit", + "groupTag": "Tag", + "groupCustom": "Benutzerdefiniert", + "layoutKanban": "Kanban", + "layoutGrid": "Grid", + "layoutFocus": "Fokus" + }, + "settings": { + "title": "Todo Einstellungen", + "taskBehavior": "Aufgaben-Verhalten", + "defaultPriority": "Standard-Priorität", + "defaultDueTime": "Standard-Fälligkeit", + "autoArchive": "Auto-Archivierung (Tage)", + "viewDisplay": "Ansicht & Darstellung", + "defaultView": "Standard-Ansicht", + "compactMode": "Kompaktmodus", + "showTaskCounts": "Aufgabenzahl anzeigen", + "showSubtaskProgress": "Teilaufgaben-Fortschritt", + "groupByProject": "Nach Projekt gruppieren", + "kanbanSettings": "Kanban Board", + "cardSize": "Kartengröße", + "cardSizeCompact": "Kompakt", + "cardSizeNormal": "Normal", + "cardSizeLarge": "Groß", + "showLabelsOnCards": "Labels auf Karten", + "wipLimit": "WIP-Limit pro Spalte", + "notifications": "Benachrichtigungen", + "defaultReminder": "Standard-Erinnerung", + "dailyDigest": "Tägliche Zusammenfassung", + "overdueNotifications": "Überfällig-Benachrichtigungen", + "smartDuration": "Smarte Dauer", + "smartDurationEnabled": "Smarte Dauer-Schätzung", + "defaultTaskDuration": "Standard-Dauer (Min.)", + "productivity": "Produktivität", + "focusMode": "Fokus-Modus", + "pomodoro": "Pomodoro", + "dailyGoal": "Tagesziel", + "showStreak": "Streak anzeigen", + "immersiveMode": "Immersiver Modus", + "reset": "Einstellungen zurücksetzen" + }, + "syntaxHelpContent": { + "title": "Quick-Add Syntax", + "date": "Datum: heute, morgen, nächsten Montag, 15.12.", + "time": "Uhrzeit: um 14 Uhr, 14:00", + "priority": "Priorität: !hoch, !niedrig, !dringend, !!!", + "labels": "Labels: #wichtig #idee", + "duration": "Dauer: 30min, 2h, 1.5 Stunden", + "recurrence": "Wiederholung: jeden Tag, wöchentlich", + "multi": "Mehrere: Task1, danach Task2", + "subtasks": "Subtasks: Titel: item1, item2, item3" + }, + "onboarding": { + "welcome": "Willkommen bei Todo!", + "intro": "Dein persönlicher Aufgabenplaner mit Kanban-Boards, smarter Eingabe und mehr.", + "step1Title": "Natürliche Sprache", + "step1": "Erstelle Aufgaben wie \"Meeting morgen um 14 Uhr !hoch #arbeit\" — Datum, Priorität und Labels werden automatisch erkannt.", + "step2Title": "Kanban-Boards", + "step2": "Organisiere deine Aufgaben visuell mit verschiedenen Board-Ansichten: Status, Priorität oder benutzerdefiniert.", + "step3Title": "Los geht's!", + "step3": "Erstelle deine erste Aufgabe mit dem + Button. Tipps findest du jederzeit über das ? Symbol.", + "next": "Weiter", + "letsGo": "Los geht's", + "getStarted": "Los geht's" + }, + "syntaxHelp": { + "title": "Quick-Add Syntax", + "description": "Nutze natürliche Sprache, um Aufgaben schnell zu erstellen. Alle Muster werden automatisch erkannt.", + "date": "Datum", + "dateToday": "Heute", + "dateTomorrow": "Morgen", + "dateNextWeekday": "Nächster Wochentag", + "dateSpecific": "Bestimmtes Datum", + "time": "Uhrzeit", + "priority": "Priorität", + "priorityUrgent": "Dringend", + "priorityHigh": "Hoch", + "priorityLow": "Niedrig", + "labels": "Labels", + "labelsAdd": "Tags hinzufügen", + "duration": "Dauer", + "duration30m": "30 Minuten", + "duration2h": "2 Stunden", + "duration90m": "90 Minuten", + "recurrence": "Wiederholung", + "recurrenceDaily": "Täglich", + "recurrenceWeekly": "Wöchentlich", + "recurrenceMonthly": "Monatlich", + "multiTask": "Mehrere Aufgaben", + "multiTaskChain": "Aufgaben verketten", + "multiTaskSemicolon": "Mit Semikolon trennen", + "subtasks": "Subtasks", + "subtasksColonComma": "Doppelpunkt + Komma", + "exampleTitle": "Beispiel", + "exampleInput": "Meeting morgen um 14 Uhr 1h !hoch #arbeit", + "exampleOutput": "→ \"Meeting\" am nächsten Tag um 14:00, Dauer 1h, Priorität Hoch, Label \"arbeit\"" + } + }, "app_slider": { "title": "Teil des Mana Ökosystems", "memoro_desc": "KI-gestützte Sprachnotizen", diff --git a/apps/manacore/apps/web/src/lib/i18n/locales/en.json b/apps/manacore/apps/web/src/lib/i18n/locales/en.json index 2f77ea482..91fbec09a 100644 --- a/apps/manacore/apps/web/src/lib/i18n/locales/en.json +++ b/apps/manacore/apps/web/src/lib/i18n/locales/en.json @@ -187,6 +187,187 @@ "invoices": "Invoices", "no_invoices": "No invoices yet" }, + "todo": { + "title": "Todo", + "tasks": "Tasks", + "completed": "completed", + "overdue": "overdue", + "today": "today", + "inbox": "Inbox", + "todayView": "Today", + "upcoming": "Upcoming", + "completedView": "Completed", + "search": "Search", + "newTask": "New task", + "quickAddPlaceholder": "e.g. 'Meeting tomorrow at 2pm !high #important'", + "addTask": "Add", + "cancel": "Cancel", + "syntaxHelp": "Syntax help", + "noTasks": "No tasks", + "noTasksInbox": "Inbox is empty", + "noTasksToday": "No tasks for today", + "noTasksUpcoming": "No upcoming tasks", + "noTasksCompleted": "No tasks completed yet", + "firstTaskHint": "Create your first task with the + button above.", + "edit": "Edit", + "markDone": "Complete", + "reopen": "Reopen", + "deleteConfirm": "Really delete?", + "yesDelete": "Yes, delete", + "save": "Save", + "close": "Close", + "description": "Description", + "addDescription": "Add description...", + "subtasks": "Subtasks", + "addSubtask": "Add subtask...", + "status": "Status", + "statusPending": "Open", + "statusInProgress": "In Progress", + "statusCompleted": "Completed", + "statusCancelled": "Cancelled", + "priority": "Priority", + "priorityUrgent": "Urgent", + "priorityHigh": "High", + "priorityMedium": "Medium", + "priorityLow": "Low", + "dueDate": "Due", + "time": "Time", + "startDate": "Start", + "recurrence": "Recurrence", + "recurrenceNone": "None", + "recurrenceDaily": "Daily", + "recurrenceWeekly": "Weekly", + "recurrenceMonthly": "Monthly", + "recurrenceYearly": "Yearly", + "reminder": "Reminder", + "reminderNone": "None", + "tags": "Tags", + "storypoints": "Points", + "duration": "Duration", + "fun": "Fun", + "projects": "Projects", + "labels": "Labels", + "sort": "Sort", + "sortManual": "Manual", + "sortDueDate": "Due date", + "sortPriority": "Priority", + "sortName": "Name", + "sortCreated": "Created", + "showCompleted": "Completed", + "boardView": "Board", + "listView": "List", + "board": { + "new": "New board", + "edit": "Edit board", + "create": "Create board", + "name": "Board name...", + "groupBy": "Group by", + "layout": "Layout", + "columns": "Columns", + "addColumn": "Column", + "columnName": "Column name...", + "delete": "Delete", + "noTasks": "No tasks", + "groupStatus": "Status", + "groupPriority": "Priority", + "groupDueDate": "Due date", + "groupTag": "Tag", + "groupCustom": "Custom", + "layoutKanban": "Kanban", + "layoutGrid": "Grid", + "layoutFocus": "Focus" + }, + "settings": { + "title": "Todo Settings", + "taskBehavior": "Task Behavior", + "defaultPriority": "Default priority", + "defaultDueTime": "Default due time", + "autoArchive": "Auto-archive (days)", + "viewDisplay": "View & Display", + "defaultView": "Default view", + "compactMode": "Compact mode", + "showTaskCounts": "Show task counts", + "showSubtaskProgress": "Subtask progress", + "groupByProject": "Group by project", + "kanbanSettings": "Kanban Board", + "cardSize": "Card size", + "cardSizeCompact": "Compact", + "cardSizeNormal": "Normal", + "cardSizeLarge": "Large", + "showLabelsOnCards": "Labels on cards", + "wipLimit": "WIP limit per column", + "notifications": "Notifications", + "defaultReminder": "Default reminder", + "dailyDigest": "Daily digest", + "overdueNotifications": "Overdue notifications", + "smartDuration": "Smart Duration", + "smartDurationEnabled": "Smart duration estimation", + "defaultTaskDuration": "Default duration (min)", + "productivity": "Productivity", + "focusMode": "Focus mode", + "pomodoro": "Pomodoro", + "dailyGoal": "Daily goal", + "showStreak": "Show streak", + "immersiveMode": "Immersive mode", + "reset": "Reset settings" + }, + "syntaxHelpContent": { + "title": "Quick-Add Syntax", + "date": "Date: today, tomorrow, next Monday, Dec 15", + "time": "Time: at 2pm, 14:00", + "priority": "Priority: !high, !low, !urgent, !!!", + "labels": "Labels: #important #idea", + "duration": "Duration: 30min, 2h, 1.5 hours", + "recurrence": "Recurrence: every day, weekly", + "multi": "Multiple: Task1, then Task2", + "subtasks": "Subtasks: Title: item1, item2, item3" + }, + "onboarding": { + "welcome": "Welcome to Todo!", + "intro": "Your personal task planner with Kanban boards, smart input, and more.", + "step1Title": "Natural Language", + "step1": "Create tasks like \"Meeting tomorrow at 2pm !high #work\" — date, priority and labels are detected automatically.", + "step2Title": "Kanban Boards", + "step2": "Organize your tasks visually with different board views: status, priority, or custom.", + "step3Title": "Let's go!", + "step3": "Create your first task with the + button. Tips are always available via the ? icon.", + "next": "Next", + "letsGo": "Let's go", + "getStarted": "Get started" + }, + "syntaxHelp": { + "title": "Quick-Add Syntax", + "description": "Use natural language to quickly create tasks. All patterns are automatically detected.", + "date": "Date", + "dateToday": "Today", + "dateTomorrow": "Tomorrow", + "dateNextWeekday": "Next weekday", + "dateSpecific": "Specific date", + "time": "Time", + "priority": "Priority", + "priorityUrgent": "Urgent", + "priorityHigh": "High", + "priorityLow": "Low", + "labels": "Labels", + "labelsAdd": "Add tags", + "duration": "Duration", + "duration30m": "30 minutes", + "duration2h": "2 hours", + "duration90m": "90 minutes", + "recurrence": "Recurrence", + "recurrenceDaily": "Daily", + "recurrenceWeekly": "Weekly", + "recurrenceMonthly": "Monthly", + "multiTask": "Multiple tasks", + "multiTaskChain": "Chain tasks", + "multiTaskSemicolon": "Separate with semicolons", + "subtasks": "Subtasks", + "subtasksColonComma": "Colon + comma", + "exampleTitle": "Example", + "exampleInput": "Meeting tomorrow at 2pm 1h !high #work", + "exampleOutput": "→ \"Meeting\" tomorrow at 14:00, duration 1h, priority High, label \"work\"" + } + }, "app_slider": { "title": "Part of the Mana Ecosystem", "memoro_desc": "AI-powered voice notes", diff --git a/apps/manacore/apps/web/src/lib/i18n/locales/es.json b/apps/manacore/apps/web/src/lib/i18n/locales/es.json index 1bc965ef1..1d32cb525 100644 --- a/apps/manacore/apps/web/src/lib/i18n/locales/es.json +++ b/apps/manacore/apps/web/src/lib/i18n/locales/es.json @@ -6,6 +6,145 @@ "back": "Atrás", "loading": "Cargando..." }, + "todo": { + "title": "Tareas", + "tasks": "Tareas", + "completed": "completadas", + "overdue": "vencidas", + "today": "hoy", + "inbox": "Bandeja", + "todayView": "Hoy", + "upcoming": "Próximas", + "completedView": "Completadas", + "search": "Buscar", + "newTask": "Nueva tarea", + "quickAddPlaceholder": "ej. 'Reunión mañana a las 14h !alta #importante'", + "addTask": "Añadir", + "cancel": "Canc.", + "syntaxHelp": "Ayuda sintaxis", + "noTasks": "Sin tareas", + "noTasksInbox": "Bandeja vacía", + "noTasksToday": "Sin tareas para hoy", + "noTasksUpcoming": "Sin tareas próximas", + "noTasksCompleted": "Ninguna tarea completada", + "firstTaskHint": "Crea tu primera tarea con el botón + arriba.", + "edit": "Editar", + "markDone": "Completar", + "reopen": "Reabrir", + "deleteConfirm": "¿Eliminar?", + "yesDelete": "Sí, eliminar", + "save": "Guardar", + "close": "Cerrar", + "description": "Descripción", + "addDescription": "Añadir descripción...", + "subtasks": "Subtareas", + "addSubtask": "Añadir subtarea...", + "status": "Estado", + "statusPending": "Abierta", + "statusInProgress": "En curso", + "statusCompleted": "Completada", + "statusCancelled": "Cancelada", + "priority": "Prioridad", + "priorityUrgent": "Urgente", + "priorityHigh": "Alta", + "priorityMedium": "Media", + "priorityLow": "Baja", + "dueDate": "Vence", + "time": "Hora", + "startDate": "Inicio", + "recurrence": "Recurrencia", + "recurrenceNone": "Ninguna", + "recurrenceDaily": "Diario", + "recurrenceWeekly": "Semanal", + "recurrenceMonthly": "Mensual", + "recurrenceYearly": "Anual", + "reminder": "Recordatorio", + "reminderNone": "Ninguno", + "tags": "Tags", + "storypoints": "Puntos", + "duration": "Duración", + "fun": "Diversión", + "projects": "Proyectos", + "labels": "Etiquetas", + "sort": "Ordenar", + "sortManual": "Manual", + "sortDueDate": "Vencimiento", + "sortPriority": "Prioridad", + "sortName": "Nombre", + "sortCreated": "Creación", + "showCompleted": "Completadas", + "boardView": "Board", + "listView": "Lista", + "board": { + "new": "Nuevo board", + "edit": "Editar board", + "create": "Crear board", + "name": "Nombre del board...", + "groupBy": "Agrupar por", + "layout": "Diseño", + "columns": "Columnas", + "addColumn": "Columna", + "columnName": "Nombre de columna...", + "delete": "Eliminar", + "noTasks": "Sin tareas", + "groupStatus": "Estado", + "groupPriority": "Prioridad", + "groupDueDate": "Vencimiento", + "groupTag": "Tag", + "groupCustom": "Personalizado", + "layoutKanban": "Kanban", + "layoutGrid": "Cuadrícula", + "layoutFocus": "Enfoque" + }, + "settings": { + "title": "Ajustes Todo" + }, + "onboarding": { + "welcome": "¡Bienvenido a Todo!", + "intro": "Tu planificador de tareas personal con boards Kanban, entrada inteligente y más.", + "step1Title": "Lenguaje natural", + "step1": "Crea tareas como \"Reunión mañana a las 14h !alta #trabajo\" — fecha, prioridad y etiquetas se detectan automáticamente.", + "step2Title": "Boards Kanban", + "step2": "Organiza tus tareas visualmente con diferentes vistas: estado, prioridad o personalizado.", + "step3Title": "¡Vamos!", + "step3": "Crea tu primera tarea con el botón +. Los consejos están disponibles en el icono ?.", + "next": "Siguiente", + "letsGo": "¡Vamos!", + "getStarted": "Empezar" + }, + "syntaxHelp": { + "title": "Sintaxis Quick-Add", + "description": "Usa lenguaje natural para crear tareas rápidamente.", + "date": "Fecha", + "dateToday": "Hoy", + "dateTomorrow": "Mañana", + "dateNextWeekday": "Próximo día", + "dateSpecific": "Fecha específica", + "time": "Hora", + "priority": "Prioridad", + "priorityUrgent": "Urgente", + "priorityHigh": "Alta", + "priorityLow": "Baja", + "labels": "Etiquetas", + "labelsAdd": "Añadir tags", + "duration": "Duración", + "duration30m": "30 minutos", + "duration2h": "2 horas", + "duration90m": "90 minutos", + "recurrence": "Recurrencia", + "recurrenceDaily": "Diario", + "recurrenceWeekly": "Semanal", + "recurrenceMonthly": "Mensual", + "multiTask": "Múltiples tareas", + "multiTaskChain": "Encadenar tareas", + "multiTaskSemicolon": "Separar con punto y coma", + "subtasks": "Subtareas", + "subtasksColonComma": "Dos puntos + coma", + "exampleTitle": "Ejemplo", + "exampleInput": "Reunión mañana a las 14h 1h !alta #trabajo", + "exampleOutput": "→ \"Reunión\" mañana a las 14:00, duración 1h, prioridad Alta, etiqueta \"trabajo\"" + } + }, "app_slider": { "title": "Parte del ecosistema Mana", "memoro_desc": "Notas de voz con IA", diff --git a/apps/manacore/apps/web/src/lib/i18n/locales/fr.json b/apps/manacore/apps/web/src/lib/i18n/locales/fr.json index ff07f2cfe..fac7caf5a 100644 --- a/apps/manacore/apps/web/src/lib/i18n/locales/fr.json +++ b/apps/manacore/apps/web/src/lib/i18n/locales/fr.json @@ -6,6 +6,145 @@ "back": "Retour", "loading": "Chargement..." }, + "todo": { + "title": "Tâches", + "tasks": "Tâches", + "completed": "terminées", + "overdue": "en retard", + "today": "aujourd'hui", + "inbox": "Boîte de réception", + "todayView": "Aujourd'hui", + "upcoming": "À venir", + "completedView": "Terminées", + "search": "Recherche", + "newTask": "Nouvelle tâche", + "quickAddPlaceholder": "ex. 'Réunion demain à 14h !haute #important'", + "addTask": "Ajouter", + "cancel": "Ann.", + "syntaxHelp": "Aide syntaxe", + "noTasks": "Aucune tâche", + "noTasksInbox": "Boîte de réception vide", + "noTasksToday": "Aucune tâche aujourd'hui", + "noTasksUpcoming": "Aucune tâche à venir", + "noTasksCompleted": "Aucune tâche terminée", + "firstTaskHint": "Créez votre première tâche avec le bouton + ci-dessus.", + "edit": "Modifier", + "markDone": "Terminer", + "reopen": "Rouvrir", + "deleteConfirm": "Vraiment supprimer ?", + "yesDelete": "Oui, supprimer", + "save": "Enregistrer", + "close": "Fermer", + "description": "Description", + "addDescription": "Ajouter une description...", + "subtasks": "Sous-tâches", + "addSubtask": "Ajouter une sous-tâche...", + "status": "Statut", + "statusPending": "Ouvert", + "statusInProgress": "En cours", + "statusCompleted": "Terminé", + "statusCancelled": "Annulé", + "priority": "Priorité", + "priorityUrgent": "Urgent", + "priorityHigh": "Haute", + "priorityMedium": "Moyenne", + "priorityLow": "Basse", + "dueDate": "Échéance", + "time": "Heure", + "startDate": "Début", + "recurrence": "Récurrence", + "recurrenceNone": "Aucune", + "recurrenceDaily": "Quotidien", + "recurrenceWeekly": "Hebdomadaire", + "recurrenceMonthly": "Mensuel", + "recurrenceYearly": "Annuel", + "reminder": "Rappel", + "reminderNone": "Aucun", + "tags": "Tags", + "storypoints": "Points", + "duration": "Durée", + "fun": "Fun", + "projects": "Projets", + "labels": "Labels", + "sort": "Tri", + "sortManual": "Manuel", + "sortDueDate": "Échéance", + "sortPriority": "Priorité", + "sortName": "Nom", + "sortCreated": "Création", + "showCompleted": "Terminées", + "boardView": "Board", + "listView": "Liste", + "board": { + "new": "Nouveau board", + "edit": "Modifier le board", + "create": "Créer un board", + "name": "Nom du board...", + "groupBy": "Grouper par", + "layout": "Disposition", + "columns": "Colonnes", + "addColumn": "Colonne", + "columnName": "Nom de colonne...", + "delete": "Supprimer", + "noTasks": "Aucune tâche", + "groupStatus": "Statut", + "groupPriority": "Priorité", + "groupDueDate": "Échéance", + "groupTag": "Tag", + "groupCustom": "Personnalisé", + "layoutKanban": "Kanban", + "layoutGrid": "Grille", + "layoutFocus": "Focus" + }, + "settings": { + "title": "Paramètres Todo" + }, + "onboarding": { + "welcome": "Bienvenue dans Todo !", + "intro": "Votre planificateur de tâches personnel avec boards Kanban, saisie intelligente et plus.", + "step1Title": "Langage naturel", + "step1": "Créez des tâches comme \"Réunion demain à 14h !haute #travail\" — date, priorité et labels sont détectés automatiquement.", + "step2Title": "Boards Kanban", + "step2": "Organisez vos tâches visuellement avec différentes vues : statut, priorité ou personnalisé.", + "step3Title": "C'est parti !", + "step3": "Créez votre première tâche avec le bouton +. Des astuces sont disponibles via l'icône ?.", + "next": "Suivant", + "letsGo": "C'est parti", + "getStarted": "Commencer" + }, + "syntaxHelp": { + "title": "Syntaxe Quick-Add", + "description": "Utilisez le langage naturel pour créer rapidement des tâches.", + "date": "Date", + "dateToday": "Aujourd'hui", + "dateTomorrow": "Demain", + "dateNextWeekday": "Prochain jour", + "dateSpecific": "Date spécifique", + "time": "Heure", + "priority": "Priorité", + "priorityUrgent": "Urgent", + "priorityHigh": "Haute", + "priorityLow": "Basse", + "labels": "Labels", + "labelsAdd": "Ajouter des tags", + "duration": "Durée", + "duration30m": "30 minutes", + "duration2h": "2 heures", + "duration90m": "90 minutes", + "recurrence": "Récurrence", + "recurrenceDaily": "Quotidien", + "recurrenceWeekly": "Hebdomadaire", + "recurrenceMonthly": "Mensuel", + "multiTask": "Tâches multiples", + "multiTaskChain": "Enchaîner les tâches", + "multiTaskSemicolon": "Séparer par point-virgule", + "subtasks": "Sous-tâches", + "subtasksColonComma": "Deux-points + virgule", + "exampleTitle": "Exemple", + "exampleInput": "Réunion demain à 14h 1h !haute #travail", + "exampleOutput": "→ \"Réunion\" demain à 14:00, durée 1h, priorité Haute, label \"travail\"" + } + }, "app_slider": { "title": "Partie de l'écosystème Mana", "memoro_desc": "Notes vocales IA", diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/MinimizedTabs.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/MinimizedTabs.svelte new file mode 100644 index 000000000..0cb7c10a5 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/MinimizedTabs.svelte @@ -0,0 +1,34 @@ + + +{#if pages.length > 0} +
+ {#each pages as page (page.id)} +
+ + +
+ {/each} +
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/OnboardingModal.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/OnboardingModal.svelte new file mode 100644 index 000000000..dfaf7f1c2 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/OnboardingModal.svelte @@ -0,0 +1,102 @@ + + +{#if open} + + +
e.target === e.currentTarget && finish()} + > +
+
+ {@const CurrentIcon = steps[step].icon} +
+ +
+ +

{steps[step].title}

+

{steps[step].desc}

+ + +
+ {#each steps as _, i} +
+ {/each} +
+ +
+ {#if step < steps.length - 1} + + {/if} + +
+
+
+
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/QuickAddTask.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/QuickAddTask.svelte new file mode 100644 index 000000000..261da437c --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/QuickAddTask.svelte @@ -0,0 +1,196 @@ + + +{#if isOpen} +
+
+ + + {#if onShowSyntaxHelp} + + {/if} + +
+ + + {#if parsed.length > 0 && hasParsedMeta} +
+ {#each parsed as task, i} +
+ {#if parsed.length > 1} + #{i + 1} + {/if} + {task.title} + {#if task.dueDate} + + + {task.dueDate.toLocaleDateString('de-DE', { day: 'numeric', month: 'short' })} + {#if task.dueTime} + {task.dueTime} + {/if} + + {/if} + {#if task.priority} + + + {task.priority} + + {/if} + {#if task.recurrenceRule} + + + + {/if} + {#if task.estimatedDuration} + + + {formatDuration(task.estimatedDuration)} + + {/if} + {#each task.labelNames as name} + + + {name} + + {/each} +
+ {/each} +
+ {/if} +
+{:else} + +{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/SubtaskList.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/SubtaskList.svelte new file mode 100644 index 000000000..127d02cc3 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/SubtaskList.svelte @@ -0,0 +1,104 @@ + + +
+ {#each subtasks as subtask (subtask.id)} +
+ + + {subtask.title} + + {#if !readonly} + + {/if} +
+ {/each} + + {#if !readonly} +
+ + +
+ {/if} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/SyncIndicator.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/SyncIndicator.svelte new file mode 100644 index 000000000..a003d6ed2 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/SyncIndicator.svelte @@ -0,0 +1,34 @@ + + +
+ {#if status === 'synced'} + + Sync + {:else if status === 'syncing'} + + Sync... + {:else if status === 'offline'} + + Offline + {:else} + + Fehler + {/if} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/SyntaxHelpOverlay.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/SyntaxHelpOverlay.svelte new file mode 100644 index 000000000..a57279f2d --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/SyntaxHelpOverlay.svelte @@ -0,0 +1,132 @@ + + +{#if open} + + +
e.target === e.currentTarget && onClose()} + > +
+
+

{$_('todo.syntaxHelp.title')}

+ +
+ +
+

+ {$_('todo.syntaxHelp.description')} +

+ +
+ {#each sections as section} +
+

+ {section.title} +

+
+ {#each section.examples as example} +
+ + {example.input} + + {example.desc} +
+ {/each} +
+
+ {/each} +
+ +
+

{$_('todo.syntaxHelp.exampleTitle')}

+ + {$_('todo.syntaxHelp.exampleInput')} + +

+ {$_('todo.syntaxHelp.exampleOutput')} +

+
+
+
+
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/TagStrip.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/TagStrip.svelte new file mode 100644 index 000000000..57bda280c --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/TagStrip.svelte @@ -0,0 +1,48 @@ + + +{#if labels.length > 0 && !collapsed} +
+ {#if viewStore.currentView === 'label'} + + {/if} + {#each labels as label (label.id)} + + {/each} +
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/TaskEditModal.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/TaskEditModal.svelte new file mode 100644 index 000000000..2118b34b3 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/TaskEditModal.svelte @@ -0,0 +1,315 @@ + + + + +{#if open} + + +
+
+ +
+
+ {#if form.showDeleteConfirm} + {$_('todo.deleteConfirm')} + + + {:else} + + {/if} +
+
+ + +
+
+ + +
+ +
+ + +
+
+ {$_('todo.description')} + +
+ +
+ {$_('todo.subtasks')} + +
+
+ + +
+ +
+ {$_('todo.status')} + +
+ + +
+ {$_('todo.priority')} + (form.priority = p)} /> +
+ + +
+ {$_('todo.dueDate')} + +
+ + +
+ {$_('todo.time')} + +
+ + +
+ {$_('todo.startDate')} + +
+ + +
+ {$_('todo.recurrence')} + +
+ + +
+ {$_('todo.reminder')} + (form.reminderMinutes = v)} + disabled={!form.dueDate} + /> +
+ + +
+ {$_('todo.tags')} + (form.selectedLabelIds = ids)} + /> +
+ + +
+ {$_('todo.storypoints')} + (form.storyPoints = v)} /> +
+ + +
+ {$_('todo.duration')} + (form.effectiveDuration = v)} + /> +
+ + +
+ {$_('todo.fun')} + (form.funRating = v)} /> +
+
+
+
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/TaskItem.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/TaskItem.svelte new file mode 100644 index 000000000..caea45cf6 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/TaskItem.svelte @@ -0,0 +1,132 @@ + + + +
{ + e.preventDefault(); + onContextMenu(e); + }} + onkeydown={(e) => e.key === 'Enter' && onClick()} +> + + + + +
+ + {task.title} + + + +
+ {#if dueInfo} + + + {dueInfo.text} + + {/if} + {#if task.priority !== 'medium'} + + {getPriorityLabel(task.priority)} + + {/if} + {#if subtaskInfo} + + + {subtaskInfo.done}/{subtaskInfo.total} + + {/if} + {#each taskTags as tag (tag.id)} + + {tag.name} + + {/each} +
+
+ + + {#if task.priority === 'urgent' || task.priority === 'high'} +
+ {/if} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/TaskList.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/TaskList.svelte new file mode 100644 index 000000000..f0400044a --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/TaskList.svelte @@ -0,0 +1,172 @@ + + +{#if dragEnabled} +
+ {#each items as task (task.id)} +
+ tasksStore.toggleComplete(task.id)} + onClick={() => onOpenTask(task)} + onContextMenu={(e) => handleContextMenu(task, e)} + /> +
+ {/each} +
+{:else} +
+ {#each tasks as task (task.id)} + tasksStore.toggleComplete(task.id)} + onClick={() => onOpenTask(task)} + onContextMenu={(e) => handleContextMenu(task, e)} + /> + {/each} +
+{/if} + + +{#if contextMenu} + + +
+
+ + +
+
+ {$_('todo.priority')} +
+ {#each ['urgent', 'high', 'medium', 'low'] as p} + + {/each} +
+ +
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/TodoToolbar.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/TodoToolbar.svelte new file mode 100644 index 000000000..c57e37ede --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/TodoToolbar.svelte @@ -0,0 +1,124 @@ + + +
+ + + + +
+ + + {#if showSortMenu} + + +
(showSortMenu = false)}>
+
+ {#each sortOptions as opt} + + {/each} +
+ {/if} +
+ + + + + + {#if showBoardToggle} + + {/if} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/BoardViewRenderer.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/BoardViewRenderer.svelte new file mode 100644 index 000000000..8759fbdf7 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/BoardViewRenderer.svelte @@ -0,0 +1,51 @@ + + +{#if view.layout === 'grid'} + +{:else if view.layout === 'fokus'} + +{:else} + +{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/FokusLayout.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/FokusLayout.svelte new file mode 100644 index 000000000..399c32dce --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/FokusLayout.svelte @@ -0,0 +1,90 @@ + + +
+ + {#if focusColumn} +
+
+ + {focusColumn.name} +
+
+ {#each focusTasks as task, i (task.id)} + + +
onOpenTask(task)} + class="group flex cursor-pointer items-center gap-4 rounded-xl border border-border bg-card p-4 transition-all hover:shadow-md" + style="border-left: 4px solid {getPriorityColor(task.priority)}" + > + +
+ + {task.title} + + {#if task.description} +

+ {task.description} +

+ {/if} +
+ +
+ {/each} +
+
+ {/if} + + + {#each remainingColumns as column (column.id)} + {#if column.tasks.length > 0} +
+
+ + {column.name} + ({column.tasks.length}) +
+
+ {/if} + {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/GridLayout.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/GridLayout.svelte new file mode 100644 index 000000000..7e52308e9 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/GridLayout.svelte @@ -0,0 +1,47 @@ + + +
+ {#each columns as column (column.id)} +
+
+ + {column.name} + ({column.tasks.length}) +
+
+ {#each column.tasks as task (task.id)} + + +
onOpenTask(task)} class="cursor-pointer"> + onToggleComplete(task.id)} + onSave={(data) => onSaveTask(task.id, data)} + onDelete={() => onDeleteTask(task.id)} + /> +
+ {/each} +
+
+ {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/KanbanLayout.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/KanbanLayout.svelte new file mode 100644 index 000000000..eb7c3000d --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/KanbanLayout.svelte @@ -0,0 +1,62 @@ + + +
+ {#each columns as column (column.id)} + + {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumn.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumn.svelte new file mode 100644 index 000000000..b5ec953fe --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumn.svelte @@ -0,0 +1,100 @@ + + +
+ + +
+ {#each items as task (task.id)} +
+ + +
onOpenTask(task)} class="cursor-pointer"> + onToggleComplete(task.id)} + onSave={(data) => onSaveTask(task.id, data)} + onDelete={() => onDeleteTask(task.id)} + /> +
+
+ {/each} +
+ +
+ onQuickAdd(title, column.id)} /> +
+
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumnHeader.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumnHeader.svelte new file mode 100644 index 000000000..356e16949 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewColumnHeader.svelte @@ -0,0 +1,26 @@ + + +
+
+ + {column.name} + + {column.tasks.length}{wipLimit != null ? `/${wipLimit}` : ''} + +
+
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewEditorModal.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewEditorModal.svelte new file mode 100644 index 000000000..bea957fa7 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewEditorModal.svelte @@ -0,0 +1,228 @@ + + +{#if open} + + +
e.target === e.currentTarget && onClose()} + > +
+ +
+

+ {view ? $_('todo.board.edit') : $_('todo.board.new')} +

+ +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {#each columns as col, i (col.id)} +
+ + + +
+ {/each} +
+
+
+ + +
+
+ {#if view} + + {/if} +
+
+ + +
+
+
+
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewSelector.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewSelector.svelte new file mode 100644 index 000000000..9cee6494e --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/ViewSelector.svelte @@ -0,0 +1,47 @@ + + +
+ {#each views as view (view.id)} + {@const Icon = getIcon(view.layout)} + + {/each} + +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/index.ts b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/index.ts new file mode 100644 index 000000000..d88f51d7f --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/board-views/index.ts @@ -0,0 +1,8 @@ +export { default as BoardViewRenderer } from './BoardViewRenderer.svelte'; +export { default as KanbanLayout } from './KanbanLayout.svelte'; +export { default as GridLayout } from './GridLayout.svelte'; +export { default as FokusLayout } from './FokusLayout.svelte'; +export { default as ViewColumn } from './ViewColumn.svelte'; +export { default as ViewColumnHeader } from './ViewColumnHeader.svelte'; +export { default as ViewSelector } from './ViewSelector.svelte'; +export { default as ViewEditorModal } from './ViewEditorModal.svelte'; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/DurationPicker.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/form/DurationPicker.svelte new file mode 100644 index 000000000..15159c99a --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/DurationPicker.svelte @@ -0,0 +1,49 @@ + + +
+ +
+ {#each presets as preset} + + {/each} +
+ {#if value && !presets.some((p) => p.value === value)} + {formatDuration(value)} + {/if} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/FunRatingPicker.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/form/FunRatingPicker.svelte new file mode 100644 index 000000000..92b628861 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/FunRatingPicker.svelte @@ -0,0 +1,41 @@ + + +
+ {#each ratings as r} + + {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/PrioritySelector.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/form/PrioritySelector.svelte new file mode 100644 index 000000000..a72712c71 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/PrioritySelector.svelte @@ -0,0 +1,32 @@ + + +
+ {#each priorities as p} + + {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/ReminderSelector.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/form/ReminderSelector.svelte new file mode 100644 index 000000000..8926b4443 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/ReminderSelector.svelte @@ -0,0 +1,37 @@ + + +
+ + +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/StorypointsSelector.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/form/StorypointsSelector.svelte new file mode 100644 index 000000000..7a78e2186 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/StorypointsSelector.svelte @@ -0,0 +1,30 @@ + + +
+ +
+ {#each points as p} + + {/each} +
+
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/TagSelector.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/form/TagSelector.svelte new file mode 100644 index 000000000..183907027 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/TagSelector.svelte @@ -0,0 +1,92 @@ + + +
+ {#each selectedLabels as label (label.id)} + + {label.name} + + + {/each} + +
+ + + {#if showPicker && availableLabels.length > 0} + + +
+ {#each availableLabels as label (label.id)} + + {/each} +
+ {/if} +
+
+ +{#if showPicker} + + +
(showPicker = false)}>
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/form/index.ts b/apps/manacore/apps/web/src/lib/modules/todo/components/form/index.ts new file mode 100644 index 000000000..b770414a5 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/form/index.ts @@ -0,0 +1,6 @@ +export { default as PrioritySelector } from './PrioritySelector.svelte'; +export { default as TagSelector } from './TagSelector.svelte'; +export { default as ReminderSelector } from './ReminderSelector.svelte'; +export { default as DurationPicker } from './DurationPicker.svelte'; +export { default as StorypointsSelector } from './StorypointsSelector.svelte'; +export { default as FunRatingPicker } from './FunRatingPicker.svelte'; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanBoardSkeleton.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanBoardSkeleton.svelte new file mode 100644 index 000000000..c57f3cf3b --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanBoardSkeleton.svelte @@ -0,0 +1,15 @@ + + +
+ {#each Array(columns) as _} + + {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanColumnSkeleton.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanColumnSkeleton.svelte new file mode 100644 index 000000000..8c5a919e0 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/KanbanColumnSkeleton.svelte @@ -0,0 +1,16 @@ +
+
+
+
+
+
+ {#each Array(3) as _} +
+
+
+
+
+
+
+ {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/StatisticsSkeleton.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/StatisticsSkeleton.svelte new file mode 100644 index 000000000..4b5bde739 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/StatisticsSkeleton.svelte @@ -0,0 +1,5 @@ +
+ {#each Array(4) as _} +
+ {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskItemSkeleton.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskItemSkeleton.svelte new file mode 100644 index 000000000..c68bd03bd --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskItemSkeleton.svelte @@ -0,0 +1,10 @@ +
+
+
+
+
+
+
+
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskListSkeleton.svelte b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskListSkeleton.svelte new file mode 100644 index 000000000..e16ea32a0 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/TaskListSkeleton.svelte @@ -0,0 +1,15 @@ + + +
+ {#each Array(count) as _, i} + + {/each} +
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/index.ts b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/index.ts new file mode 100644 index 000000000..266050b38 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/components/skeletons/index.ts @@ -0,0 +1,5 @@ +export { default as TaskItemSkeleton } from './TaskItemSkeleton.svelte'; +export { default as TaskListSkeleton } from './TaskListSkeleton.svelte'; +export { default as KanbanColumnSkeleton } from './KanbanColumnSkeleton.svelte'; +export { default as KanbanBoardSkeleton } from './KanbanBoardSkeleton.svelte'; +export { default as StatisticsSkeleton } from './StatisticsSkeleton.svelte'; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts b/apps/manacore/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts new file mode 100644 index 000000000..5a46954b8 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts @@ -0,0 +1,233 @@ +/** + * useTaskForm — Shared form state logic for TaskEditModal & inline editing. + */ + +import type { Task, TaskPriority, Subtask } from '../types'; +import { reminderTable } from '../collections'; + +export interface TaskFormState { + title: string; + description: string; + dueDate: string; + dueTime: string; + startDate: string; + priority: TaskPriority; + status: string; + selectedLabelIds: string[]; + subtasks: Subtask[]; + recurrenceRule: string; + storyPoints: number | null; + effectiveDuration: number | null; + funRating: number | null; + reminderMinutes: number | null; + showDeleteConfirm: boolean; + isLoading: boolean; +} + +export function useTaskForm() { + let title = $state(''); + let description = $state(''); + let dueDate = $state(''); + let dueTime = $state(''); + let startDate = $state(''); + let priority = $state('medium'); + let status = $state('pending'); + let selectedLabelIds = $state([]); + let subtasks = $state([]); + let recurrenceRule = $state(''); + let storyPoints = $state(null); + let effectiveDuration = $state(null); + let funRating = $state(null); + let reminderMinutes = $state(null); + let showDeleteConfirm = $state(false); + let isLoading = $state(false); + + function initFromTask(task: Task) { + title = task.title; + description = task.description ?? ''; + dueDate = task.dueDate ? task.dueDate.split('T')[0] : ''; + dueTime = task.scheduledStartTime ?? ''; + startDate = task.scheduledDate ? task.scheduledDate.split('T')[0] : ''; + priority = task.priority; + status = task.status; + subtasks = task.subtasks ? [...task.subtasks] : []; + recurrenceRule = task.recurrenceRule ?? ''; + effectiveDuration = task.estimatedDuration ?? null; + showDeleteConfirm = false; + isLoading = false; + + const meta = task.metadata as Record | null; + selectedLabelIds = (meta?.labelIds as string[]) ?? []; + storyPoints = (meta?.storyPoints as number) ?? null; + funRating = (meta?.funRating as number) ?? null; + reminderMinutes = null; + + // Load existing reminder + loadReminder(task.id); + } + + async function loadReminder(taskId: string) { + try { + const reminders = await reminderTable.where('taskId').equals(taskId).toArray(); + const active = reminders.find((r) => !r.deletedAt && r.status === 'pending'); + if (active) { + reminderMinutes = active.minutesBefore; + } + } catch { + // Reminders table may not exist yet + } + } + + function buildUpdatePayload(): Record { + const update: Record = { + title: title.trim(), + description: description || undefined, + priority, + dueDate: dueDate ? new Date(dueDate).toISOString() : null, + scheduledDate: startDate ? new Date(startDate).toISOString() : null, + scheduledStartTime: dueTime || null, + estimatedDuration: effectiveDuration, + recurrenceRule: recurrenceRule || null, + subtasks: subtasks.length > 0 ? subtasks : null, + isCompleted: status === 'completed', + completedAt: status === 'completed' ? new Date().toISOString() : null, + metadata: { + labelIds: selectedLabelIds, + storyPoints, + funRating, + }, + }; + return update; + } + + async function persistReminder(taskId: string) { + try { + // Remove old reminders + const existing = await reminderTable.where('taskId').equals(taskId).toArray(); + for (const r of existing) { + if (!r.deletedAt) { + await reminderTable.update(r.id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + } + } + + // Create new if set + if (reminderMinutes != null && dueDate) { + await reminderTable.add({ + id: crypto.randomUUID(), + taskId, + minutesBefore: reminderMinutes, + type: 'push', + status: 'pending', + }); + } + } catch { + // Silently ignore if table doesn't exist + } + } + + return { + get title() { + return title; + }, + set title(v: string) { + title = v; + }, + get description() { + return description; + }, + set description(v: string) { + description = v; + }, + get dueDate() { + return dueDate; + }, + set dueDate(v: string) { + dueDate = v; + }, + get dueTime() { + return dueTime; + }, + set dueTime(v: string) { + dueTime = v; + }, + get startDate() { + return startDate; + }, + set startDate(v: string) { + startDate = v; + }, + get priority() { + return priority; + }, + set priority(v: TaskPriority) { + priority = v; + }, + get status() { + return status; + }, + set status(v: string) { + status = v; + }, + get selectedLabelIds() { + return selectedLabelIds; + }, + set selectedLabelIds(v: string[]) { + selectedLabelIds = v; + }, + get subtasks() { + return subtasks; + }, + set subtasks(v: Subtask[]) { + subtasks = v; + }, + get recurrenceRule() { + return recurrenceRule; + }, + set recurrenceRule(v: string) { + recurrenceRule = v; + }, + get storyPoints() { + return storyPoints; + }, + set storyPoints(v: number | null) { + storyPoints = v; + }, + get effectiveDuration() { + return effectiveDuration; + }, + set effectiveDuration(v: number | null) { + effectiveDuration = v; + }, + get funRating() { + return funRating; + }, + set funRating(v: number | null) { + funRating = v; + }, + get reminderMinutes() { + return reminderMinutes; + }, + set reminderMinutes(v: number | null) { + reminderMinutes = v; + }, + get showDeleteConfirm() { + return showDeleteConfirm; + }, + set showDeleteConfirm(v: boolean) { + showDeleteConfirm = v; + }, + get isLoading() { + return isLoading; + }, + set isLoading(v: boolean) { + isLoading = v; + }, + + initFromTask, + buildUpdatePayload, + persistReminder, + }; +} diff --git a/apps/manacore/apps/web/src/lib/modules/todo/index.ts b/apps/manacore/apps/web/src/lib/modules/todo/index.ts index 9f9dd0eb0..14d7c575a 100644 --- a/apps/manacore/apps/web/src/lib/modules/todo/index.ts +++ b/apps/manacore/apps/web/src/lib/modules/todo/index.ts @@ -2,10 +2,17 @@ * Todo module — barrel exports. */ +// Stores export { tasksStore } from './stores/tasks.svelte'; export { boardViewsStore } from './stores/board-views.svelte'; export { viewStore } from './stores/view.svelte'; export { labelsStore } from './stores/labels.svelte'; +export { remindersStore } from './stores/reminders.svelte'; +export { todoSettings } from './stores/settings.svelte'; +export { minimizedPagesStore } from './stores/minimized-pages.svelte'; +export { contactsStore } from './stores/contacts.svelte'; + +// Queries export { useAllTasks, useAllLabels, @@ -25,6 +32,8 @@ export { getPriorityColor, getTaskStats, } from './queries'; + +// Collections export { taskTable, todoProjectTable, @@ -34,6 +43,26 @@ export { boardViewTable, TODO_GUEST_SEED, } from './collections'; + +// View Grouping +export { groupTasksByView, getDropActionUpdate } from './view-grouping'; +export type { GroupedColumn } from './view-grouping'; + +// Utilities +export { + parseTaskInput, + parseMultiTaskInput, + resolveTaskIds, + formatDuration, +} from './utils/task-parser'; +export { estimateDuration } from './utils/time-estimator'; +export type { ParsedTask, ParsedTaskWithIds } from './utils/task-parser'; +export type { DurationEstimate } from './utils/time-estimator'; + +// Composables +export { useTaskForm } from './composables/useTaskForm.svelte'; + +// Types export type { LocalTask, LocalLabel, diff --git a/apps/manacore/apps/web/src/lib/modules/todo/stores/contacts.svelte.ts b/apps/manacore/apps/web/src/lib/modules/todo/stores/contacts.svelte.ts new file mode 100644 index 000000000..1661df8e7 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/stores/contacts.svelte.ts @@ -0,0 +1,66 @@ +/** + * Contacts Integration Store — Task assignment via Contacts app. + * + * Checks if the Contacts module is available and provides search functionality. + */ + +import { db } from '$lib/data/database'; + +export interface ContactResult { + id: string; + name: string; + email?: string; +} + +let isAvailable = $state(false); +let cache = $state>(new Map()); + +async function checkAvailability() { + try { + const tables = db.tables.map((t) => t.name); + isAvailable = tables.includes('contacts'); + } catch { + isAvailable = false; + } +} + +// Check on init +checkAvailability(); + +export const contactsStore = { + get isAvailable() { + return isAvailable; + }, + + async searchContacts(query: string): Promise { + if (!isAvailable || !query.trim()) return []; + + try { + const q = query.toLowerCase(); + const all = await db.table('contacts').toArray(); + return all + .filter((c: any) => { + if (c.deletedAt) return false; + const name = `${c.firstName ?? ''} ${c.lastName ?? ''}`.toLowerCase(); + const email = (c.email ?? '').toLowerCase(); + return name.includes(q) || email.includes(q); + }) + .slice(0, 10) + .map((c: any) => { + const result: ContactResult = { + id: c.id, + name: `${c.firstName ?? ''} ${c.lastName ?? ''}`.trim(), + email: c.email, + }; + cache.set(c.id, result); + return result; + }); + } catch { + return []; + } + }, + + getFromCache(id: string): ContactResult | undefined { + return cache.get(id); + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/stores/minimized-pages.svelte.ts b/apps/manacore/apps/web/src/lib/modules/todo/stores/minimized-pages.svelte.ts new file mode 100644 index 000000000..4557bf714 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/stores/minimized-pages.svelte.ts @@ -0,0 +1,63 @@ +/** + * Minimized Pages Store — Multi-page system with minimized tabs. + * + * Allows users to "minimize" views to a tab bar and restore them later. + */ + +export interface MinimizedPage { + id: string; + title: string; + icon?: string; + route?: string; +} + +let pages = $state([]); +let activePageId = $state(null); +let showPicker = $state(false); + +export const minimizedPagesStore = { + get pages() { + return pages; + }, + get activePageId() { + return activePageId; + }, + get showPicker() { + return showPicker; + }, + + minimize(page: MinimizedPage) { + if (!pages.find((p) => p.id === page.id)) { + pages = [...pages, page]; + } + }, + + restore(id: string) { + activePageId = id; + }, + + remove(id: string) { + pages = pages.filter((p) => p.id !== id); + if (activePageId === id) { + activePageId = null; + } + }, + + maximize(id: string) { + activePageId = id; + }, + + togglePicker() { + showPicker = !showPicker; + }, + + closePicker() { + showPicker = false; + }, + + clear() { + pages = []; + activePageId = null; + showPicker = false; + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts b/apps/manacore/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts new file mode 100644 index 000000000..d7a8751c8 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts @@ -0,0 +1,61 @@ +/** + * Reminders Store — Mutation-Only Service + * + * Reads via liveQuery (useAllReminders in queries.ts). + * This store handles create and delete operations for task reminders. + */ + +import { reminderTable } from '../collections'; +import type { LocalReminder } from '../types'; + +export const remindersStore = { + async createReminder(data: { + taskId: string; + minutesBefore: number; + type?: 'push' | 'email' | 'both'; + }) { + const newReminder: LocalReminder = { + id: crypto.randomUUID(), + taskId: data.taskId, + minutesBefore: data.minutesBefore, + type: data.type ?? 'push', + status: 'pending', + }; + await reminderTable.add(newReminder); + return newReminder; + }, + + async deleteReminder(id: string) { + await reminderTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }, + + async deleteRemindersForTask(taskId: string) { + const reminders = await reminderTable.where('taskId').equals(taskId).toArray(); + for (const r of reminders) { + if (!r.deletedAt) { + await reminderTable.update(r.id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + } + } + }, + + async replaceReminder( + taskId: string, + minutesBefore: number | null, + type?: 'push' | 'email' | 'both' + ) { + // Delete existing + await this.deleteRemindersForTask(taskId); + + // Create new if set + if (minutesBefore != null) { + return this.createReminder({ taskId, minutesBefore, type }); + } + return null; + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/utils/time-estimator.ts b/apps/manacore/apps/web/src/lib/modules/todo/utils/time-estimator.ts new file mode 100644 index 000000000..bcfcbf619 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/todo/utils/time-estimator.ts @@ -0,0 +1,100 @@ +/** + * Smart Duration Estimation — History-based task duration estimation. + * + * Analyzes completed tasks with known durations to suggest durations for new tasks. + * Uses weighted scoring by: labels, title similarity, priority. + * Fully offline — reads from IndexedDB. + */ + +import { taskTable } from '../collections'; +import type { LocalTask, TaskPriority } from '../types'; + +export interface DurationEstimate { + minutes: number; + confidence: 'low' | 'medium' | 'high'; + sampleSize: number; +} + +/** + * Estimate duration for a new task based on historical data. + */ +export async function estimateDuration( + title: string, + options?: { + priority?: TaskPriority; + labelIds?: string[]; + defaultDuration?: number; + } +): Promise { + const allTasks = await taskTable.toArray(); + const completed = allTasks.filter( + (t) => t.isCompleted && t.estimatedDuration && t.estimatedDuration > 0 && !t.deletedAt + ); + + if (completed.length < 3) return null; + + // Score each historical task by relevance + const scored = completed.map((t) => { + let score = 1; + + // Title similarity (simple word overlap) + const titleWords = new Set( + title + .toLowerCase() + .split(/\s+/) + .filter((w) => w.length > 2) + ); + const taskWords = new Set( + t.title + .toLowerCase() + .split(/\s+/) + .filter((w) => w.length > 2) + ); + let overlap = 0; + for (const w of titleWords) { + if (taskWords.has(w)) overlap++; + } + if (titleWords.size > 0) { + score += (overlap / titleWords.size) * 3; + } + + // Priority match + if (options?.priority && t.priority === options.priority) { + score += 1; + } + + // Label overlap + if (options?.labelIds && options.labelIds.length > 0) { + const taskLabels: string[] = (t.metadata as { labelIds?: string[] })?.labelIds ?? []; + const labelOverlap = options.labelIds.filter((id) => taskLabels.includes(id)).length; + score += labelOverlap * 1.5; + } + + return { task: t, score, duration: t.estimatedDuration! }; + }); + + // Sort by score descending and take top matches + scored.sort((a, b) => b.score - a.score); + const top = scored.slice(0, 10); + + if (top.length === 0) return null; + + // Weighted average + const totalWeight = top.reduce((sum, s) => sum + s.score, 0); + const weightedAvg = top.reduce((sum, s) => sum + s.duration * s.score, 0) / totalWeight; + + // Round to nearest 5 minutes + const minutes = Math.round(weightedAvg / 5) * 5; + + // Determine confidence + const maxScore = Math.max(...top.map((s) => s.score)); + let confidence: DurationEstimate['confidence'] = 'low'; + if (top.length >= 5 && maxScore > 3) confidence = 'high'; + else if (top.length >= 3 && maxScore > 2) confidence = 'medium'; + + return { + minutes: Math.max(5, minutes), + confidence, + sampleSize: top.length, + }; +} diff --git a/apps/manacore/apps/web/src/routes/(app)/todo/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/todo/+page.svelte index de985ceee..e4f16890b 100644 --- a/apps/manacore/apps/web/src/routes/(app)/todo/+page.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/todo/+page.svelte @@ -1,16 +1,15 @@ Todo - ManaCore -
- -
-

Todo

-
- {stats.total} Aufgaben - {stats.completed} erledigt - {#if stats.overdue > 0} - {stats.overdue} ueberfaellig - {/if} - {#if stats.today > 0} - {stats.today} heute +
+ +
+
+

{$_('todo.title')}

+ {#if isLoaded} +
+ {stats.total} {$_('todo.tasks')} + {stats.completed} {$_('todo.completed')} + {#if stats.overdue > 0} + {stats.overdue} {$_('todo.overdue')} + {/if} + {#if stats.today > 0} + {stats.today} {$_('todo.today')} + {/if} +
+ {:else} + {/if}
+
+ + + + +
- -
- {#each views as view} - - {/each} + +
+
+ {#each views as view} + + {/each} +
+ + 0} + {isBoardView} + onToggleBoard={() => (isBoardView = !isBoardView)} + />
- + + todoSettings.toggleFilterStrip()} + /> + + {#if viewStore.currentView === 'search'}
viewStore.updateSearchQuery(e.currentTarget.value)} class="w-full rounded-lg border border-border bg-card py-2.5 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" + autofocus + /> +
+ {/if} + + + {#if isBoardView} +
+ (activeBoardId = id)} + onCreateView={() => { + editBoardView = null; + showBoardEditor = true; + }} />
{/if} - {#if isAdding} -
-
{ - e.preventDefault(); - handleQuickAdd(); - }} - class="flex gap-2" - > - - - -
-
- {:else} - + {#if !isBoardView} + (showSyntaxHelp = true)} /> {/if} - - {#if displayTasks.length === 0} + + {#if !isLoaded} + + {:else if isBoardView && activeBoard} + tasksStore.toggleComplete(id)} + onSaveTask={(id, data) => tasksStore.updateTask(id, data)} + onDeleteTask={(id) => tasksStore.deleteTask(id)} + onQuickAdd={handleBoardQuickAdd} + onOpenTask={(task) => (editTask = task)} + /> + {:else if displayTasks.length === 0}

{#if viewStore.currentView === 'completed'} - Noch keine Aufgaben erledigt + {$_('todo.noTasksCompleted')} {:else if viewStore.currentView === 'today'} - Keine Aufgaben fuer heute + {$_('todo.noTasksToday')} {:else if viewStore.currentView === 'upcoming'} - Keine anstehenden Aufgaben + {$_('todo.noTasksUpcoming')} + {:else if viewStore.currentView === 'search'} + {$_('todo.noTasks')} {:else} - Inbox ist leer + {$_('todo.noTasksInbox')} {/if}

{#if viewStore.currentView === 'inbox'} - Erstelle deine erste Aufgabe mit dem + Button oben. + {$_('todo.firstTaskHint')} {/if}

{:else} -
- {#each displayTasks as task (task.id)} -
(selectedTaskId = selectedTaskId === task.id ? null : task.id)} - use:dropTarget={{ - accepts: ['tag'], - onDrop: (payload) => handleTagDrop(task, payload), - canDrop: tagNotAlreadyOnTask(task), - }} - > - - - - -
-
- - {task.title} - -
- - -
- {#if task.dueDate} - - {formatDueDate(task.dueDate)} - - {/if} - {#if task.priority !== 'medium'} - - {getPriorityLabel(task.priority)} - - {/if} - {#if task.subtasks?.length} - - {task.subtasks.filter((s) => s.isCompleted).length}/{task.subtasks.length} Teilaufgaben - - {/if} - {#each getTaskTags(task).slice(0, 3) as tag (tag.id)} - - {tag.name} - - {/each} -
- - - {#if selectedTaskId === task.id} -
- {#if task.description} -

{task.description}

- {/if} - - {#if task.subtasks?.length} -
- {#each task.subtasks as subtask (subtask.id)} -
- {#if subtask.isCompleted} - - {:else} - - {/if} - - {subtask.title} - -
- {/each} -
- {/if} - -
- - - tasksStore.updateTask(task.id, { - dueDate: e.currentTarget.value - ? new Date(e.currentTarget.value).toISOString() - : null, - })} - class="rounded-md border border-border bg-background px-2 py-1 text-xs focus:border-primary focus:outline-none" - /> - - -
-
- {/if} -
- - - {#if task.priority === 'urgent' || task.priority === 'high'} -
- {/if} -
- {/each} -
+ (editTask = task)} + />

- {displayTasks.length} Aufgabe{displayTasks.length !== 1 ? 'n' : ''} + {displayTasks.length} + {$_('todo.tasks')}

{/if} - {#if allProjects.length > 0} + {#if !isBoardView && allProjects.length > 0}

- Projekte + {$_('todo.projects')}

{#each allProjects as project (project.id)} @@ -519,33 +380,6 @@ style="background-color: {project.color ?? '#6b7280'}" >
{project.name} - - - {/each} -
-
- {/if} - - - {#if allLabels.length > 0} -
-

- Labels -

-
- {#each allLabels as label (label.id)} - {/each}
@@ -553,7 +387,25 @@ {/if}
- + +{#if editTask} + (editTask = null)} /> +{/if} + + + (showBoardEditor = false)} +/> + + + (showSyntaxHelp = false)} /> + + + (showOnboarding = false)} /> + + (shareTask = null)} @@ -564,7 +416,6 @@ />