From 50d8836abe858728c69e5dffd21c68b651505b4c Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 21 May 2026 22:00:57 +1000 Subject: [PATCH] feat(dashboard): implement sorting and filtering --- .../dashboard-list-item.component.ts | 4 + .../f-cross-dashboard.component.html | 46 ++++++++- .../dashboard/f-cross-dashboard.component.ts | 96 +++++++++++++++++-- 3 files changed, 131 insertions(+), 15 deletions(-) diff --git a/src/app/dashboard/dashboard-list-item.component.ts b/src/app/dashboard/dashboard-list-item.component.ts index b201cfcad..6d321294b 100644 --- a/src/app/dashboard/dashboard-list-item.component.ts +++ b/src/app/dashboard/dashboard-list-item.component.ts @@ -1,4 +1,5 @@ import {Component, Input} from '@angular/core'; +import {TaskStatusEnum} from '../api/models/task-status'; export type DashboardTask = { title: string; @@ -6,6 +7,9 @@ export type DashboardTask = { abbreviation: string; color: string; comments: number; + status: TaskStatusEnum; + date: Date; + weight: number; }; @Component({ diff --git a/src/app/dashboard/f-cross-dashboard.component.html b/src/app/dashboard/f-cross-dashboard.component.html index 978148aa1..6787111c4 100644 --- a/src/app/dashboard/f-cross-dashboard.component.html +++ b/src/app/dashboard/f-cross-dashboard.component.html @@ -1,15 +1,51 @@
-
+
-
-

{{ unit.code }}

+
+

+ {{ unit.code }} +

+ - sort - filter_list_alt + +
+ + +
+ +
+
+
+ +
+ + +
+ + {{ mode }} +
+
+
diff --git a/src/app/dashboard/f-cross-dashboard.component.ts b/src/app/dashboard/f-cross-dashboard.component.ts index bd4bbc072..ff2d9314f 100644 --- a/src/app/dashboard/f-cross-dashboard.component.ts +++ b/src/app/dashboard/f-cross-dashboard.component.ts @@ -1,10 +1,21 @@ import {Component, OnInit} from '@angular/core'; import {GlobalStateService} from 'src/app/projects/states/index/global-state.service'; import {Project} from '../api/models/project'; -import {TaskStatus} from '../api/models/task-status'; +import {TaskStatus, TaskStatusEnum} from '../api/models/task-status'; import {DashboardTask} from './dashboard-list-item.component'; import {Task} from '../api/models/task'; -import {TaskDefinition} from '../api/models/task-definition'; + +enum Filter { + HideCompleted = 'Hide Completed', +} + +enum SortMode { + Recommended = 'Recommended', + SubmissionDate = 'Due Date', + Default = 'Default', +} + +const completedTypes: readonly TaskStatusEnum[] = ['complete']; type DashboardUnit = { projectId: number; @@ -20,37 +31,102 @@ type DashboardUnit = { export class CrossDashboardComponent implements OnInit { constructor(private globalStateService: GlobalStateService) {} - units: DashboardUnit[] = []; + filterOptions = Object.values(Filter); + sortOptions = Object.values(SortMode); + + private units: DashboardUnit[] = []; + unitsProcessed: DashboardUnit[] = []; + + private filters = new Map(); + private sorting = new Map(); ngOnInit(): void { this.globalStateService.onLoad(() => { this.globalStateService.currentUserProjects.values.subscribe((projects) => { this.units = this.mapProjects(projects); + this.processTasks(); }); }); } - mapProjects(projects: readonly Project[]): DashboardUnit[] { + unitIdentify(_index: number, item: DashboardUnit) { + return item.projectId; + } + + setSort(project: number, mode: SortMode) { + this.sorting.set(project, mode); + this.processTasks(); + } + + toggleFilter(project: number, filter: Filter) { + let filters = this.filters.get(project) ?? []; + if (filters.includes(filter)) { + filters = filters.filter((f) => f != filter); + } else { + filters = [...filters, filter]; + } + this.filters.set(project, filters); + this.processTasks(); + } + + private processTasks() { + this.unitsProcessed = this.units.map((unit) => ({ + ...unit, + tasks: unit.tasks + .filter((task) => { + const filters = this.filters.get(unit.projectId) ?? []; + if (filters.includes(Filter.HideCompleted) && completedTypes.includes(task.status)) { + return false; + } else { + return true; + } + }) + .sort((a, b) => { + const sort = this.sorting.get(unit.projectId) ?? 'recommended'; + if (completedTypes.includes(a.status) && !completedTypes.includes(b.status)) { + return -1; + } else if (!completedTypes.includes(a.status) && completedTypes.includes(b.status)) { + return 1; + } + switch (sort) { + case SortMode.Recommended: { + // TODO: Connect to recommender's points + return 0; + } + case SortMode.SubmissionDate: + return a.date.getTime() - b.date.getTime(); + case SortMode.Default: + return a.weight - b.weight; + } + }), + })); + } + + private mapProjects(projects: readonly Project[]): DashboardUnit[] { return projects.map((project) => { + project.calcTopTasks(); const unit = project.unit; return { projectId: project.id, code: unit.code, name: unit.name, - tasks: this.mapTasks(project.tasks, unit.taskDefinitions), + tasks: this.mapTasks(project.activeTasks()), }; }); } - mapTasks(tasks: readonly Task[], taskDefs: readonly TaskDefinition[]): DashboardTask[] { - return taskDefs.map((def) => { - const task = tasks.find((t) => t.taskDefId == def.id); + private mapTasks(tasks: readonly Task[]): DashboardTask[] { + return tasks.map((task) => { + const def = task.definition; return { title: def.name, subtitle: `${def.abbreviation} - ${def.targetGradeText} Task`, abbreviation: def.abbreviation, - color: TaskStatus.STATUS_COLORS.get(task?.status ?? 'not_started'), - comments: task?.numNewComments ?? 0, + color: TaskStatus.STATUS_COLORS.get(task.status), + comments: task.numNewComments ?? 0, + status: task.status, + date: def.targetDate, + weight: task.topWeight, }; }); }