diff --git a/apps/todo/apps/web/src/lib/components/FilterStrip.svelte b/apps/todo/apps/web/src/lib/components/FilterStrip.svelte deleted file mode 100644 index 0fb53a17c..000000000 --- a/apps/todo/apps/web/src/lib/components/FilterStrip.svelte +++ /dev/null @@ -1,421 +0,0 @@ - - - - -
-
- - - - - - - - {#each priorities as priority (priority.value)} - - {/each} - - - {#each sortOptions as option (option.id)} - - {/each} - - - - - -
e.stopPropagation()}> - - - {#if showFilterDropdown} -
e.stopPropagation()}> -
-
Projekt
- -
-
- {/if} -
-
-
- - diff --git a/apps/todo/apps/web/src/lib/components/TaskFilters.svelte b/apps/todo/apps/web/src/lib/components/TaskFilters.svelte new file mode 100644 index 000000000..157a80662 --- /dev/null +++ b/apps/todo/apps/web/src/lib/components/TaskFilters.svelte @@ -0,0 +1,728 @@ + + + + +{#if variant === 'strip'} + +
+
+ + + + + {#if showKanbanNav} + + {/if} + + + {#each priorities as priority (priority.value)} + + {/each} + + + {#if showSort && onSortChange} + {#each sortOptions as option (option.id)} + + {/each} + {/if} + + + {#if showCompleted && onToggleCompleted} + + {/if} + + +
e.stopPropagation()}> + + + {#if showFilterDropdown} +
e.stopPropagation()}> +
+
Projekt
+ +
+
+ {/if} +
+
+
+{:else} + +
+
+ + {#if showSearch} +
+
+ + + + onSearchChange(e.currentTarget.value)} + placeholder="Aufgaben suchen..." + class="w-full pl-10 pr-8 py-2 text-sm bg-background border border-border rounded-lg outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary placeholder:text-muted-foreground transition-all" + /> + {#if searchQuery} + + {/if} +
+ + {#if hasActiveFilters} + + {/if} +
+ {/if} + + +
+ +
+ Priorität +
+ {#each priorities as priority} + + {/each} +
+
+ + + + +
+ Projekt + +
+ + + {#if showLabels} + + +
+ Tags + + + {#if showLabelsDropdown} + +
(showLabelsDropdown = false)}>
+
+ {#if labelsStore.labels.length === 0} +

Keine Tags vorhanden

+ {:else} +
+ {#each labelsStore.labels as label} + + {/each} +
+ {/if} +
+ {/if} +
+ {/if} +
+
+
+{/if} + + diff --git a/apps/todo/apps/web/src/lib/components/kanban/KanbanFilters.svelte b/apps/todo/apps/web/src/lib/components/kanban/KanbanFilters.svelte deleted file mode 100644 index ed16f9648..000000000 --- a/apps/todo/apps/web/src/lib/components/kanban/KanbanFilters.svelte +++ /dev/null @@ -1,319 +0,0 @@ - - -
-
- -
-
- - - - onSearchChange(e.currentTarget.value)} - placeholder="Aufgaben suchen..." - class="w-full pl-10 pr-8 py-2 text-sm bg-background border border-border rounded-lg outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary placeholder:text-muted-foreground transition-all" - /> - {#if searchQuery} - - {/if} -
- - {#if hasActiveFilters} - - {/if} -
- - -
- -
- Priorität -
- {#each priorities as priority} - - {/each} -
-
- - - - -
- Projekt - -
- - - - -
- Tags - - - {#if showLabelsDropdown} - -
(showLabelsDropdown = false)}>
-
- {#if labelsStore.labels.length === 0} -

Keine Tags vorhanden

- {:else} -
- {#each labelsStore.labels as label} - - {/each} -
- {/if} -
- {/if} -
-
-
-
- - diff --git a/apps/todo/apps/web/src/lib/components/kanban/index.ts b/apps/todo/apps/web/src/lib/components/kanban/index.ts index 56e0f3247..39f1e79b3 100644 --- a/apps/todo/apps/web/src/lib/components/kanban/index.ts +++ b/apps/todo/apps/web/src/lib/components/kanban/index.ts @@ -3,5 +3,4 @@ export { default as KanbanColumn } from './KanbanColumn.svelte'; export { default as KanbanColumnHeader } from './KanbanColumnHeader.svelte'; export { default as KanbanTaskCard } from './KanbanTaskCard.svelte'; export { default as AddColumnButton } from './AddColumnButton.svelte'; -export { default as KanbanFilters } from './KanbanFilters.svelte'; export { default as BoardNavigation } from './BoardNavigation.svelte'; diff --git a/apps/todo/apps/web/src/lib/stores/view.svelte.ts b/apps/todo/apps/web/src/lib/stores/view.svelte.ts index 640176045..a8970aed7 100644 --- a/apps/todo/apps/web/src/lib/stores/view.svelte.ts +++ b/apps/todo/apps/web/src/lib/stores/view.svelte.ts @@ -2,6 +2,8 @@ * View Store - Manages current view state using Svelte 5 runes */ +import type { TaskPriority } from '@todo/shared'; + export type ViewType = | 'inbox' | 'today' @@ -22,6 +24,12 @@ let sortBy = $state('order'); let sortOrder = $state('asc'); let showCompleted = $state(false); +// Filter state (used by TaskFilters strip in list view) +let filterPriorities = $state([]); +let filterProjectId = $state(null); +let filterLabelIds = $state([]); +let filterSearchQuery = $state(''); + export const viewStore = { // Getters get currentView() { @@ -45,6 +53,18 @@ export const viewStore = { get showCompleted() { return showCompleted; }, + get filterPriorities() { + return filterPriorities; + }, + get filterProjectId() { + return filterProjectId; + }, + get filterLabelIds() { + return filterLabelIds; + }, + get filterSearchQuery() { + return filterSearchQuery; + }, /** * Set current view to inbox @@ -145,6 +165,44 @@ export const viewStore = { showCompleted = !showCompleted; }, + /** + * Set filter priorities + */ + setFilterPriorities(priorities: TaskPriority[]) { + filterPriorities = priorities; + }, + + /** + * Set filter project + */ + setFilterProjectId(id: string | null) { + filterProjectId = id; + }, + + /** + * Set filter label IDs + */ + setFilterLabelIds(ids: string[]) { + filterLabelIds = ids; + }, + + /** + * Set filter search query + */ + setFilterSearchQuery(query: string) { + filterSearchQuery = query; + }, + + /** + * Clear all filters + */ + clearFilters() { + filterPriorities = []; + filterProjectId = null; + filterLabelIds = []; + filterSearchQuery = ''; + }, + /** * Reset to default state */ @@ -156,5 +214,9 @@ export const viewStore = { sortBy = 'order'; sortOrder = 'asc'; showCompleted = false; + filterPriorities = []; + filterProjectId = null; + filterLabelIds = []; + filterSearchQuery = ''; }, }; diff --git a/apps/todo/apps/web/src/routes/(app)/+page.svelte b/apps/todo/apps/web/src/routes/(app)/+page.svelte index 6d39410cb..f0e8dc919 100644 --- a/apps/todo/apps/web/src/routes/(app)/+page.svelte +++ b/apps/todo/apps/web/src/routes/(app)/+page.svelte @@ -12,6 +12,29 @@ let isLoading = $state(true); + // Apply viewStore filters to task lists + function applyFilters(tasks: Task[]): Task[] { + let filtered = tasks; + if (viewStore.filterPriorities.length > 0) { + filtered = filtered.filter((t) => viewStore.filterPriorities.includes(t.priority)); + } + if (viewStore.filterProjectId) { + filtered = filtered.filter((t) => t.projectId === viewStore.filterProjectId); + } + if (viewStore.filterLabelIds.length > 0) { + filtered = filtered.filter((t) => + t.labels?.some((l) => viewStore.filterLabelIds.includes(l.id)) + ); + } + if (viewStore.filterSearchQuery.trim()) { + const q = viewStore.filterSearchQuery.toLowerCase(); + filtered = filtered.filter( + (t) => t.title.toLowerCase().includes(q) || t.description?.toLowerCase().includes(q) + ); + } + return filtered; + } + onMount(async () => { viewStore.setToday(); @@ -25,20 +48,22 @@ isLoading = false; }); - // Derived task lists - let overdueTasks = $derived(tasksStore.overdueTasks); - let todayTasks = $derived(tasksStore.todayTasks); - let completedTasks = $derived(tasksStore.completedTasks); + // Derived task lists (with filters applied) + let overdueTasks = $derived(applyFilters(tasksStore.overdueTasks)); + let todayTasks = $derived(applyFilters(tasksStore.todayTasks)); + let completedTasks = $derived(applyFilters(tasksStore.completedTasks)); // Tomorrow's tasks let tomorrowDate = $derived(addDays(startOfDay(new Date()), 1)); let dayAfterTomorrowDate = $derived(addDays(startOfDay(new Date()), 2)); let tomorrowTasks = $derived( - tasksStore.tasks.filter((task) => { - if (!task.dueDate || task.isCompleted) return false; - const taskDate = startOfDay(new Date(task.dueDate)); - return taskDate.getTime() === tomorrowDate.getTime(); - }) + applyFilters( + tasksStore.tasks.filter((task) => { + if (!task.dueDate || task.isCompleted) return false; + const taskDate = startOfDay(new Date(task.dueDate)); + return taskDate.getTime() === tomorrowDate.getTime(); + }) + ) ); // Group upcoming tasks by day (starting from day after tomorrow) @@ -49,11 +74,13 @@ // Start from day after tomorrow (day 2) through day 7 for (let i = 2; i <= 7; i++) { const date = addDays(today, i); - const dayTasks = tasksStore.tasks.filter((task) => { - if (!task.dueDate || task.isCompleted) return false; - const taskDate = startOfDay(new Date(task.dueDate)); - return taskDate.getTime() === date.getTime(); - }); + const dayTasks = applyFilters( + tasksStore.tasks.filter((task) => { + if (!task.dueDate || task.isCompleted) return false; + const taskDate = startOfDay(new Date(task.dueDate)); + return taskDate.getTime() === date.getTime(); + }) + ); if (dayTasks.length > 0) { const label = format(date, 'EEEE, d. MMMM', { locale: de }); diff --git a/apps/todo/apps/web/src/routes/(app)/kanban/+page.svelte b/apps/todo/apps/web/src/routes/(app)/kanban/+page.svelte index 162f1e5ef..a751e2646 100644 --- a/apps/todo/apps/web/src/routes/(app)/kanban/+page.svelte +++ b/apps/todo/apps/web/src/routes/(app)/kanban/+page.svelte @@ -2,7 +2,8 @@ import { onMount, onDestroy } from 'svelte'; import type { TaskPriority } from '@todo/shared'; import { kanbanStore } from '$lib/stores/kanban.svelte'; - import { KanbanBoard, KanbanFilters, BoardNavigation } from '$lib/components/kanban'; + import { KanbanBoard, BoardNavigation } from '$lib/components/kanban'; + import TaskFilters from '$lib/components/TaskFilters.svelte'; // Filter state let filterPriorities = $state([]); @@ -218,16 +219,19 @@ {#if showFilters}
- (filterPriorities = priorities)} - onProjectChange={(projectId) => (filterProjectId = projectId)} - onLabelsChange={(labelIds) => (filterLabelIds = labelIds)} - onSearchChange={(query) => (filterSearchQuery = query)} + onPrioritiesChange={(priorities: TaskPriority[]) => (filterPriorities = priorities)} + onProjectChange={(projectId: string | null) => (filterProjectId = projectId)} + onLabelsChange={(labelIds: string[]) => (filterLabelIds = labelIds)} + onSearchChange={(query: string) => (filterSearchQuery = query)} onClearFilters={clearFilters} + showSearch={true} + showLabels={true} />
{/if}