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 @@
-
+
+