diff --git a/apps/manacore/apps/web/src/lib/api/services/index.ts b/apps/manacore/apps/web/src/lib/api/services/index.ts index b55b8f1d8..735e56626 100644 --- a/apps/manacore/apps/web/src/lib/api/services/index.ts +++ b/apps/manacore/apps/web/src/lib/api/services/index.ts @@ -4,7 +4,7 @@ * Re-exports all app-specific services for the dashboard. */ -export { todoService, type Task, type Project } from './todo'; +export { todoService, type Task, type Project, type Label, type Subtask } from './todo'; export { calendarService, type Calendar, type CalendarEvent } from './calendar'; export { chatService, type Conversation, type Message, type AiModel } from './chat'; export { contactsService, type Contact, type ContactActivity } from './contacts'; diff --git a/apps/manacore/apps/web/src/lib/api/services/todo.ts b/apps/manacore/apps/web/src/lib/api/services/todo.ts index 16389b036..6e5a88d44 100644 --- a/apps/manacore/apps/web/src/lib/api/services/todo.ts +++ b/apps/manacore/apps/web/src/lib/api/services/todo.ts @@ -31,6 +31,24 @@ function getClient() { return _client; } +/** + * Label entity from Todo backend + */ +export interface Label { + id: string; + name: string; + color: string; +} + +/** + * Subtask entity from Todo backend + */ +export interface Subtask { + id: string; + title: string; + isCompleted: boolean; +} + /** * Task entity from Todo backend */ @@ -44,7 +62,8 @@ export interface Task { dueTime?: string; isCompleted: boolean; status: 'pending' | 'in_progress' | 'completed' | 'cancelled'; - labelIds: string[]; + labels?: Label[]; + subtasks?: Subtask[] | null; createdAt: string; updatedAt: string; } diff --git a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte index c056d0d65..8d7fd7757 100644 --- a/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte +++ b/apps/manacore/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte @@ -6,6 +6,7 @@ import { onMount } from 'svelte'; import { _ } from 'svelte-i18n'; import { todoService, type Task } from '$lib/api/services'; + import { APP_URLS } from '@manacore/shared-branding'; import WidgetSkeleton from '../WidgetSkeleton.svelte'; import WidgetError from '../WidgetError.svelte'; @@ -17,6 +18,16 @@ const MAX_DISPLAY = 5; + const isDev = typeof window !== 'undefined' && window.location.hostname === 'localhost'; + const todoUrl = isDev ? APP_URLS.todo.dev : APP_URLS.todo.prod; + + const priorityColors: Record = { + urgent: '#ef4444', + high: '#f97316', + medium: '#eab308', + low: '#22c55e', + }; + async function load() { state = 'loading'; retrying = true; @@ -31,7 +42,6 @@ error = result.error; state = 'error'; - // Don't retry if service is unavailable (network error) const isServiceUnavailable = error?.includes('nicht erreichbar'); if (!isServiceUnavailable && retryCount < 3) { retryCount++; @@ -44,21 +54,16 @@ onMount(load); - function getPriorityColor(priority: string): string { - switch (priority) { - case 'urgent': - return 'text-red-500'; - case 'high': - return 'text-orange-500'; - case 'medium': - return 'text-yellow-500'; - default: - return 'text-muted-foreground'; - } - } - const displayedTasks = $derived((data || []).slice(0, MAX_DISPLAY)); const remainingCount = $derived(Math.max(0, (data || []).length - MAX_DISPLAY)); + const completedCount = $derived((data || []).filter((t) => t.isCompleted).length); + const totalCount = $derived((data || []).length); + + function getSubtaskProgress(task: Task): string | null { + if (!task.subtasks || task.subtasks.length === 0) return null; + const done = task.subtasks.filter((s) => s.isCompleted).length; + return `${done}/${task.subtasks.length}`; + }
@@ -67,9 +72,9 @@ {$_('dashboard.widgets.tasks_today.title')} - {#if (data || []).length > 0} - - {(data || []).length} + {#if totalCount > 0} + + {completedCount}/{totalCount} {/if}
@@ -78,7 +83,7 @@ {:else if state === 'error'} - {:else if (data || []).length === 0} + {:else if totalCount === 0}
🎉

@@ -86,15 +91,23 @@

{:else} -
+
{#each displayedTasks as task} -
+
+ + +
{#if task.isCompleted} {/if}
+ +

{task.dueTime}

+ + {#if task.dueTime || getSubtaskProgress(task) || (task.labels && task.labels.length > 0)} +
+ {#if task.dueTime} + {task.dueTime} + {/if} + {#if getSubtaskProgress(task)} + + + + + {getSubtaskProgress(task)} + + {/if} + {#if task.labels && task.labels.length > 0} + {#each task.labels.slice(0, 2) as label} + + + {label.name} + + {/each} + {#if task.labels.length > 2} + +{task.labels.length - 2} + {/if} + {/if} +
{/if}
- {#if !task.isCompleted && task.priority !== 'low'} - - {/if} -
+ {/each} {#if remainingCount > 0} = { + urgent: '#ef4444', + high: '#f97316', + medium: '#eab308', + low: '#22c55e', + }; + async function load() { state = 'loading'; retrying = true; @@ -31,7 +42,6 @@ error = result.error; state = 'error'; - // Don't retry if service is unavailable (network error) const isServiceUnavailable = error?.includes('nicht erreichbar'); if (!isServiceUnavailable && retryCount < 3) { retryCount++; @@ -50,16 +60,22 @@ const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); - if (date.toDateString() === today.toDateString()) { - return 'Heute'; - } - if (date.toDateString() === tomorrow.toDateString()) { - return 'Morgen'; - } - + if (date.toDateString() === today.toDateString()) return 'Heute'; + if (date.toDateString() === tomorrow.toDateString()) return 'Morgen'; return date.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric', month: 'short' }); } + function isOverdue(dateStr: string): boolean { + const date = new Date(dateStr); + const today = new Date(); + today.setHours(0, 0, 0, 0); + return date < today; + } + + function isToday(dateStr: string): boolean { + return new Date(dateStr).toDateString() === new Date().toDateString(); + } + const displayedTasks = $derived(data.slice(0, MAX_DISPLAY)); const remainingCount = $derived(Math.max(0, data.length - MAX_DISPLAY)); @@ -71,7 +87,7 @@ {$_('dashboard.widgets.tasks_upcoming.title')} {#if data.length > 0} - + {data.length} {/if} @@ -89,23 +105,57 @@

{:else} -