diff --git a/apps/calendar/apps/web/src/hooks.server.ts b/apps/calendar/apps/web/src/hooks.server.ts index 9bff13ef1..8e3424f74 100644 --- a/apps/calendar/apps/web/src/hooks.server.ts +++ b/apps/calendar/apps/web/src/hooks.server.ts @@ -21,8 +21,7 @@ const PUBLIC_BACKEND_URL_CLIENT = 'http://localhost:3014'; const PUBLIC_STT_URL = process.env.PUBLIC_STT_URL || 'https://stt-api.mana.how'; -// Cross-app integration URLs (for todo and contacts APIs) -const PUBLIC_TODO_BACKEND_URL = process.env.PUBLIC_TODO_BACKEND_URL || 'http://localhost:3018'; +// Cross-app integration URLs (for contacts API) const PUBLIC_CONTACTS_API_URL = process.env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015'; const PUBLIC_GLITCHTIP_DSN = process.env.PUBLIC_GLITCHTIP_DSN || ''; @@ -35,7 +34,6 @@ export const handle: Handle = async ({ event, resolve }) => { window.__PUBLIC_MANA_CORE_AUTH_URL__ = ${JSON.stringify(PUBLIC_MANA_CORE_AUTH_URL_CLIENT)}; window.__PUBLIC_BACKEND_URL__ = ${JSON.stringify(PUBLIC_BACKEND_URL_CLIENT)}; window.__PUBLIC_STT_URL__ = ${JSON.stringify(PUBLIC_STT_URL)}; -window.__PUBLIC_TODO_BACKEND_URL__ = ${JSON.stringify(PUBLIC_TODO_BACKEND_URL)}; window.__PUBLIC_CONTACTS_API_URL__ = ${JSON.stringify(PUBLIC_CONTACTS_API_URL)}; window.__PUBLIC_GLITCHTIP_DSN__ = ${JSON.stringify(PUBLIC_GLITCHTIP_DSN)}; `; @@ -48,7 +46,6 @@ window.__PUBLIC_GLITCHTIP_DSN__ = ${JSON.stringify(PUBLIC_GLITCHTIP_DSN)}; PUBLIC_MANA_CORE_AUTH_URL_CLIENT, PUBLIC_BACKEND_URL_CLIENT, PUBLIC_STT_URL, - PUBLIC_TODO_BACKEND_URL, PUBLIC_CONTACTS_API_URL, ], }); diff --git a/apps/calendar/apps/web/src/lib/api/todos.ts b/apps/calendar/apps/web/src/lib/api/todos.ts deleted file mode 100644 index ba7891683..000000000 --- a/apps/calendar/apps/web/src/lib/api/todos.ts +++ /dev/null @@ -1,382 +0,0 @@ -/** - * Cross-App API Client for Todo Backend - * Allows Calendar app to fetch/manage todos from the Todo service - */ - -import { browser } from '$app/environment'; -import { env } from '$env/dynamic/public'; -import { createApiClient, buildQueryString, type ApiResult } from '@manacore/shared-api-client'; -import { authStore } from '$lib/stores/auth.svelte'; - -// Get todo API base URL from injected window variable (browser) or env (SSR) -function getTodoApiBase(): string { - if (browser && typeof window !== 'undefined') { - const injectedUrl = (window as unknown as { __PUBLIC_TODO_BACKEND_URL__?: string }) - .__PUBLIC_TODO_BACKEND_URL__; - if (injectedUrl) return injectedUrl; - } - return env.PUBLIC_TODO_BACKEND_URL || 'http://localhost:3018'; -} - -let _todoClient: ReturnType | null = null; - -function getTodoClient() { - if (!_todoClient) { - _todoClient = createApiClient({ - baseUrl: getTodoApiBase(), - apiPrefix: '/api/v1', - getAuthToken: () => authStore.getValidToken(), - timeout: 30000, - debug: import.meta.env.DEV, - useRuntimeUrl: false, - }); - } - return _todoClient; -} - -// For backwards compatibility -const todoClient = { - get: (endpoint: string) => getTodoClient().get(endpoint), - post: (endpoint: string, body?: unknown) => getTodoClient().post(endpoint, body), - put: (endpoint: string, body?: unknown) => getTodoClient().put(endpoint, body), - patch: (endpoint: string, body?: unknown) => getTodoClient().patch(endpoint, body), - delete: (endpoint: string) => getTodoClient().delete(endpoint), -}; - -// ============================================ -// Types (mirrored from @todo/shared for cross-app use) -// ============================================ - -export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'; -export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'; - -export interface Subtask { - id: string; - title: string; - isCompleted: boolean; - completedAt?: string | null; - order: number; -} - -export interface Label { - id: string; - userId: string; - name: string; - color: string; - createdAt: string; - updatedAt: string; -} - -export interface Project { - id: string; - userId: string; - name: string; - description?: string | null; - color: string; - icon?: string | null; - order: number; - isArchived: boolean; - isDefault: boolean; - createdAt: string; - updatedAt: string; -} - -export interface TaskMetadata { - notes?: string; - attachments?: string[]; - linkedCalendarEventId?: string | null; - storyPoints?: number | null; - effectiveDuration?: { - value: number; - unit: 'minutes' | 'hours' | 'days'; - } | null; - funRating?: number | null; -} - -export interface Task { - id: string; - projectId?: string | null; - userId: string; - parentTaskId?: string | null; - title: string; - description?: string | null; - dueDate?: string | null; - dueTime?: string | null; - startDate?: string | null; - // Time-Blocking (for calendar integration) - scheduledDate?: string | null; - scheduledStartTime?: string | null; // HH:mm format - scheduledEndTime?: string | null; // HH:mm format - estimatedDuration?: number | null; // Duration in minutes - priority: TaskPriority; - status: TaskStatus; - isCompleted: boolean; - completedAt?: string | null; - order: number; - columnId?: string | null; - columnOrder?: number; - recurrenceRule?: string | null; - recurrenceEndDate?: string | null; - lastOccurrence?: string | null; - subtasks?: Subtask[] | null; - metadata?: TaskMetadata | null; - labels?: Label[]; - project?: Project | null; - createdAt: string; - updatedAt: string; -} - -export interface CreateTaskInput { - title: string; - description?: string; - projectId?: string | null; - dueDate?: string | null; - dueTime?: string | null; - // Time-Blocking - scheduledDate?: string | null; - scheduledStartTime?: string | null; - scheduledEndTime?: string | null; - estimatedDuration?: number | null; - priority?: TaskPriority; - labelIds?: string[]; - subtasks?: Omit[]; - recurrenceRule?: string | null; - metadata?: TaskMetadata; -} - -export interface UpdateTaskInput { - title?: string; - description?: string | null; - projectId?: string | null; - dueDate?: string | null; - dueTime?: string | null; - // Time-Blocking - scheduledDate?: string | null; - scheduledStartTime?: string | null; - scheduledEndTime?: string | null; - estimatedDuration?: number | null; - priority?: TaskPriority; - status?: TaskStatus; - isCompleted?: boolean; - subtasks?: Subtask[] | null; - recurrenceRule?: string | null; - metadata?: TaskMetadata | null; - labelIds?: string[]; -} - -export interface TaskQuery { - projectId?: string; - labelId?: string; - priority?: TaskPriority; - status?: TaskStatus; - isCompleted?: boolean; - dueDateFrom?: string; - dueDateTo?: string; - search?: string; - sortBy?: 'dueDate' | 'priority' | 'createdAt' | 'order'; - sortOrder?: 'asc' | 'desc'; - limit?: number; - offset?: number; -} - -// ============================================ -// API Response Types -// ============================================ - -interface TasksResponse { - tasks: Task[]; -} - -interface TaskResponse { - task: Task; -} - -interface ProjectsResponse { - projects: Project[]; -} - -interface LabelsResponse { - labels: Label[]; -} - -// ============================================ -// API Client (using shared base client) -// ============================================ - -async function fetchTodoApi( - endpoint: string, - options: { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; body?: unknown } = {} -): Promise<{ data: T | null; error: Error | null }> { - const { method = 'GET', body } = options; - - let result: ApiResult; - switch (method) { - case 'POST': - result = await todoClient.post(endpoint, body); - break; - case 'PUT': - result = await todoClient.put(endpoint, body); - break; - case 'PATCH': - result = await todoClient.patch(endpoint, body); - break; - case 'DELETE': - result = await todoClient.delete(endpoint); - break; - default: - result = await todoClient.get(endpoint); - } - - if (result.error) { - return { data: null, error: new Error(result.error.message) }; - } - return { data: result.data, error: null }; -} - -// ============================================ -// Task API Functions -// ============================================ - -export async function getTasks( - query: TaskQuery = {} -): Promise<{ data: Task[] | null; error: Error | null }> { - const queryString = buildQueryString( - query as Record - ); - const result = await fetchTodoApi(`/tasks${queryString}`); - return { - data: result.data?.tasks || null, - error: result.error, - }; -} - -export async function getTask(id: string): Promise<{ data: Task | null; error: Error | null }> { - const result = await fetchTodoApi(`/tasks/${id}`); - return { - data: result.data?.task || null, - error: result.error, - }; -} - -export async function createTask( - data: CreateTaskInput -): Promise<{ data: Task | null; error: Error | null }> { - const result = await fetchTodoApi('/tasks', { - method: 'POST', - body: data, - }); - return { - data: result.data?.task || null, - error: result.error, - }; -} - -export async function updateTask( - id: string, - data: UpdateTaskInput -): Promise<{ data: Task | null; error: Error | null }> { - const result = await fetchTodoApi(`/tasks/${id}`, { - method: 'PUT', - body: data, - }); - return { - data: result.data?.task || null, - error: result.error, - }; -} - -export async function deleteTask(id: string): Promise<{ error: Error | null }> { - const result = await fetchTodoApi(`/tasks/${id}`, { - method: 'DELETE', - }); - return { error: result.error }; -} - -export async function completeTask( - id: string -): Promise<{ data: Task | null; error: Error | null }> { - const result = await fetchTodoApi(`/tasks/${id}/complete`, { - method: 'POST', - }); - return { - data: result.data?.task || null, - error: result.error, - }; -} - -export async function uncompleteTask( - id: string -): Promise<{ data: Task | null; error: Error | null }> { - const result = await fetchTodoApi(`/tasks/${id}/uncomplete`, { - method: 'POST', - }); - return { - data: result.data?.task || null, - error: result.error, - }; -} - -export async function getTodayTasks(): Promise<{ data: Task[] | null; error: Error | null }> { - const result = await fetchTodoApi('/tasks/today'); - return { - data: result.data?.tasks || null, - error: result.error, - }; -} - -export async function getUpcomingTasks(): Promise<{ data: Task[] | null; error: Error | null }> { - const result = await fetchTodoApi('/tasks/upcoming'); - return { - data: result.data?.tasks || null, - error: result.error, - }; -} - -// ============================================ -// Project API Functions -// ============================================ - -export async function getProjects(): Promise<{ data: Project[] | null; error: Error | null }> { - const result = await fetchTodoApi('/projects'); - return { - data: result.data?.projects || null, - error: result.error, - }; -} - -// ============================================ -// Label API Functions -// ============================================ - -export async function getLabels(): Promise<{ data: Label[] | null; error: Error | null }> { - const result = await fetchTodoApi('/labels'); - return { - data: result.data?.labels || null, - error: result.error, - }; -} - -// ============================================ -// Priority Colors Helper -// ============================================ - -export const PRIORITY_COLORS: Record = { - urgent: 'hsl(var(--color-danger))', - high: 'hsl(var(--color-warning))', - medium: 'hsl(var(--color-accent))', - low: 'hsl(var(--color-success))', -}; - -export const PRIORITY_LABELS: Record = { - urgent: 'Dringend', - high: 'Wichtig', - medium: 'Normal', - low: 'Später', -}; - -export const PRIORITY_ORDER: Record = { - urgent: 0, - high: 1, - medium: 2, - low: 3, -}; diff --git a/apps/calendar/apps/web/src/lib/components/agenda/AgendaFilters.svelte b/apps/calendar/apps/web/src/lib/components/agenda/AgendaFilters.svelte index 70b5617a0..f361e72a5 100644 --- a/apps/calendar/apps/web/src/lib/components/agenda/AgendaFilters.svelte +++ b/apps/calendar/apps/web/src/lib/components/agenda/AgendaFilters.svelte @@ -1,24 +1,13 @@
-
- - -
-
@@ -69,65 +35,30 @@ .agenda-filters { display: flex; align-items: center; - justify-content: space-between; + justify-content: flex-end; gap: 1rem; padding: 0.75rem 1rem; background: hsl(var(--color-surface)); border-radius: var(--radius-lg); border: 1px solid hsl(var(--color-border)); } - .filter-group { display: flex; align-items: center; gap: 0.5rem; } - - .type-toggles { - display: flex; - gap: 0.375rem; - } - - .filter-toggle { - display: flex; - align-items: center; - gap: 0.375rem; - padding: 0.375rem 0.75rem; - border-radius: var(--radius-md); - border: 1px solid hsl(var(--color-border)); - background: transparent; - color: hsl(var(--color-muted-foreground)); - font-size: 0.8125rem; - font-weight: 500; - cursor: pointer; - transition: all 150ms ease; - } - - .filter-toggle:hover { - border-color: hsl(var(--color-primary)); - color: hsl(var(--color-primary)); - } - - .filter-toggle.active { - background: hsl(var(--color-primary) / 0.1); - border-color: hsl(var(--color-primary)); - color: hsl(var(--color-primary)); - } - .range-selector { display: flex; align-items: center; gap: 0.5rem; color: hsl(var(--color-muted-foreground)); } - @media (max-width: 480px) { .agenda-filters { flex-direction: column; align-items: stretch; gap: 0.75rem; } - .filter-group { justify-content: center; } diff --git a/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte b/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte index 0d157dd06..34e56cc4d 100644 --- a/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte +++ b/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte @@ -1,110 +1,46 @@ -{#if type === 'event' && event} - -{:else if type === 'todo' && todo} -
-
- -
- + diff --git a/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte index 4823cbc71..6a7323187 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte @@ -11,9 +11,7 @@ } from '$lib/data/queries'; import type { Calendar } from '@calendar/shared'; import { searchStore } from '$lib/stores/search.svelte'; - import { todosStore } from '$lib/stores/todos.svelte'; import { birthdaysStore, type BirthdayEvent } from '$lib/stores/birthdays.svelte'; - import TodoDayCell from './TodoDayCell.svelte'; import BirthdayPopover from '$lib/components/birthday/BirthdayPopover.svelte'; import { useBirthdayPopover } from '$lib/composables'; import { goto } from '$app/navigation'; @@ -327,11 +325,6 @@
- - {#if todosStore.serviceAvailable} - - {/if} -
{#each getEventsForDay(day) as event} {@const isBeingDragged = isDragging && draggedEvent?.id === event.id} diff --git a/apps/calendar/apps/web/src/lib/components/calendar/TaskBlock.svelte b/apps/calendar/apps/web/src/lib/components/calendar/TaskBlock.svelte deleted file mode 100644 index c47f882ed..000000000 --- a/apps/calendar/apps/web/src/lib/components/calendar/TaskBlock.svelte +++ /dev/null @@ -1,297 +0,0 @@ - - -
- - {#if onResizeStart && !task.isCompleted} -
- {/if} - -
- - - -
- - {task.scheduledStartTime || ''} - {#if task.scheduledEndTime} - - {task.scheduledEndTime} - {/if} - - {task.title} -
- - - {#if onResizeStart && !task.isCompleted} -
- {/if} -
- - diff --git a/apps/calendar/apps/web/src/lib/components/calendar/TodoDayCell.svelte b/apps/calendar/apps/web/src/lib/components/calendar/TodoDayCell.svelte deleted file mode 100644 index d2e0f44fa..000000000 --- a/apps/calendar/apps/web/src/lib/components/calendar/TodoDayCell.svelte +++ /dev/null @@ -1,121 +0,0 @@ - - -{#if todosForDay.length > 0} -
- {#each visibleTodos as task (task.id)} - - {/each} - - {#if overflowCount > 0} - +{overflowCount} Aufgaben - {/if} -
-{/if} - - -{#if selectedTask} - -{/if} - - diff --git a/apps/calendar/apps/web/src/lib/components/calendar/TodoRow.svelte b/apps/calendar/apps/web/src/lib/components/calendar/TodoRow.svelte deleted file mode 100644 index 178112b98..000000000 --- a/apps/calendar/apps/web/src/lib/components/calendar/TodoRow.svelte +++ /dev/null @@ -1,169 +0,0 @@ - - -{#if todosForDay.length > 0} -
- Aufgaben: -
- {#each visibleTodos as task (task.id)} - - - {/each} - - {#if overflowCount > 0} - - {/if} -
-
-{/if} - - -{#if selectedTask} - -{/if} - - diff --git a/apps/calendar/apps/web/src/lib/components/calendar/TodoSidebarSection.svelte b/apps/calendar/apps/web/src/lib/components/calendar/TodoSidebarSection.svelte deleted file mode 100644 index 997751262..000000000 --- a/apps/calendar/apps/web/src/lib/components/calendar/TodoSidebarSection.svelte +++ /dev/null @@ -1,323 +0,0 @@ - - -
- -
- - -
- - - {#if isExpanded} -
- {#if !todosStore.serviceAvailable} -
- - Todo-Service nicht erreichbar -
- {:else if todosStore.loading} -
-
- Laden... -
- {:else if displayTodos.length === 0} -
- - Keine offenen Aufgaben -
- {:else} -
- {#each displayTodos as task (task.id)} - handleTaskClick(task)} - /> - {/each} -
- - {#if totalActiveCount > maxItems} - - {/if} - {/if} - - - {#if showQuickAdd} -
- -
- {/if} -
- {/if} -
- - -{#if selectedTask} - -{/if} - - diff --git a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte index 54aacf4d3..19d77cf5a 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte @@ -12,7 +12,6 @@ } from '$lib/data/queries'; import type { Calendar } from '@calendar/shared'; import { searchStore } from '$lib/stores/search.svelte'; - import { todosStore, type Task } from '$lib/stores/todos.svelte'; import { birthdaysStore, type BirthdayEvent } from '$lib/stores/birthdays.svelte'; import BirthdayPopover from '$lib/components/birthday/BirthdayPopover.svelte'; import { @@ -20,8 +19,6 @@ useCurrentTimeIndicator, useBirthdayPopover, useEventDragDrop, - useTaskDragDrop, - useSidebarDrop, useDragToCreate, useCalendarKeyboard, } from '$lib/composables'; @@ -34,7 +31,6 @@ type OverflowEvents, } from '$lib/utils/eventFiltering'; import EventCard from './EventCard.svelte'; - import TaskBlock from './TaskBlock.svelte'; import { ContextMenu, type ContextMenuItem } from '@manacore/shared-ui'; import { goto } from '$app/navigation'; import { @@ -58,10 +54,9 @@ date?: Date; onQuickCreate?: (date: Date, position: { x: number; y: number }, endDate?: Date) => void; onEventClick?: (event: CalendarEvent) => void; - onTaskClick?: (task: Task) => void; } - let { date, onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + let { date, onQuickCreate, onEventClick }: Props = $props(); // Get calendars and events from layout context (live queries) const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars'); @@ -150,18 +145,6 @@ minutesToPercent, })); - const taskDragDrop = useTaskDragDrop(() => ({ - containerEl: daysContainerEl, - days, - firstVisibleHour, - totalVisibleHours, - })); - - const sidebarDrop = useSidebarDrop(() => ({ - firstVisibleHour, - totalVisibleHours, - })); - const dragToCreate = useDragToCreate(() => ({ containerEl: daysContainerEl, days, @@ -170,11 +153,7 @@ totalVisibleHours, hourHeight: HOUR_HEIGHT, minutesToPercent, - isOtherOperationActive: () => - eventDragDrop.isDragging || - eventDragDrop.isResizing || - taskDragDrop.isTaskDragging || - taskDragDrop.isTaskResizing, + isOtherOperationActive: () => eventDragDrop.isDragging || eventDragDrop.isResizing, onCreateEnd: (startTime, endTime, position) => { if (onQuickCreate) { onQuickCreate(startTime, position, endTime); @@ -189,10 +168,6 @@ isActive: () => eventDragDrop.isDragging || eventDragDrop.isResizing, cancel: eventDragDrop.cancel, }, - { - isActive: () => taskDragDrop.isTaskDragging || taskDragDrop.isTaskResizing, - cancel: taskDragDrop.cancel, - }, { isActive: () => dragToCreate.isCreating, cancel: dragToCreate.cancel }, ]); @@ -309,37 +284,6 @@ return `${formatEventTime(event.startTime)} - ${formatEventTime(event.endTime)}`; } - /** - * Get style for a scheduled task (time-blocking) - */ - function getTaskStyle(task: Task): string { - if (!task.scheduledStartTime) return ''; - - // Parse HH:mm time - const [startHour, startMin] = task.scheduledStartTime.split(':').map(Number); - const startMinutes = startHour * 60 + startMin; - - // Calculate duration - use estimatedDuration or scheduledEndTime or default 30 min - let duration = task.estimatedDuration || 30; - if (task.scheduledEndTime) { - const [endHour, endMin] = task.scheduledEndTime.split(':').map(Number); - const endMinutes = endHour * 60 + endMin; - duration = endMinutes - startMinutes; - } - - const top = minutesToPercent(startMinutes); - const height = Math.max((duration / (totalVisibleHours * 60)) * 100, 2); - - return `top: ${top}%; height: ${height}%;`; - } - - /** - * Get scheduled tasks for a specific day - */ - function getScheduledTasksForDay(day: Date): Task[] { - return todosStore.getScheduledTasksForDay(day); - } - function formatEventTime(date: Date | string): string { return settingsStore.formatTime(toDate(date)); } @@ -514,14 +458,10 @@
sidebarDrop.handleDragOver(e, day)} - ondragleave={sidebarDrop.handleDragLeave} - ondrop={(e) => sidebarDrop.handleDrop(e, day)} > {#each hours as hour}
{/each} - - {#if settingsStore.showTasksInCalendar} - {#each getScheduledTasksForDay(day) as task (task.id)} - {@const isTaskBeingDragged = - taskDragDrop.isTaskDragging && taskDragDrop.draggedTask?.id === task.id} - {@const isTaskBeingResized = - taskDragDrop.isTaskResizing && taskDragDrop.resizeTask?.id === task.id} - {@const isTaskCrossDayDrag = - isTaskBeingDragged && - taskDragDrop.taskDragTargetDay !== null && - !isSameDay(day, taskDragDrop.taskDragTargetDay)} - - {/each} - - - {#if taskDragDrop.isTaskDragging && taskDragDrop.draggedTask && taskDragDrop.taskDragTargetDay && isSameDay(day, taskDragDrop.taskDragTargetDay) && !getScheduledTasksForDay(day).some((t) => t.id === taskDragDrop.draggedTask!.id)} - - {/if} - {/if} - {#if eventDragDrop.isDragging && eventDragDrop.draggedEvent && eventDragDrop.dragTargetDay && isSameDay(day, eventDragDrop.dragTargetDay) && !getEventsForDay(day).some((e) => e.id === eventDragDrop.draggedEvent!.id)} - import type { TaskPriority } from '$lib/api/todos'; - import { PRIORITY_COLORS, PRIORITY_LABELS } from '$lib/api/todos'; - - interface Props { - priority: TaskPriority; - variant?: 'dot' | 'badge' | 'pill'; - size?: 'sm' | 'md'; - showLabel?: boolean; - } - - let { priority, variant = 'dot', size = 'md', showLabel = false }: Props = $props(); - - const color = $derived(PRIORITY_COLORS[priority]); - const label = $derived(PRIORITY_LABELS[priority]); - - -{#if variant === 'dot'} - -{:else if variant === 'badge'} - - {#if showLabel} - {label} - {:else} - {priority.charAt(0).toUpperCase()} - {/if} - -{:else if variant === 'pill'} - - - {#if showLabel} - {label} - {/if} - -{/if} - - diff --git a/apps/calendar/apps/web/src/lib/components/todo/QuickAddTodo.svelte b/apps/calendar/apps/web/src/lib/components/todo/QuickAddTodo.svelte deleted file mode 100644 index 0511d4157..000000000 --- a/apps/calendar/apps/web/src/lib/components/todo/QuickAddTodo.svelte +++ /dev/null @@ -1,227 +0,0 @@ - - -{#if showButton && !isExpanded} - -{:else} -
- - - - {#if showButton} - - {/if} - - -
-{/if} - - diff --git a/apps/calendar/apps/web/src/lib/components/todo/TodoCheckbox.svelte b/apps/calendar/apps/web/src/lib/components/todo/TodoCheckbox.svelte deleted file mode 100644 index f82cdba07..000000000 --- a/apps/calendar/apps/web/src/lib/components/todo/TodoCheckbox.svelte +++ /dev/null @@ -1,130 +0,0 @@ - - - - - diff --git a/apps/calendar/apps/web/src/lib/components/todo/TodoDetailModal.svelte b/apps/calendar/apps/web/src/lib/components/todo/TodoDetailModal.svelte deleted file mode 100644 index 9374f919f..000000000 --- a/apps/calendar/apps/web/src/lib/components/todo/TodoDetailModal.svelte +++ /dev/null @@ -1,763 +0,0 @@ - - - - - - - - diff --git a/apps/calendar/apps/web/src/lib/components/todo/TodoItem.svelte b/apps/calendar/apps/web/src/lib/components/todo/TodoItem.svelte deleted file mode 100644 index afa33e4d2..000000000 --- a/apps/calendar/apps/web/src/lib/components/todo/TodoItem.svelte +++ /dev/null @@ -1,318 +0,0 @@ - - - -
- - -
-
- {#if showPriority && variant !== 'minimal'} - - {/if} - - {task.title} - - {#if subtaskProgress && variant === 'default'} - - {subtaskProgress.completed}/{subtaskProgress.total} - - {/if} -
- - {#if variant !== 'minimal'} -
- {#if showDueDate && dueDateLabel} - - {dueDateLabel} - - {/if} - - {#if showProject && task.project} - - {task.project.name} - - {/if} - - {#if task.labels && task.labels.length > 0 && variant === 'default'} -
- {#each task.labels.slice(0, 2) as label} - - {label.name} - - {/each} - {#if task.labels.length > 2} - +{task.labels.length - 2} - {/if} -
- {/if} -
- {/if} -
-
- - diff --git a/apps/calendar/apps/web/src/lib/composables/index.ts b/apps/calendar/apps/web/src/lib/composables/index.ts index 5a085789d..c03549eec 100644 --- a/apps/calendar/apps/web/src/lib/composables/index.ts +++ b/apps/calendar/apps/web/src/lib/composables/index.ts @@ -14,12 +14,6 @@ export { type EventResizeState, } from './useEventDragDrop.svelte'; -// Task drag/drop and resize -export { useTaskDragDrop, type TaskDragDropConfig } from './useTaskDragDrop.svelte'; - -// Sidebar task drop handling -export { useSidebarDrop, type SidebarDropConfig } from './useSidebarDrop.svelte'; - // Drag-to-create export { useDragToCreate, type DragToCreateConfig } from './useDragToCreate.svelte'; diff --git a/apps/calendar/apps/web/src/lib/composables/useSidebarDrop.svelte.ts b/apps/calendar/apps/web/src/lib/composables/useSidebarDrop.svelte.ts deleted file mode 100644 index 5b975e7c0..000000000 --- a/apps/calendar/apps/web/src/lib/composables/useSidebarDrop.svelte.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Sidebar Task Drop Composable - * Handles dropping tasks from sidebar into calendar day columns - */ - -import { todosStore } from '$lib/stores/todos.svelte'; -import { format } from 'date-fns'; -import { formatTime, getSnapMinutes } from '$lib/utils/drag-helpers'; - -export interface SidebarDropConfig { - /** First visible hour (for filtered hours mode) */ - firstVisibleHour: number; - /** Total visible hours */ - totalVisibleHours: number; - /** Minutes per snap interval (default: 15) */ - snapMinutes?: number; -} - -export function useSidebarDrop(getConfig: () => SidebarDropConfig) { - // Track active drop target (for visual feedback) - let dropTarget = $state<{ day: Date; y: number } | null>(null); - - /** - * Handle dragover event on a day column - */ - function handleDragOver(e: DragEvent, day: Date) { - e.preventDefault(); - if (!e.dataTransfer) return; - - // Check if this is a sidebar task drag - const types = e.dataTransfer.types; - if (!types.includes('application/json')) return; - - e.dataTransfer.dropEffect = 'move'; - dropTarget = { day, y: e.clientY }; - } - - /** - * Handle dragleave event - */ - function handleDragLeave(e: DragEvent) { - // Only clear if leaving the column entirely - const relatedTarget = e.relatedTarget as HTMLElement; - if (!relatedTarget?.closest('.day-column')) { - dropTarget = null; - } - } - - /** - * Handle drop event on a day column - */ - async function handleDrop(e: DragEvent, day: Date) { - e.preventDefault(); - dropTarget = null; - - if (!e.dataTransfer) return; - - const jsonData = e.dataTransfer.getData('application/json'); - if (!jsonData) return; - - try { - const data = JSON.parse(jsonData); - if (data.type !== 'sidebar-task') return; - - const config = getConfig(); - - // Calculate drop time from Y position - const dayColumn = (e.target as HTMLElement).closest('.day-column'); - if (!dayColumn) return; - - const rect = dayColumn.getBoundingClientRect(); - const relativeY = e.clientY - rect.top; - const percentY = Math.max(0, Math.min(100, (relativeY / rect.height) * 100)); - - const minutesPerPercent = (config.totalVisibleHours * 60) / 100; - const rawMinutes = percentY * minutesPerPercent; - const snapMinutes = getSnapMinutes(getConfig().snapMinutes); - const snappedMinutes = Math.round(rawMinutes / snapMinutes) * snapMinutes; - const totalMinutes = config.firstVisibleHour * 60 + snappedMinutes; - - const hours = Math.floor(totalMinutes / 60); - const minutes = totalMinutes % 60; - const startTime = formatTime(hours, minutes); - - // Calculate end time - const duration = data.estimatedDuration || 30; - const endMinutes = totalMinutes + duration; - const endHours = Math.floor(endMinutes / 60); - const endMins = endMinutes % 60; - const endTime = formatTime(endHours, endMins); - - // Update the task with scheduled time - await todosStore.updateTodo(data.taskId, { - scheduledDate: format(day, 'yyyy-MM-dd'), - scheduledStartTime: startTime, - scheduledEndTime: endTime, - estimatedDuration: duration, - }); - } catch (err) { - console.error('Failed to parse drop data:', err); - } - } - - /** - * Clear drop target (use when component unmounts or for manual cleanup) - */ - function clearDropTarget() { - dropTarget = null; - } - - return { - // State (reactive getter) - get dropTarget() { - return dropTarget; - }, - - // Methods - handleDragOver, - handleDragLeave, - handleDrop, - clearDropTarget, - }; -} diff --git a/apps/calendar/apps/web/src/lib/composables/useTaskDragDrop.svelte.ts b/apps/calendar/apps/web/src/lib/composables/useTaskDragDrop.svelte.ts deleted file mode 100644 index d62aa4fe9..000000000 --- a/apps/calendar/apps/web/src/lib/composables/useTaskDragDrop.svelte.ts +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Task Drag & Drop + Resize Composable - * Extracts duplicated task drag/resize logic from WeekView, DayView, MultiDayView - * - * Uses document-level event listeners for smooth drag operations across the entire screen. - */ - -import type { Task } from '$lib/stores/todos.svelte'; -import { todosStore } from '$lib/stores/todos.svelte'; -import { format } from 'date-fns'; -import { formatTime, getSnapMinutes } from '$lib/utils/drag-helpers'; - -export interface TaskDragDropConfig { - /** Reference to the container element for position calculations */ - containerEl: HTMLElement | null; - /** Array of visible days (for multi-day views) or single day (for day view) */ - days: Date[]; - /** First visible hour (for filtered hours mode) */ - firstVisibleHour: number; - /** Total visible hours */ - totalVisibleHours: number; - /** Minutes per snap interval (default: 15) */ - snapMinutes?: number; -} - -export function useTaskDragDrop(getConfig: () => TaskDragDropConfig) { - // ========== Drag State ========== - let isTaskDragging = $state(false); - let draggedTask = $state(null); - let taskDragTargetDay = $state(null); - let taskDragPreviewTop = $state(0); - let taskDragPreviewHeight = $state(0); - - // ========== Resize State ========== - let isTaskResizing = $state(false); - let resizeTask = $state(null); - let taskResizeEdge = $state<'top' | 'bottom'>('bottom'); - let taskResizePreviewTop = $state(0); - let taskResizePreviewHeight = $state(0); - - // Track if we actually moved during drag/resize - let hasMoved = $state(false); - - // ========== Helper Functions ========== - - // ========== Drag Functions ========== - - function startDrag(task: Task, e: PointerEvent) { - e.preventDefault(); - - const config = getConfig(); - isTaskDragging = true; - draggedTask = task; - hasMoved = false; - - // Initialize preview position from task's current time - if (task.scheduledStartTime) { - const [h, m] = task.scheduledStartTime.split(':').map(Number); - const startMinutes = h * 60 + m - config.firstVisibleHour * 60; - taskDragPreviewTop = (startMinutes / (config.totalVisibleHours * 60)) * 100; - } - - const duration = task.estimatedDuration || 30; - taskDragPreviewHeight = (duration / (config.totalVisibleHours * 60)) * 100; - - document.addEventListener('pointermove', handleDragMove); - document.addEventListener('pointerup', handleDragEnd); - } - - function handleDragMove(e: PointerEvent) { - if (!isTaskDragging || !draggedTask) return; - - const config = getConfig(); - hasMoved = true; - - // Find which day column we're over - if (config.containerEl) { - const dayColumns = config.containerEl.querySelectorAll('.day-column'); - for (let i = 0; i < dayColumns.length; i++) { - const col = dayColumns[i]; - const rect = col.getBoundingClientRect(); - if (e.clientX >= rect.left && e.clientX <= rect.right) { - taskDragTargetDay = config.days[i]; - break; - } - } - } - - // Calculate vertical position - const targetColumn = config.containerEl?.querySelector('.day-column'); - if (!targetColumn) return; - - const rect = targetColumn.getBoundingClientRect(); - const relativeY = e.clientY - rect.top; - const percentY = Math.max(0, Math.min(100, (relativeY / rect.height) * 100)); - - // Snap to intervals - const minutesPerPercent = (config.totalVisibleHours * 60) / 100; - const rawMinutes = percentY * minutesPerPercent; - const snapMinutes = getSnapMinutes(getConfig().snapMinutes); - const snappedMinutes = Math.round(rawMinutes / snapMinutes) * snapMinutes; - taskDragPreviewTop = (snappedMinutes / (config.totalVisibleHours * 60)) * 100; - } - - async function handleDragEnd() { - document.removeEventListener('pointermove', handleDragMove); - document.removeEventListener('pointerup', handleDragEnd); - - if (!isTaskDragging || !draggedTask || !hasMoved) { - cleanupDrag(); - return; - } - - const config = getConfig(); - - // Calculate new time from position - const minutesFromStart = (taskDragPreviewTop / 100) * (config.totalVisibleHours * 60); - const totalMinutes = config.firstVisibleHour * 60 + minutesFromStart; - const hours = Math.floor(totalMinutes / 60); - const minutes = Math.round(totalMinutes % 60); - - const newStartTime = formatTime(hours, minutes); - - // Calculate end time based on duration - const duration = draggedTask.estimatedDuration || 30; - const endTotalMinutes = totalMinutes + duration; - const endHours = Math.floor(endTotalMinutes / 60); - const endMins = Math.round(endTotalMinutes % 60); - const newEndTime = formatTime(endHours, endMins); - - await todosStore.updateTodo(draggedTask.id, { - scheduledDate: taskDragTargetDay ? format(taskDragTargetDay, 'yyyy-MM-dd') : undefined, - scheduledStartTime: newStartTime, - scheduledEndTime: newEndTime, - }); - - cleanupDrag(); - } - - function cleanupDrag() { - isTaskDragging = false; - draggedTask = null; - taskDragTargetDay = null; - hasMoved = false; - } - - // ========== Resize Functions ========== - - function startResize(task: Task, edge: 'top' | 'bottom', e: PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - - const config = getConfig(); - isTaskResizing = true; - resizeTask = task; - taskResizeEdge = edge; - hasMoved = false; - - // Initialize preview position - if (task.scheduledStartTime) { - const [h, m] = task.scheduledStartTime.split(':').map(Number); - const startMinutes = h * 60 + m - config.firstVisibleHour * 60; - taskResizePreviewTop = (startMinutes / (config.totalVisibleHours * 60)) * 100; - } - - const duration = task.estimatedDuration || 30; - taskResizePreviewHeight = (duration / (config.totalVisibleHours * 60)) * 100; - - document.addEventListener('pointermove', handleResizeMove); - document.addEventListener('pointerup', handleResizeEnd); - } - - function handleResizeMove(e: PointerEvent) { - if (!isTaskResizing || !resizeTask) return; - - const config = getConfig(); - hasMoved = true; - - const targetColumn = config.containerEl?.querySelector('.day-column'); - if (!targetColumn) return; - - const rect = targetColumn.getBoundingClientRect(); - const relativeY = e.clientY - rect.top; - const percentY = Math.max(0, Math.min(100, (relativeY / rect.height) * 100)); - - const minutesPerPercent = (config.totalVisibleHours * 60) / 100; - const snapMinutes = getSnapMinutes(getConfig().snapMinutes); - - if (taskResizeEdge === 'top') { - // Adjust start time, keep end fixed - const originalEndPercent = taskResizePreviewTop + taskResizePreviewHeight; - const rawMinutes = percentY * minutesPerPercent; - const snappedMinutes = Math.round(rawMinutes / snapMinutes) * snapMinutes; - taskResizePreviewTop = (snappedMinutes / (config.totalVisibleHours * 60)) * 100; - taskResizePreviewHeight = Math.max(2, originalEndPercent - taskResizePreviewTop); - } else { - // Adjust end time, keep start fixed - const rawMinutes = percentY * minutesPerPercent; - const snappedMinutes = Math.round(rawMinutes / snapMinutes) * snapMinutes; - const newBottom = (snappedMinutes / (config.totalVisibleHours * 60)) * 100; - taskResizePreviewHeight = Math.max(2, newBottom - taskResizePreviewTop); - } - } - - async function handleResizeEnd() { - document.removeEventListener('pointermove', handleResizeMove); - document.removeEventListener('pointerup', handleResizeEnd); - - if (!isTaskResizing || !resizeTask || !hasMoved) { - cleanupResize(); - return; - } - - const config = getConfig(); - - // Calculate new times from position - const startMinutes = - (taskResizePreviewTop / 100) * (config.totalVisibleHours * 60) + config.firstVisibleHour * 60; - const endMinutes = - ((taskResizePreviewTop + taskResizePreviewHeight) / 100) * (config.totalVisibleHours * 60) + - config.firstVisibleHour * 60; - - const startHours = Math.floor(startMinutes / 60); - const startMins = Math.round(startMinutes % 60); - const endHours = Math.floor(endMinutes / 60); - const endMins = Math.round(endMinutes % 60); - - const newStartTime = formatTime(startHours, startMins); - const newEndTime = formatTime(endHours, endMins); - const newDuration = Math.round(endMinutes - startMinutes); - - await todosStore.updateTodo(resizeTask.id, { - scheduledStartTime: newStartTime, - scheduledEndTime: newEndTime, - estimatedDuration: newDuration, - }); - - cleanupResize(); - } - - function cleanupResize() { - isTaskResizing = false; - resizeTask = null; - hasMoved = false; - } - - // ========== Combined Cleanup ========== - - function cleanup() { - document.removeEventListener('pointermove', handleDragMove); - document.removeEventListener('pointerup', handleDragEnd); - document.removeEventListener('pointermove', handleResizeMove); - document.removeEventListener('pointerup', handleResizeEnd); - cleanupDrag(); - cleanupResize(); - } - - /** - * Cancel any active drag/resize operation - */ - function cancel() { - if (isTaskDragging || isTaskResizing) { - cleanup(); - } - } - - return { - // Drag state (reactive getters) - get isTaskDragging() { - return isTaskDragging; - }, - get draggedTask() { - return draggedTask; - }, - get taskDragTargetDay() { - return taskDragTargetDay; - }, - get taskDragPreviewTop() { - return taskDragPreviewTop; - }, - get taskDragPreviewHeight() { - return taskDragPreviewHeight; - }, - - // Resize state (reactive getters) - get isTaskResizing() { - return isTaskResizing; - }, - get resizeTask() { - return resizeTask; - }, - get taskResizeEdge() { - return taskResizeEdge; - }, - get taskResizePreviewTop() { - return taskResizePreviewTop; - }, - get taskResizePreviewHeight() { - return taskResizePreviewHeight; - }, - - // Shared state - get hasMoved() { - return hasMoved; - }, - - // Methods - startDrag, - startResize, - cancel, - cleanup, - }; -} diff --git a/apps/calendar/apps/web/src/lib/composables/useTaskDragDrop.test.ts b/apps/calendar/apps/web/src/lib/composables/useTaskDragDrop.test.ts deleted file mode 100644 index 885f63304..000000000 --- a/apps/calendar/apps/web/src/lib/composables/useTaskDragDrop.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; - -// Polyfill PointerEvent for jsdom -if (typeof globalThis.PointerEvent === 'undefined') { - globalThis.PointerEvent = class PointerEvent extends MouseEvent { - constructor(type: string, params: PointerEventInit = {}) { - super(type, params); - } - } as unknown as typeof PointerEvent; -} - -vi.mock('$lib/stores/todos.svelte', () => ({ - todosStore: { - updateTodo: vi.fn().mockResolvedValue(undefined), - }, -})); - -vi.mock('$lib/utils/calendarConstants', () => ({ - SNAP_INTERVAL_MINUTES: 15, -})); - -import { useTaskDragDrop } from './useTaskDragDrop.svelte'; - -function createMockContainer() { - const el = { - getBoundingClientRect: () => ({ - left: 0, - top: 0, - right: 700, - bottom: 960, - width: 700, - height: 960, - }), - querySelectorAll: () => [], - querySelector: () => null, - parentElement: { scrollTop: 0 }, - } as unknown as HTMLElement; - return el; -} - -function makeDays(): Date[] { - return Array.from({ length: 7 }, (_, i) => { - const d = new Date('2026-03-02'); - d.setDate(d.getDate() + i); - return d; - }); -} - -describe('useTaskDragDrop', () => { - function createInstance() { - return useTaskDragDrop(() => ({ - containerEl: createMockContainer(), - days: makeDays(), - firstVisibleHour: 0, - totalVisibleHours: 24, - })); - } - - it('should start in idle state', () => { - const td = createInstance(); - expect(td.isTaskDragging).toBe(false); - expect(td.isTaskResizing).toBe(false); - expect(td.draggedTask).toBeNull(); - expect(td.resizeTask).toBeNull(); - expect(td.hasMoved).toBe(false); - }); - - it('should set isTaskDragging when startDrag is called', () => { - const td = createInstance(); - const task = { - id: 'task-1', - scheduledStartTime: '10:00', - estimatedDuration: 30, - }; - const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 }); - - td.startDrag(task as any, event); - - expect(td.isTaskDragging).toBe(true); - expect(td.draggedTask).toBeTruthy(); - expect(td.draggedTask!.id).toBe('task-1'); - - td.cancel(); - }); - - it('should set isTaskResizing when startResize is called', () => { - const td = createInstance(); - const task = { - id: 'task-1', - scheduledStartTime: '10:00', - estimatedDuration: 30, - }; - const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 }); - - td.startResize(task as any, 'bottom', event); - - expect(td.isTaskResizing).toBe(true); - expect(td.resizeTask).toBeTruthy(); - - td.cancel(); - }); - - it('should reset state on cancel', () => { - const td = createInstance(); - const task = { id: 'task-1', scheduledStartTime: '10:00', estimatedDuration: 30 }; - const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 }); - - td.startDrag(task as any, event); - expect(td.isTaskDragging).toBe(true); - - td.cancel(); - expect(td.isTaskDragging).toBe(false); - expect(td.draggedTask).toBeNull(); - }); - - it('should calculate preview position from task time', () => { - const td = createInstance(); - const task = { - id: 'task-1', - scheduledStartTime: '12:00', // noon - estimatedDuration: 60, - }; - const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 480 }); - - td.startDrag(task as any, event); - - // Preview top should be around 50% (12:00 / 24:00) - expect(td.taskDragPreviewTop).toBeCloseTo(50, 0); - // Height should be ~4.17% (60 min / 1440 min) - expect(td.taskDragPreviewHeight).toBeCloseTo(4.17, 0); - - td.cancel(); - }); -}); diff --git a/apps/calendar/apps/web/src/lib/config/helpConfig.ts b/apps/calendar/apps/web/src/lib/config/helpConfig.ts index d4c6587a7..acffe0f51 100644 --- a/apps/calendar/apps/web/src/lib/config/helpConfig.ts +++ b/apps/calendar/apps/web/src/lib/config/helpConfig.ts @@ -1,4 +1,4 @@ -import { NavigationArrow, CalendarBlank, ListChecks } from '@manacore/shared-icons'; +import { NavigationArrow, CalendarBlank } from '@manacore/shared-icons'; import { COMMON_SHORTCUTS, COMMON_SYNTAX, @@ -26,12 +26,6 @@ const CALENDAR_SHORTCUTS: ShortcutCategory[] = [ { keys: ['Cmd', '2'], altKeys: ['Ctrl', '2'], - description: 'Aufgaben öffnen', - category: 'navigation', - }, - { - keys: ['Cmd', '3'], - altKeys: ['Ctrl', '3'], description: 'Einstellungen öffnen', category: 'navigation', }, @@ -59,23 +53,6 @@ const CALENDAR_SHORTCUTS: ShortcutCategory[] = [ }, ], }, - { - id: 'tasks', - title: 'Aufgaben', - icon: ListChecks, - shortcuts: [ - { - keys: ['Enter'], - description: 'Aufgabe öffnen', - category: 'tasks', - }, - { - keys: ['Space'], - description: 'Aufgabe abhaken', - category: 'tasks', - }, - ], - }, ]; /** diff --git a/apps/calendar/apps/web/src/lib/content/help/index.ts b/apps/calendar/apps/web/src/lib/content/help/index.ts index 4dfe7b2ee..2622f1c69 100644 --- a/apps/calendar/apps/web/src/lib/content/help/index.ts +++ b/apps/calendar/apps/web/src/lib/content/help/index.ts @@ -169,10 +169,6 @@ export function getCalendarHelpContent(locale: string): HelpContent { }, { shortcut: 'Cmd/Ctrl + 2', - action: t('Aufgaben öffnen', 'Open Tasks'), - }, - { - shortcut: 'Cmd/Ctrl + 3', action: t('Einstellungen öffnen', 'Open Settings'), }, ], diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/de.json b/apps/calendar/apps/web/src/lib/i18n/locales/de.json index 80719cfd1..5a19df88a 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/de.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/de.json @@ -202,17 +202,6 @@ "eventCreated": "Termin erstellt", "eventDeleted": "Termin gelöscht" }, - "priority": { - "urgent": "Dringend", - "high": "Wichtig", - "medium": "Normal", - "low": "Später" - }, - "todo": { - "task": "Aufgabe", - "markComplete": "Als erledigt markieren", - "markIncomplete": "Als unerledigt markieren" - }, "a11y": { "createEventOn": "Termin erstellen am {date}", "slotTime": "{day} {time}" diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/en.json b/apps/calendar/apps/web/src/lib/i18n/locales/en.json index ce92606cb..06c63f2a1 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/en.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/en.json @@ -202,17 +202,6 @@ "eventCreated": "Event created", "eventDeleted": "Event deleted" }, - "priority": { - "urgent": "Urgent", - "high": "High", - "medium": "Normal", - "low": "Low" - }, - "todo": { - "task": "Task", - "markComplete": "Mark as complete", - "markIncomplete": "Mark as incomplete" - }, "a11y": { "createEventOn": "Create event on {date}", "slotTime": "{day} {time}" diff --git a/apps/calendar/apps/web/src/lib/stores/birthdays.svelte.ts b/apps/calendar/apps/web/src/lib/stores/birthdays.svelte.ts index c908e89e6..443a74e18 100644 --- a/apps/calendar/apps/web/src/lib/stores/birthdays.svelte.ts +++ b/apps/calendar/apps/web/src/lib/stores/birthdays.svelte.ts @@ -1,6 +1,6 @@ /** * Birthdays Store - Manages contact birthdays for calendar display - * Cross-app integration with Contacts Backend (similar to todosStore) + * Cross-app integration with Contacts Backend */ import { browser } from '$app/environment'; diff --git a/apps/calendar/apps/web/src/lib/stores/settings.svelte.ts b/apps/calendar/apps/web/src/lib/stores/settings.svelte.ts index d59be174a..7ba2d0be7 100644 --- a/apps/calendar/apps/web/src/lib/stores/settings.svelte.ts +++ b/apps/calendar/apps/web/src/lib/stores/settings.svelte.ts @@ -48,7 +48,6 @@ let _dateStripCollapsed = $state(false); let _tagStripCollapsed = $state(true); let _selectedTagIds = $state([]); let _immersiveModeEnabled = $state(false); -let _showTasksInCalendar = $state(false); let _sidebarCollapsed = $state(true); const DEFAULT_SETTINGS: CalendarAppSettings = { @@ -234,9 +233,6 @@ export const settingsStore = { get immersiveModeEnabled() { return _immersiveModeEnabled; }, - get showTasksInCalendar() { - return _showTasksInCalendar; - }, get sidebarCollapsed() { return _sidebarCollapsed; }, @@ -283,10 +279,6 @@ export const settingsStore = { _selectedTagIds = []; }, - toggleTasksInCalendar() { - _showTasksInCalendar = !_showTasksInCalendar; - }, - toggleImmersiveMode() { _immersiveModeEnabled = !_immersiveModeEnabled; }, diff --git a/apps/calendar/apps/web/src/lib/stores/todos.svelte.ts b/apps/calendar/apps/web/src/lib/stores/todos.svelte.ts deleted file mode 100644 index 4ac372ac0..000000000 --- a/apps/calendar/apps/web/src/lib/stores/todos.svelte.ts +++ /dev/null @@ -1,493 +0,0 @@ -/** - * Todos Store - Manages todos from Todo-App using Svelte 5 runes - * Cross-app integration with Todo Backend - */ - -import * as api from '$lib/api/todos'; -import type { - Task, - TaskPriority, - CreateTaskInput, - UpdateTaskInput, - TaskQuery, - Project, - Label, -} from '$lib/api/todos'; -import { PRIORITY_ORDER } from '$lib/api/todos'; -import { - format, - parseISO, - isSameDay, - isToday, - isBefore, - startOfDay, - addDays, - isWithinInterval, -} from 'date-fns'; - -// Re-export types for convenience -export type { Task, TaskPriority, CreateTaskInput, UpdateTaskInput, Project, Label }; - -// State -let todos = $state([]); -let projects = $state([]); -let labels = $state([]); -let loading = $state(false); -let error = $state(null); -let loadedRange = $state<{ start: Date; end: Date } | null>(null); -let serviceAvailable = $state(true); - -export const todosStore = { - // ========== Getters ========== - get todos() { - return todos ?? []; - }, - get projects() { - return projects ?? []; - }, - get labels() { - return labels ?? []; - }, - get loading() { - return loading; - }, - get error() { - return error; - }, - get serviceAvailable() { - return serviceAvailable; - }, - - // ========== Derived Getters ========== - - /** - * Get todos for a specific day (by dueDate) - */ - getTodosForDay(date: Date): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos.filter((task) => { - if (!task.dueDate || task.isCompleted) return false; - const dueDate = typeof task.dueDate === 'string' ? parseISO(task.dueDate) : task.dueDate; - return isSameDay(dueDate, date); - }); - }, - - /** - * Get scheduled tasks for a specific day (by scheduledDate - for time-blocking) - * Note: Includes completed tasks so they remain visible in the calendar - */ - getScheduledTasksForDay(date: Date): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos.filter((task) => { - if (!task.scheduledDate) return false; - const scheduledDate = - typeof task.scheduledDate === 'string' ? parseISO(task.scheduledDate) : task.scheduledDate; - return isSameDay(scheduledDate, date); - }); - }, - - /** - * Get scheduled tasks within a date range (for time-blocking) - * Note: Includes completed tasks so they remain visible in the calendar - */ - getScheduledTasksInRange(start: Date, end: Date): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos.filter((task) => { - if (!task.scheduledDate) return false; - const scheduledDate = - typeof task.scheduledDate === 'string' ? parseISO(task.scheduledDate) : task.scheduledDate; - return isWithinInterval(scheduledDate, { start, end }); - }); - }, - - /** - * Get unscheduled tasks (no scheduledDate - for sidebar drag source) - */ - get unscheduledForTimeBlocking(): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos - .filter((task) => !task.isCompleted && !task.scheduledDate) - .sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]); - }, - - /** - * Get todos within a date range - */ - getTodosInRange(start: Date, end: Date): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos.filter((task) => { - if (!task.dueDate) return false; - const dueDate = typeof task.dueDate === 'string' ? parseISO(task.dueDate) : task.dueDate; - return isWithinInterval(dueDate, { start, end }); - }); - }, - - /** - * Get today's uncompleted todos - */ - get todaysTodos(): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos - .filter((task) => { - if (task.isCompleted) return false; - if (!task.dueDate) return false; - const dueDate = typeof task.dueDate === 'string' ? parseISO(task.dueDate) : task.dueDate; - return isToday(dueDate); - }) - .sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]); - }, - - /** - * Get overdue todos (due before today, not completed) - */ - get overdueTodos(): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - const today = startOfDay(new Date()); - - return currentTodos - .filter((task) => { - if (task.isCompleted) return false; - if (!task.dueDate) return false; - const dueDate = typeof task.dueDate === 'string' ? parseISO(task.dueDate) : task.dueDate; - return isBefore(startOfDay(dueDate), today); - }) - .sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]); - }, - - /** - * Get upcoming todos (next 7 days, not including today) - */ - get upcomingTodos(): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - const tomorrow = startOfDay(addDays(new Date(), 1)); - const weekFromNow = startOfDay(addDays(new Date(), 7)); - - return currentTodos - .filter((task) => { - if (task.isCompleted) return false; - if (!task.dueDate) return false; - const dueDate = typeof task.dueDate === 'string' ? parseISO(task.dueDate) : task.dueDate; - return isWithinInterval(startOfDay(dueDate), { start: tomorrow, end: weekFromNow }); - }) - .sort((a, b) => { - // First sort by date - const dateA = a.dueDate ? parseISO(a.dueDate as string) : new Date(); - const dateB = b.dueDate ? parseISO(b.dueDate as string) : new Date(); - const dateDiff = dateA.getTime() - dateB.getTime(); - if (dateDiff !== 0) return dateDiff; - // Then by priority - return PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]; - }); - }, - - /** - * Get todos without due date - */ - get unscheduledTodos(): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos - .filter((task) => !task.isCompleted && !task.dueDate) - .sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]); - }, - - /** - * Get completed todos - */ - get completedTodos(): Task[] { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return []; - - return currentTodos.filter((task) => task.isCompleted); - }, - - /** - * Get combined sidebar todos (overdue + today, sorted by priority) - * Limited to show in sidebar - */ - getSidebarTodos(limit = 5): Task[] { - const overdue = this.overdueTodos; - const today = this.todaysTodos; - - // Combine and sort: overdue first, then today, both by priority - const combined = [...overdue, ...today]; - - return combined.slice(0, limit); - }, - - /** - * Get total count of active todos (not completed) - */ - get activeTodosCount(): number { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return 0; - - return currentTodos.filter((task) => !task.isCompleted).length; - }, - - // ========== API Methods ========== - - /** - * Fetch todos for a date range - * Note: Fetches both completed and uncompleted tasks so scheduled tasks remain visible - */ - async fetchTodos(startDate?: Date, endDate?: Date) { - loading = true; - error = null; - - const query: TaskQuery = {}; - - if (startDate) { - query.dueDateFrom = format(startDate, 'yyyy-MM-dd'); - } - if (endDate) { - query.dueDateTo = format(endDate, 'yyyy-MM-dd'); - } - - const result = await api.getTasks(query); - - if (result.error) { - error = result.error.message; - serviceAvailable = false; - } else { - todos = result.data || []; - serviceAvailable = true; - if (startDate && endDate) { - loadedRange = { start: startDate, end: endDate }; - } - } - - loading = false; - return result; - }, - - /** - * Fetch today's todos (shortcut) - only uncompleted tasks - */ - async fetchTodayTodos() { - loading = true; - error = null; - - const result = await api.getTodayTasks(); - - if (result.error) { - error = result.error.message; - serviceAvailable = false; - } else { - // Merge with existing todos (avoid duplicates) - const newTodos = result.data || []; - const existingIds = new Set(todos.map((t) => t.id)); - const uniqueNew = newTodos.filter((t) => !existingIds.has(t.id)); - todos = [...todos, ...uniqueNew]; - serviceAvailable = true; - } - - loading = false; - return result; - }, - - /** - * Fetch all scheduled todos (including completed ones) - * Used for calendar time-blocking to keep completed tasks visible - */ - async fetchScheduledTodos() { - loading = true; - error = null; - - // Fetch all tasks without isCompleted filter - API will return all - const result = await api.getTasks({}); - - if (result.error) { - error = result.error.message; - serviceAvailable = false; - } else { - // Only keep tasks that have a scheduledDate (for time-blocking) - // Merge with existing todos (avoid duplicates) - const allTasks = result.data || []; - const scheduledTasks = allTasks.filter((t) => t.scheduledDate); - const existingIds = new Set(todos.map((t) => t.id)); - const uniqueNew = scheduledTasks.filter((t) => !existingIds.has(t.id)); - // Also update existing scheduled tasks (in case isCompleted changed) - todos = todos.map((existing) => { - const updated = scheduledTasks.find((t) => t.id === existing.id); - return updated || existing; - }); - todos = [...todos, ...uniqueNew]; - serviceAvailable = true; - } - - loading = false; - return result; - }, - - /** - * Fetch upcoming todos (shortcut) - */ - async fetchUpcomingTodos() { - loading = true; - error = null; - - const result = await api.getUpcomingTasks(); - - if (result.error) { - error = result.error.message; - // Only set serviceAvailable to false if we have no todos yet - // (if fetchTodayTodos succeeded, we should still show the service as available) - if (todos.length === 0) { - serviceAvailable = false; - } - } else { - // Merge with existing todos (avoid duplicates) - const newTodos = result.data || []; - const existingIds = new Set(todos.map((t) => t.id)); - const uniqueNew = newTodos.filter((t) => !existingIds.has(t.id)); - todos = [...todos, ...uniqueNew]; - serviceAvailable = true; - } - - loading = false; - return result; - }, - - /** - * Fetch projects - */ - async fetchProjects() { - const result = await api.getProjects(); - - if (!result.error && result.data) { - projects = result.data; - } - - return result; - }, - - /** - * Fetch labels - */ - async fetchLabels() { - const result = await api.getLabels(); - - if (!result.error && result.data) { - labels = result.data; - } - - return result; - }, - - /** - * Create a new todo - */ - async createTodo(data: CreateTaskInput) { - const result = await api.createTask(data); - - if (result.data) { - todos = [...todos, result.data]; - } - - return result; - }, - - /** - * Update a todo - */ - async updateTodo(id: string, data: UpdateTaskInput) { - const result = await api.updateTask(id, data); - - if (result.data) { - todos = todos.map((t) => (t.id === id ? result.data! : t)); - } - - return result; - }, - - /** - * Delete a todo (optimistic update) - */ - async deleteTodo(id: string) { - // Optimistic: remove todo immediately - const todoToDelete = todos.find((t) => t.id === id); - todos = todos.filter((t) => t.id !== id); - - const result = await api.deleteTask(id); - - if (result.error) { - // Rollback: restore the todo on error - if (todoToDelete) { - todos = [...todos, todoToDelete]; - } - } - - return result; - }, - - /** - * Toggle todo completion - */ - async toggleComplete(id: string) { - const todo = todos.find((t) => t.id === id); - if (!todo) return { data: null, error: new Error('Todo not found') }; - - const result = todo.isCompleted ? await api.uncompleteTask(id) : await api.completeTask(id); - - if (result.data) { - todos = todos.map((t) => (t.id === id ? result.data! : t)); - } - - return result; - }, - - /** - * Get todo by ID - */ - getById(id: string): Task | undefined { - const currentTodos = todos ?? []; - if (!Array.isArray(currentTodos)) return undefined; - - return currentTodos.find((t) => t.id === id); - }, - - /** - * Get project by ID - */ - getProjectById(id: string): Project | undefined { - const currentProjects = projects ?? []; - if (!Array.isArray(currentProjects)) return undefined; - - return currentProjects.find((p) => p.id === id); - }, - - /** - * Clear todos cache - */ - clear() { - todos = []; - loadedRange = null; - }, - - /** - * Check if Todo service is available - */ - async checkServiceHealth(): Promise { - const result = await api.getTasks({ limit: 1 }); - serviceAvailable = !result.error; - return serviceAvailable; - }, -}; diff --git a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte index d66722551..f663af2da 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte @@ -14,7 +14,6 @@ PillNavItem, PillDropdownItem, QuickInputItem, - PillTabGroupConfig, PillTagSelectorConfig, PillNavElement, } from '@manacore/shared-ui'; @@ -268,8 +267,7 @@ // User email for user dropdown — empty string for guests so PillNav shows login button let userEmail = $derived(authStore.isAuthenticated ? authStore.user?.email || 'Menü' : ''); - // Base navigation items for Calendar (without Kalender/Aufgaben - handled by tab group) - // Tags are now in the tag-selector dropdown in prependElements + // Base navigation items for Calendar let baseNavItems = $derived([ { href: '/', @@ -284,20 +282,6 @@ filterHiddenNavItems('calendar', baseNavItems, userSettings.nav?.hiddenNavItems || {}) ); - // Active tab based on sidebar state: 'tasks' when sidebar is open, 'calendar' when closed - let activeTab = $derived(settingsStore.sidebarCollapsed ? 'calendar' : 'tasks'); - - // Tab group for Kalender/Aufgaben - let calendarTasksTabGroup = $derived({ - type: 'tabs', - options: [ - { id: 'calendar', icon: 'calendar', label: 'Kalender', title: 'Kalender anzeigen' }, - { id: 'tasks', icon: 'check-square', label: 'Aufgaben', title: 'Aufgaben-Sidebar öffnen' }, - ], - value: activeTab, - onChange: handleTabChange, - }); - // Tag selector config for PillNavigation let tagSelectorConfig = $derived({ type: 'tag-selector', @@ -309,33 +293,10 @@ label: 'Tags', }); - // Prepended elements (tab groups at the start of navigation) - // Note: View switcher moved to ViewsBar component - let prependElements = $derived( - showCalendarToolbar - ? [calendarTasksTabGroup, { type: 'divider' }, tagSelectorConfig] - : [calendarTasksTabGroup] - ); + // Prepended elements + let prependElements = $derived(showCalendarToolbar ? [tagSelectorConfig] : []); - // Handle tab change: toggle sidebar for tasks, close for calendar - function handleTabChange(tabId: string) { - // Always navigate to main calendar page if not there - if ($page.url.pathname !== '/') { - goto('/'); - } - - if (tabId === 'tasks') { - // Toggle behavior: if sidebar is already open, close it - settingsStore.toggleSidebar(); - } else if (tabId === 'calendar') { - // Kalender-Tab: close sidebar if open - if (!settingsStore.sidebarCollapsed) { - settingsStore.toggleSidebar(); - } - } - } - - // Navigation shortcuts (Ctrl+1 = Kalender, Ctrl+2 = Aufgaben toggle, Ctrl+3+ = other nav items) + // Navigation shortcuts function handleKeydown(event: KeyboardEvent) { const target = event.target as HTMLElement; @@ -346,17 +307,11 @@ if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { const num = parseInt(event.key); if (num === 1) { - // Ctrl+1: Kalender (close sidebar) event.preventDefault(); - handleTabChange('calendar'); - } else if (num === 2) { - // Ctrl+2: Aufgaben (toggle sidebar) + goto('/'); + } else if (num >= 2 && num <= baseNavItems.length + 1) { event.preventDefault(); - handleTabChange('tasks'); - } else if (num >= 3 && num <= baseNavItems.length + 2) { - // Ctrl+3+: other nav items (offset by 2 for the tab group) - event.preventDefault(); - const route = baseNavItems[num - 3]?.href; + const route = baseNavItems[num - 2]?.href; if (route) { goto(route); } @@ -595,7 +550,7 @@ >
{@render children()} diff --git a/apps/calendar/apps/web/src/routes/(app)/+page.svelte b/apps/calendar/apps/web/src/routes/(app)/+page.svelte index 6875190ec..e23ca53ba 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+page.svelte @@ -4,40 +4,28 @@ import { viewStore } from '$lib/stores/view.svelte'; import { eventsStore } from '$lib/stores/events.svelte'; import { settingsStore } from '$lib/stores/settings.svelte'; - import { todosStore } from '$lib/stores/todos.svelte'; import { birthdaysStore } from '$lib/stores/birthdays.svelte'; import { getDefaultCalendar } from '$lib/data/queries'; import ViewCarousel from '$lib/components/calendar/ViewCarousel.svelte'; - import TodoSidebarSection from '$lib/components/calendar/TodoSidebarSection.svelte'; import QuickEventOverlay from '$lib/components/event/QuickEventOverlay.svelte'; import ServiceStatusBanner from '$lib/components/ServiceStatusBanner.svelte'; import type { CalendarEvent, Calendar } from '@calendar/shared'; import { addMinutes } from 'date-fns'; import { browser } from '$app/environment'; - import { CaretDoubleLeft } from '@manacore/shared-icons'; // Get calendars from layout context (live query) const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars'); - // Quick event overlay state - for both create and edit let showQuickOverlay = $state(false); let quickCreateDate = $state(new Date()); let editingEvent = $state(null); - - // Generate a unique key for the overlay to force remount let overlayKey = $state(0); function handleQuickCreate(date: Date, position: { x: number; y: number }, endDate?: Date) { - // Close any existing overlay first editingEvent = null; - quickCreateDate = date; - - // Create draft event immediately so it appears in the grid const defaultCalendar = getDefaultCalendar(calendarsCtx.value); - // Use provided endDate or calculate from default duration const endTime = endDate ?? addMinutes(date, settingsStore.defaultEventDuration); - eventsStore.createDraftEvent({ calendarId: defaultCalendar?.id || '', title: '', @@ -45,15 +33,12 @@ endTime: endTime.toISOString(), isAllDay: false, }); - overlayKey++; showQuickOverlay = true; } function handleEventClick(event: CalendarEvent) { - // Close any existing overlay/draft first eventsStore.clearDraftEvent(); - editingEvent = event; overlayKey++; showQuickOverlay = true; @@ -66,19 +51,12 @@ } function handleEventCreated() { - // Event is automatically added to store, draft is cleared eventsStore.clearDraftEvent(); } - function handleEventUpdated() { - // Event is automatically updated in store - } + function handleEventUpdated() {} + function handleEventDeleted() {} - function handleEventDeleted() { - // Event is automatically removed from store - } - - // Voice event creation handler interface VoiceEventData { title: string; startTime?: Date; @@ -92,16 +70,10 @@ function handleVoiceEventCreate(event: CustomEvent) { const data = event.detail; - - // Close any existing overlay first editingEvent = null; eventsStore.clearDraftEvent(); - - // Determine start time - use parsed time or default to now const startTime = data.startTime || new Date(); quickCreateDate = startTime; - - // Calculate end time let endTime: Date; if (data.endTime) { endTime = data.endTime; @@ -111,11 +83,7 @@ } else { endTime = addMinutes(startTime, settingsStore.defaultEventDuration); } - - // Get default calendar const defaultCalendar = getDefaultCalendar(calendarsCtx.value); - - // Create draft event with voice transcription data eventsStore.createDraftEvent({ calendarId: defaultCalendar?.id || '', title: data.title, @@ -125,12 +93,10 @@ location: data.location, description: data.description ? `Sprachnotiz: ${data.description}` : undefined, }); - overlayKey++; showQuickOverlay = true; } - // Listen for voice event creation from layout $effect(() => { if (browser) { const handler = (e: Event) => handleVoiceEventCreate(e as CustomEvent); @@ -162,12 +128,6 @@ />
- todosStore.fetchTodos()} - /> {#if settingsStore.showBirthdays}
- - - - -
+
- - - - {#if showQuickOverlay} {#key overlayKey} .calendar-layout { display: flex; - gap: 1.5rem; width: 100%; flex: 1; min-height: 0; position: relative; } - /* Desktop only elements */ - .desktop-only { - display: flex; - } - - /* Mobile only elements - hidden by default */ - .mobile-only { - display: none; - } - - .calendar-sidebar { - width: 260px; - flex-shrink: 0; - flex-direction: column; - gap: 1rem; - position: relative; - transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); - transform-origin: left top; - } - - .calendar-sidebar.collapsed { - width: 0; - opacity: 0; - overflow: hidden; - pointer-events: none; - padding: 0; - margin: 0; - } - - .calendar-layout:has(.calendar-sidebar.collapsed) { - gap: 0; - } - - .sidebar-collapse-btn { - position: absolute; - top: 0; - right: -12px; - width: 24px; - height: 24px; - border-radius: var(--radius-full); - background: var(--color-surface); - border: 1px solid var(--color-border); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - z-index: 10; - transition: all 150ms ease; - color: var(--color-muted-foreground); - } - - .sidebar-collapse-btn:hover { - background: var(--color-muted); - color: var(--color-foreground); - } - .calendar-main { flex: 1; display: flex; @@ -296,15 +175,6 @@ min-width: 0; min-height: 0; overflow: hidden; - background: var(--color-surface); - border-radius: var(--radius-lg); - border: 1px solid var(--color-border); - transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); - } - - .calendar-main.expanded { - border-radius: 0; - border: none; } .calendar-content { @@ -313,27 +183,6 @@ overflow: hidden; } - /* Mobile: Bottom Todo Section */ - .calendar-sidebar-mobile { - width: 100%; - flex-direction: column; - background: var(--color-surface); - border-top: 1px solid var(--color-border); - padding: 0.75rem; - overflow-y: auto; - transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); - } - - .calendar-sidebar-mobile.collapsed { - height: 0; - flex: 0; - padding: 0; - opacity: 0; - overflow: hidden; - border: none; - } - - /* Mobile Layout - 50/50 Splitscreen */ @media (max-width: 768px) { .calendar-layout { flex-direction: column; @@ -344,27 +193,7 @@ overflow: hidden; } - .desktop-only { - display: none !important; - } - - .mobile-only { - display: flex; - } - .calendar-main { - border-radius: 0; - border: none; - min-height: 0; - overflow: hidden; - } - - .calendar-layout:has(.calendar-sidebar-mobile:not(.collapsed)) .calendar-main { - flex: 0 0 50%; - height: 50%; - } - - .calendar-layout:has(.calendar-sidebar-mobile.collapsed) .calendar-main { flex: 1; height: 100%; } @@ -373,37 +202,6 @@ height: 100%; overflow-y: auto; } - - .calendar-sidebar-mobile { - display: flex; - flex-direction: column; - flex: 0 0 50%; - height: 50%; - max-height: none; - border-radius: 0; - margin-bottom: 0; - padding: 0; - border-top: none; - overflow: hidden; - } - - .calendar-sidebar-mobile > :global(*) { - flex: 1; - min-height: 0; - } - - .calendar-sidebar-mobile.collapsed { - flex: 0; - height: 0; - padding: 0; - border: none; - } - } - - @media (min-width: 769px) and (max-width: 1024px) { - .calendar-sidebar { - width: 220px; - } } .service-banners {