From e4d4e78d747e2342d60795012001d1e7e6b66942 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Mon, 29 Jun 2026 16:26:29 +0200 Subject: [PATCH] icon --- .../app/dashboard/dashboard.component.html | 61 ++++++++++++ .../app/dashboard/dashboard.component.scss | 82 +++++++++++++++- .../src/app/dashboard/dashboard.component.ts | 93 +++++++++++++++++++ listify-client/src/index.html | 3 +- 4 files changed, 237 insertions(+), 2 deletions(-) diff --git a/listify-client/src/app/dashboard/dashboard.component.html b/listify-client/src/app/dashboard/dashboard.component.html index 4f1959e..fd6bf9c 100644 --- a/listify-client/src/app/dashboard/dashboard.component.html +++ b/listify-client/src/app/dashboard/dashboard.component.html @@ -31,6 +31,67 @@ } @else if (dashboard()) { +
+
+
+

Aktuelle Tasks

+

Heute faellige und ueberfaellige Aufgaben.

+
+ + + Alle Tasks + +
+ + @if (tasksLoading()) { + + + +

Tasks werden geladen

+
+
+ } @else if (hasCurrentTasks()) { + + +
+ @for (task of currentTasks(); track task.id) { +
+ + +
+ {{ task.title }} + @if (task.notes) { + {{ task.notes }} + } +
+ + + + {{ taskDateLabel(task) }} + +
+ } +
+
+
+ } @else { + + + +

Keine aktuellen Tasks

+

Heute ist nichts offen oder ueberfaellig.

+
+
+ } +
+
diff --git a/listify-client/src/app/dashboard/dashboard.component.scss b/listify-client/src/app/dashboard/dashboard.component.scss index 9de569e..b5f5dc6 100644 --- a/listify-client/src/app/dashboard/dashboard.component.scss +++ b/listify-client/src/app/dashboard/dashboard.component.scss @@ -37,7 +37,8 @@ } .important-list-card, -.suggestion-card { +.suggestion-card, +.current-tasks-card { min-width: 0; overflow: hidden; border: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent); @@ -46,6 +47,10 @@ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); } +.compact-state mat-card-content { + padding: 1rem; +} + .important-list-card mat-card-title, .important-list-card mat-card-subtitle, .suggestion-card mat-card-title, @@ -53,6 +58,73 @@ overflow-wrap: anywhere; } +.current-task-list { + display: grid; + gap: 0.45rem; +} + +.current-task-row { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: center; + gap: 0.35rem 0.65rem; + min-width: 0; + padding: 0.55rem 0; + border-bottom: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 64%, transparent); +} + +.current-task-row:last-child { + border-bottom: 0; +} + +.current-task-main { + display: grid; + gap: 0.15rem; + min-width: 0; +} + +.current-task-main strong, +.current-task-main span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.current-task-main strong { + font-weight: 600; +} + +.current-task-main span { + color: var(--mat-sys-on-surface-variant); + font-size: 0.85rem; +} + +.task-date-chip { + display: inline-flex; + grid-column: 2; + align-items: center; + width: fit-content; + max-width: 100%; + gap: 0.25rem; + padding: 0.2rem 0.5rem; + border-radius: 999px; + background: color-mix(in srgb, var(--mat-sys-primary-container) 65%, transparent); + color: var(--mat-sys-on-primary-container); + font-size: 0.8rem; + line-height: 1.2; +} + +.current-task-row.overdue .task-date-chip { + background: color-mix(in srgb, var(--mat-sys-error-container) 78%, transparent); + color: var(--mat-sys-on-error-container); +} + +.task-date-chip mat-icon { + width: 16px; + height: 16px; + font-size: 16px; +} + .dashboard-meta { display: flex; flex-wrap: wrap; @@ -129,6 +201,14 @@ a mat-progress-spinner { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } + + .current-task-row { + grid-template-columns: auto minmax(0, 1fr) auto; + } + + .task-date-chip { + grid-column: auto; + } } @media (min-width: 1040px) { diff --git a/listify-client/src/app/dashboard/dashboard.component.ts b/listify-client/src/app/dashboard/dashboard.component.ts index 7f1912c..243ef86 100644 --- a/listify-client/src/app/dashboard/dashboard.component.ts +++ b/listify-client/src/app/dashboard/dashboard.component.ts @@ -4,11 +4,14 @@ import { RouterLink } from '@angular/router'; import { finalize } from 'rxjs'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { getAuthErrorMessage } from '../auth/error-message'; +import { TasksService } from '../tasks/tasks.service'; +import { UserTask } from '../tasks/tasks.models'; import { DashboardResponse, DashboardWeeklySuggestion } from './dashboard.models'; import { DashboardService } from './dashboard.service'; @@ -19,6 +22,7 @@ import { DashboardService } from './dashboard.service'; RouterLink, MatButtonModule, MatCardModule, + MatCheckboxModule, MatIconModule, MatProgressBarModule, MatProgressSpinnerModule, @@ -29,21 +33,43 @@ import { DashboardService } from './dashboard.service'; }) export class DashboardComponent implements OnInit { private readonly dashboardService = inject(DashboardService); + private readonly tasksService = inject(TasksService); private readonly snackBar = inject(MatSnackBar); protected readonly dashboard = signal(null); + protected readonly tasks = signal([]); protected readonly loading = signal(true); + protected readonly tasksLoading = signal(true); protected readonly errorMessage = signal(null); protected readonly creatingSuggestionId = signal(null); + protected readonly updatingTaskId = signal(null); protected readonly hasImportantLists = computed( () => (this.dashboard()?.importantLists.length ?? 0) > 0, ); + protected readonly currentTasks = computed(() => { + const today = this.dashboard()?.dateKey ?? this.todayKey(); + + return this.tasks() + .filter((task) => !task.completed && task.dueDate <= today) + .sort((left, right) => { + const dueDateComparison = left.dueDate.localeCompare(right.dueDate); + + if (dueDateComparison !== 0) { + return dueDateComparison; + } + + return new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime(); + }) + .slice(0, 6); + }); + protected readonly hasCurrentTasks = computed(() => this.currentTasks().length > 0); protected readonly suggestions = computed( () => this.dashboard()?.weeklySuggestions.suggestions ?? [], ); ngOnInit(): void { this.loadDashboard(); + this.loadTasks(); } protected loadDashboard(): void { @@ -62,6 +88,48 @@ export class DashboardComponent implements OnInit { }); } + protected loadTasks(): void { + this.tasksLoading.set(true); + + this.tasksService.listTasks().subscribe({ + next: (tasks) => { + this.tasks.set(tasks); + this.tasksLoading.set(false); + }, + error: (error: unknown) => { + this.tasksLoading.set(false); + this.snackBar.open(getAuthErrorMessage(error), 'OK', { + duration: 5000, + }); + }, + }); + } + + protected completeTask(task: UserTask): void { + if (this.updatingTaskId()) { + return; + } + + this.updatingTaskId.set(task.id); + this.tasksService + .updateTask(task.id, { completed: true }) + .pipe(finalize(() => this.updatingTaskId.set(null))) + .subscribe({ + next: (updatedTask) => { + this.tasks.update((tasks) => + tasks.map((existingTask) => + existingTask.id === updatedTask.id ? updatedTask : existingTask, + ), + ); + }, + error: (error: unknown) => { + this.snackBar.open(getAuthErrorMessage(error), 'OK', { + duration: 5000, + }); + }, + }); + } + protected createSuggestion(suggestion: DashboardWeeklySuggestion): void { if (suggestion.createdListId || this.creatingSuggestionId()) { return; @@ -102,4 +170,29 @@ export class DashboardComponent implements OnInit { protected progressPercent(value: number): number { return Math.round(value * 100); } + + protected taskDateLabel(task: UserTask): string { + const today = this.dashboard()?.dateKey ?? this.todayKey(); + + if (task.dueDate === today) { + return 'Heute'; + } + + return `Ueberfaellig seit ${this.formatDay(task.dueDate)}`; + } + + private formatDay(value: string): string { + const [year, month, day] = value.split('-'); + + return year && month && day ? `${day}.${month}.${year}` : value; + } + + private todayKey(): string { + const date = new Date(); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; + } } diff --git a/listify-client/src/index.html b/listify-client/src/index.html index 1f3493b..ac070e3 100644 --- a/listify-client/src/index.html +++ b/listify-client/src/index.html @@ -6,7 +6,8 @@ - + +