From 59e535af943c929404f25c0b1a20567a786a6d79 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 31 Mar 2026 12:41:28 +0200 Subject: [PATCH] refactor(todo): move ViewSelector behind Layout pill, simplify homepage - Move board view management (ViewSelector, activeViewId, ViewEditorModal) from +page.svelte to +layout.svelte - Layout pill in PillNav now toggles ViewSelector strip visibility - +page.svelte reduced to minimal BoardViewRenderer with context-provided view - Provide activeView via Svelte context from layout - Fix broken import in TaskItem.svelte (linter artifact) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/lib/components/TaskItem.svelte | 2 +- .../apps/web/src/routes/(app)/+layout.svelte | 134 +++++- .../apps/web/src/routes/(app)/+page.svelte | 450 +----------------- 3 files changed, 135 insertions(+), 451 deletions(-) diff --git a/apps/todo/apps/web/src/lib/components/TaskItem.svelte b/apps/todo/apps/web/src/lib/components/TaskItem.svelte index f59a5f743..bc1cb0c2c 100644 --- a/apps/todo/apps/web/src/lib/components/TaskItem.svelte +++ b/apps/todo/apps/web/src/lib/components/TaskItem.svelte @@ -19,8 +19,8 @@ import { contactsStore } from '$lib/stores/contacts.svelte'; import { ContactAvatar, ContactSelector } from '@manacore/shared-ui'; import SubtaskList from './SubtaskList.svelte'; - import { import { Check, CheckSquare, DotsSixVertical } from '@manacore/shared-icons'; + import { PrioritySelector, StorypointsSelector, DurationPicker, diff --git a/apps/todo/apps/web/src/routes/(app)/+layout.svelte b/apps/todo/apps/web/src/routes/(app)/+layout.svelte index 99f104b0e..82fb8b249 100644 --- a/apps/todo/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/todo/apps/web/src/routes/(app)/+layout.svelte @@ -52,7 +52,15 @@ import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui'; import { TodoEvents } from '@manacore/shared-utils/analytics'; import { todoStore, taskCollection } from '$lib/data/local-store'; - import { useAllTasks, useAllProjects, getActiveProjects } from '$lib/data/task-queries'; + import type { LocalBoardView } from '$lib/data/local-store'; + import { + useAllTasks, + useAllProjects, + useAllBoardViews, + getActiveProjects, + } from '$lib/data/task-queries'; + import { boardViewsStore } from '$lib/stores/board-views.svelte'; + import { ViewSelector, ViewEditorModal } from '$lib/components/board-views'; import SyncIndicator from '$lib/components/SyncIndicator.svelte'; import { List, X } from '@manacore/shared-icons'; @@ -61,10 +69,90 @@ const allProjects = useAllProjects(); const allTags = useAllSharedTags(); + // ─── Board View Management ────────────────────────────── + const boardViews = useAllBoardViews(); + const ACTIVE_VIEW_KEY = 'todo:activeViewId'; + let activeViewId = $state(null); + + // Auto-select first view when views load and nothing is selected + $effect(() => { + if (boardViews.value.length > 0 && !activeViewId) { + const stored = + typeof localStorage !== 'undefined' ? localStorage.getItem(ACTIVE_VIEW_KEY) : null; + activeViewId = + stored && boardViews.value.find((v) => v.id === stored) ? stored : boardViews.value[0].id; + } + if ( + activeViewId && + boardViews.value.length > 0 && + !boardViews.value.find((v) => v.id === activeViewId) + ) { + activeViewId = boardViews.value[0].id; + } + }); + + let activeView = $derived(boardViews.value.find((v) => v.id === activeViewId) ?? null); + + function handleSelectView(viewId: string) { + activeViewId = viewId; + localStorage.setItem(ACTIVE_VIEW_KEY, viewId); + } + + // ViewSelector visibility (toggled via Layout pill) + let isViewSelectorVisible = $state(false); + + // View Editor Modal + let showViewEditor = $state(false); + let editingView = $state(null); + + function handleCreateView() { + editingView = null; + showViewEditor = true; + } + + function handleEditView(view: LocalBoardView) { + editingView = view; + showViewEditor = true; + } + + async function handleSaveView(data: Partial) { + if (editingView) { + await boardViewsStore.updateView(editingView.id, data); + } else { + const newView = await boardViewsStore.createView({ + name: data.name ?? 'Neue View', + icon: data.icon ?? 'columns', + groupBy: data.groupBy ?? 'status', + layout: data.layout ?? 'kanban', + columns: data.columns ?? [], + order: boardViews.value.length, + }); + if (newView?.id) handleSelectView(newView.id); + } + showViewEditor = false; + editingView = null; + } + + async function handleDeleteView() { + if (!editingView) return; + await boardViewsStore.deleteView(editingView.id); + showViewEditor = false; + editingView = null; + } + + async function handleReorderViews(viewIds: string[]) { + await boardViewsStore.reorderViews(viewIds); + } + // Provide data to child components via Svelte context setContext('projects', allProjects); setContext('tasks', allTasks); setContext('tags', allTags); + setContext('activeView', { + get value() { + return activeView; + }, + }); // Edit mode state — shared between layout (PillNav button) and page (editor) let editMode = $state(false); @@ -249,12 +337,12 @@ // Keep navRoutes for keyboard shortcuts (Ctrl+1-3) const viewRoutes: Record = { fokus: '/', uebersicht: '/', matrix: '/' }; - // Handle edit mode toggle - function handleEditToggle() { - editMode = !editMode; + // Handle view selector toggle (Layout pill) + function handleViewSelectorToggle() { + isViewSelectorVisible = !isViewSelectorVisible; } - // Filter, Tags, and Edit stay as standalone pills (toggle behavior, not navigation) + // Filter, Tags, and Layout stay as standalone pills (toggle behavior, not navigation) let baseNavItems = $derived([ { href: '/', @@ -274,10 +362,10 @@ ? [ { href: '/', - label: editMode ? 'Fertig' : 'Layout', - icon: editMode ? 'check' : 'grid', - onClick: handleEditToggle, - active: editMode, + label: activeView?.name ?? 'Layout', + icon: 'grid', + onClick: handleViewSelectorToggle, + active: isViewSelectorVisible, }, ] : []), @@ -462,6 +550,18 @@ ariaLabel="Hauptnavigation" /> + + {#if isViewSelectorVisible && ($page.url.pathname === '/' || $page.url.pathname === '')} + + {/if} + {#if isTagStripVisible} - {#if $page.url.pathname === '/' || $page.url.pathname === '/kanban' || $page.url.pathname === '/statistics'} + + {#if $page.url.pathname === '/' || $page.url.pathname === '/statistics'} {/if} + + + { + showViewEditor = false; + editingView = null; + }} + />