From 3c165a7fecce45282763e9bf1e701c3252467cd3 Mon Sep 17 00:00:00 2001 From: Millicent Amolo Date: Sun, 26 Apr 2026 19:37:07 +1000 Subject: [PATCH 1/3] feat: migrate student-task-list from CoffeeScript to Angular/TypeScript Convert student-task-list.coffee to student-task-list.component.ts Convert student-task-list.tpl.html to student-task-list.component.html Convert student-task-list.scss to student-task-list.component.scss Add student-task-list.component.css for proper styling Update Angular module imports and declarations Update AngularJS module with downgrade directive Update dashboard template to use Angular property bindings Remove old CoffeeScript files Fix filtering and sorting logic Handle taskData gracefully when not provided All original functionality preserved Fixes #472 --- package-lock.json | 1 + src/app/doubtfire-angular.module.ts | 2 + src/app/doubtfire-angularjs.module.ts | 6 +- .../states/dashboard/dashboard.tpl.html | 6 +- .../dashboard/directives/directives.coffee | 1 - .../student-task-list.coffee | 62 -------- .../student-task-list.component.css | 1 + ....html => student-task-list.component.html} | 51 +++---- .../student-task-list.component.scss | 1 + .../student-task-list.component.ts | 134 ++++++++++++++++++ .../student-task-list/student-task-list.scss | 3 - 11 files changed, 173 insertions(+), 95 deletions(-) delete mode 100644 src/app/projects/states/dashboard/directives/student-task-list/student-task-list.coffee create mode 100644 src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css rename src/app/projects/states/dashboard/directives/student-task-list/{student-task-list.tpl.html => student-task-list.component.html} (55%) create mode 100644 src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.scss create mode 100644 src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts delete mode 100644 src/app/projects/states/dashboard/directives/student-task-list/student-task-list.scss diff --git a/package-lock.json b/package-lock.json index 9e5fdd1ef7..06415bae1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11200,6 +11200,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index ae030bcc3e..78f0b9bb5f 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -135,6 +135,7 @@ import {environment} from 'src/environments/environment'; import {PickerModule} from '@ctrl/ngx-emoji-mart'; import {EmojiModule} from '@ctrl/ngx-emoji-mart/ngx-emoji'; import {EmojiService} from './common/services/emoji.service'; +import {StudentTaskListComponent} from './projects/states/dashboard/directives/student-task-list/student-task-list.component'; import {TaskListItemComponent} from './projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component'; import {CreatePortfolioTaskListItemComponent} from './projects/states/dashboard/directives/student-task-list/create-portfolio-task-list-item/create-portfolio-task-list-item.component'; import {TaskDescriptionCardComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.component'; @@ -410,6 +411,7 @@ const GANTT_CHART_CONFIG = { UnitAnalyticsComponent, StudentTutorialSelectComponent, StudentCampusSelectComponent, + StudentTaskListComponent, TaskListItemComponent, CreatePortfolioTaskListItemComponent, TaskDescriptionCardComponent, diff --git a/src/app/doubtfire-angularjs.module.ts b/src/app/doubtfire-angularjs.module.ts index ca57426fd2..55d36dfa1d 100644 --- a/src/app/doubtfire-angularjs.module.ts +++ b/src/app/doubtfire-angularjs.module.ts @@ -64,7 +64,6 @@ import 'build/src/app/projects/states/groups/groups.js'; import 'build/src/app/projects/states/feedback/feedback.js'; import 'build/src/app/projects/states/states.js'; import 'build/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.js'; -import 'build/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.js'; import 'build/src/app/projects/states/dashboard/directives/directives.js'; import 'build/src/app/projects/states/dashboard/directives/task-dashboard/task-dashboard.js'; import 'build/src/app/projects/states/dashboard/dashboard.js'; @@ -160,6 +159,7 @@ import {WebcalService} from './api/services/webcal.service'; import {StudentTutorialSelectComponent} from './units/states/edit/directives/unit-students-editor/student-tutorial-select/student-tutorial-select.component'; import {StudentCampusSelectComponent} from './units/states/edit/directives/unit-students-editor/student-campus-select/student-campus-select.component'; import {EmojiService} from './common/services/emoji.service'; +import {StudentTaskListComponent} from './projects/states/dashboard/directives/student-task-list/student-task-list.component'; import {TaskListItemComponent} from './projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component'; import {CreatePortfolioTaskListItemComponent} from './projects/states/dashboard/directives/student-task-list/create-portfolio-task-list-item/create-portfolio-task-list-item.component'; import {TaskDescriptionCardComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.component'; @@ -466,6 +466,10 @@ DoubtfireAngularJSModule.directive( 'taskListItem', downgradeComponent({component: TaskListItemComponent}), ); +DoubtfireAngularJSModule.directive( + 'studentTaskList', + downgradeComponent({component: StudentTaskListComponent}), +); DoubtfireAngularJSModule.directive( 'createPortfolioTaskListItem', downgradeComponent({component: CreatePortfolioTaskListItemComponent}), diff --git a/src/app/projects/states/dashboard/dashboard.tpl.html b/src/app/projects/states/dashboard/dashboard.tpl.html index e79f1af1ca..48b872845b 100644 --- a/src/app/projects/states/dashboard/dashboard.tpl.html +++ b/src/app/projects/states/dashboard/dashboard.tpl.html @@ -1,8 +1,8 @@
diff --git a/src/app/projects/states/dashboard/directives/directives.coffee b/src/app/projects/states/dashboard/directives/directives.coffee index 8ac3fe3ed8..ad3f6ba895 100644 --- a/src/app/projects/states/dashboard/directives/directives.coffee +++ b/src/app/projects/states/dashboard/directives/directives.coffee @@ -1,5 +1,4 @@ angular.module('doubtfire.projects.states.dashboard.directives', [ - 'doubtfire.projects.states.dashboard.directives.student-task-list' 'doubtfire.projects.states.dashboard.directives.progress-dashboard' 'doubtfire.projects.states.dashboard.directives.task-dashboard' ]) diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.coffee b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.coffee deleted file mode 100644 index b4212fa09b..0000000000 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.coffee +++ /dev/null @@ -1,62 +0,0 @@ -angular.module('doubtfire.projects.states.dashboard.directives.student-task-list', []) -# -# View a list of tasks -# -.directive('studentTaskList', -> - restrict: 'E' - templateUrl: 'projects/states/dashboard/directives/student-task-list/student-task-list.tpl.html' - scope: - project: '=' - # Function to invoke to refresh tasks - refreshTasks: '=?' - # Special taskData object (wraps the selectedTask) - taskData: '=' - controller: ($scope, $timeout, $filter, gradeService) -> - # Check taskSource exists - unless $scope.taskData? - throw Error "Invalid taskData provided. Must wrap the selectedTask and selectedTaskAbbr" - # Set up initial filtered tasks - $scope.filteredTasks = [] - # Set up filters - $scope.filters = { - taskName: null - } - # Sets new filteredTasks variable - applyFilters = -> - filteredTasks = $filter('tasksWithName')($scope.project.activeTasks(), $scope.filters.taskName) - $scope.filteredTasks = filteredTasks - $scope.showCreatePortfolio = !$scope.filters.taskName? || 'create portfolio'.indexOf($scope.filters.taskName.toLowerCase()) >= 0 - # Apply filters first-time - applyFilters() - # Sort the tasks according to priority. - $scope.project.calcTopTasks() - # When refreshing tasks, we are just reloading the active tasks - $scope.refreshTasks = applyFilters - # Expose grade service names - $scope.gradeNames = gradeService.grades - # On task name change, reapply filters - $scope.taskNameChanged = applyFilters - # UI call to change currently selected task - $scope.setSelectedTask = (task) -> - # Clicking on already selected task will disable that selection - task = null if $scope.isSelectedTask(task) - $scope.taskData.selectedTask = task - $scope.taskData.onSelectedTaskChange?(task) - scrollToTaskInList(task) if task? - scrollToTaskInList = (task) -> - taskEl = document.querySelector("##{task.taskKeyToIdString()}") - return unless taskEl? - funcName = if taskEl.scrollIntoViewIfNeeded? then 'scrollIntoViewIfNeeded' else if taskEl.scrollIntoView? then 'scrollIntoView' - return unless funcName? - taskEl[funcName]({behavior: 'smooth'}) - $timeout -> - scrollToTaskInList($scope.taskData.selectedTask) if $scope.taskData.selectedTask? - $scope.isSelectedTask = (task) -> - # Compare by definition - task.definition.id == $scope.taskData?.selectedTask?.definition.id - $scope.nearEnd = () -> - lateDate = new Date($scope.project.unit.endDate) # Get end date as date - lateDate.setDate(lateDate.getDate() - 21) # subtract 21 days - new Date() > lateDate - -) diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css new file mode 100644 index 0000000000..21adf1cddb --- /dev/null +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css @@ -0,0 +1 @@ +/* Task list component styles - styles are inherited from existing task-list mixin */ diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.tpl.html b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html similarity index 55% rename from src/app/projects/states/dashboard/directives/student-task-list/student-task-list.tpl.html rename to src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html index 93573d01c9..2adc64aaf5 100644 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.tpl.html +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html @@ -3,66 +3,67 @@
diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.scss b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.scss new file mode 100644 index 0000000000..604734571d --- /dev/null +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.scss @@ -0,0 +1 @@ +/* Task list component styles - inheriting from existing task-list styles */ diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts new file mode 100644 index 0000000000..3af9b5d129 --- /dev/null +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts @@ -0,0 +1,134 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { GradeService } from 'src/app/common/services/grade.service'; + +@Component({ + selector: 'student-task-list', + templateUrl: './student-task-list.component.html', + styleUrls: ['./student-task-list.component.scss'], +}) +export class StudentTaskListComponent implements OnInit { + @Input() project: any; + @Input() taskData: any; + @Input() refreshTasks: any; + + filteredTasks: any[] = []; + filters: any = { + taskName: null + }; + showCreatePortfolio: boolean = false; + gradeNames: any; + + constructor(private gradeService: GradeService) { + // Expose grade service names + this.gradeNames = this.gradeService.grades; + } + + ngOnInit() { + // Check taskData exists but don't throw error - handle gracefully + if (!this.taskData) { + console.warn('StudentTaskList - taskData not provided, creating default'); + this.taskData = { + selectedTask: null, + selectedTaskAbbr: null, + onSelectedTaskChange: null + }; + } + + // Sort the tasks according to priority + if (this.project) { + this.project.calcTopTasks(); + } + + // Apply filters first-time + this.applyFilters(); + + // Set refreshTasks function + this.refreshTasks = this.applyFilters; + + // Scroll to selected task after timeout + setTimeout(() => { + if (this.taskData?.selectedTask) { + this.scrollToTaskInList(this.taskData.selectedTask); + } + }); + } + + applyFilters() { + if (this.project && this.project.activeTasks) { + const allTasks = this.project.activeTasks(); + + // Use the tasksWithName filter (equivalent to AngularJS filter) + let filteredTasks = this.filterTasksByName(allTasks, this.filters.taskName); + + // Sort by topWeight (equivalent to orderBy: 'topWeight') + filteredTasks.sort((a, b) => (b.topWeight || 0) - (a.topWeight || 0)); + + this.filteredTasks = filteredTasks; + this.showCreatePortfolio = !this.filters.taskName || + 'create portfolio'.indexOf(this.filters.taskName.toLowerCase()) >= 0; + } else { + this.filteredTasks = []; + this.showCreatePortfolio = !this.filters.taskName || + 'create portfolio'.indexOf(this.filters.taskName.toLowerCase()) >= 0; + } + } + + filterTasksByName(tasks: any[], taskName: string): any[] { + if (!taskName || taskName.trim() === '') { + return tasks; + } + + const searchTerm = taskName.toLowerCase(); + return tasks.filter(task => + task.definition.name.toLowerCase().includes(searchTerm) || + task.definition.abbreviation.toLowerCase().includes(searchTerm) + ); + } + + taskNameChanged() { + this.applyFilters(); + } + + setSelectedTask(task: any) { + // Clicking on already selected task will disable that selection + if (this.isSelectedTask(task)) { + task = null; + } + this.taskData.selectedTask = task; + if (this.taskData.onSelectedTaskChange) { + this.taskData.onSelectedTaskChange(task); + } + if (task) { + this.scrollToTaskInList(task); + } + } + + scrollToTaskInList(task: any) { + const taskEl = document.querySelector("#" + task.taskKeyToIdString()) as any; + if (!taskEl) return; + + const funcName = taskEl.scrollIntoViewIfNeeded ? 'scrollIntoViewIfNeeded' : + taskEl.scrollIntoView ? 'scrollIntoView' : null; + + if (funcName) { + taskEl[funcName]({behavior: 'smooth'}); + } + } + + isSelectedTask(task: any): boolean { + // Compare by definition + return task && this.taskData?.selectedTask && + task.definition.id === this.taskData.selectedTask.definition.id; + } + + nearEnd(): boolean { + if (!this.project || !this.project.unit) return false; + const lateDate = new Date(this.project.unit.endDate); // Get end date as date + lateDate.setDate(lateDate.getDate() - 21); // subtract 21 days + return new Date() > lateDate; + } + + trackByTaskId(index: number, task: any): any { + return task.id || task.definition.abbreviation; + } +} diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.scss b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.scss deleted file mode 100644 index 8cca67607c..0000000000 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.scss +++ /dev/null @@ -1,3 +0,0 @@ -student-task-list { - @include task-list(); -} From af0462ef0f74a839fb67df846911304f4772bb17 Mon Sep 17 00:00:00 2001 From: Millicent Amolo Date: Tue, 28 Apr 2026 01:39:54 +1000 Subject: [PATCH 2/3] fix: resolve task selection and hover issues - Add proper CSS hover styles for task items - Fix taskData initialization in setSelectedTask - Remove debug console logs - Ensure click events work correctly - Add pointer-events management for badges --- .../student-task-list.component.css | 40 +++++++++++++++++++ .../student-task-list.component.ts | 15 ++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css index 21adf1cddb..cdfe60f7df 100644 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css @@ -1 +1,41 @@ /* Task list component styles - styles are inherited from existing task-list mixin */ + +/* Task item hover effects */ +.list-group-item-task { + cursor: pointer; + transition: background-color 0.2s ease; + position: relative; +} + +.list-group-item-task:hover { + background-color: #f5f5f5; +} + +.list-group-item-task.selected { + background-color: #e6f3ff; + border-color: #9ec0ff; +} + +/* Task data hover effects */ +.task-data h4:hover { + color: #337ab7; +} + +/* Ensure task item is fully clickable */ +.list-group-item-task .task-data, +.list-group-item-task .task-badges { + position: relative; + z-index: 1; +} + +/* Make sure badges don't block clicks */ +.task-badges { + pointer-events: none; +} + +/* Ensure entire task item can be clicked */ +.list-group-item-task { + min-height: 60px; + display: block; + width: 100%; +} diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts index 3af9b5d129..b97ee227ad 100644 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.ts @@ -26,7 +26,6 @@ export class StudentTaskListComponent implements OnInit { ngOnInit() { // Check taskData exists but don't throw error - handle gracefully if (!this.taskData) { - console.warn('StudentTaskList - taskData not provided, creating default'); this.taskData = { selectedTask: null, selectedTaskAbbr: null, @@ -90,14 +89,26 @@ export class StudentTaskListComponent implements OnInit { } setSelectedTask(task: any) { + // Ensure taskData exists + if (!this.taskData) { + this.taskData = { + selectedTask: null, + selectedTaskAbbr: null, + onSelectedTaskChange: null + }; + } + // Clicking on already selected task will disable that selection if (this.isSelectedTask(task)) { task = null; } + this.taskData.selectedTask = task; - if (this.taskData.onSelectedTaskChange) { + + if (this.taskData.onSelectedTaskChange && typeof this.taskData.onSelectedTaskChange === 'function') { this.taskData.onSelectedTaskChange(task); } + if (task) { this.scrollToTaskInList(task); } From e58eb6b2935d929a9435582561fdca9f6b5e1bfc Mon Sep 17 00:00:00 2001 From: Millicent Amolo Date: Sat, 2 May 2026 07:01:32 +1000 Subject: [PATCH 3/3] fix(dashboard): complete student task list migration --- .../entity-form/entity-form.component.ts | 12 +++-- .../states/dashboard/dashboard.coffee | 6 +++ .../student-task-list.component.css | 41 ---------------- .../student-task-list.component.html | 47 +------------------ .../student-task-list.component.scss | 40 ++++++++++++++++ .../student-task-list.component.ts | 39 +++++++++++++-- .../task-list-item.component.scss | 17 +++++++ .../task-comments-viewer.component.ts | 8 +++- 8 files changed, 115 insertions(+), 95 deletions(-) delete mode 100644 src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css diff --git a/src/app/common/entity-form/entity-form.component.ts b/src/app/common/entity-form/entity-form.component.ts index 5ebe626f8a..605ebd2473 100644 --- a/src/app/common/entity-form/entity-form.component.ts +++ b/src/app/common/entity-form/entity-form.component.ts @@ -5,6 +5,8 @@ import { EntityService } from 'ngx-entity-service'; import { Observable, tap } from 'rxjs'; import { Sort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; +import { AppInjector } from 'src/app/app-injector'; +import { AlertService } from 'src/app/common/services/alert.service'; export type OnSuccessMethod = (object: T, isNew: boolean) => void; @@ -58,6 +60,10 @@ export abstract class EntityFormComponent implements AfterView ngAfterViewInit() {} + protected get dfAlertService(): AlertService { + return AppInjector.get(AlertService); + } + /** * Cancel edit of current selected value. */ @@ -138,14 +144,14 @@ export abstract class EntityFormComponent implements AfterView response = service.create(data, this.optionsOnRequest('create')); } else { // Nothing has changed if the selected value, so we want to inform the user - alertService.error( `${this.entityName} was not changed`, 6000); + this.dfAlertService.error(`${this.entityName} was not changed`, 6000); return; } // Handle the response response.subscribe({ next: (result: T) => { - alertService.success( `${this.entityName} saved`, 2000); + this.dfAlertService.success(`${this.entityName} saved`, 2000); // Success is implemented on all inheriting instances and is used // to handle the response appropriately for the context of the form success(result, this.selected ? false : true); @@ -163,7 +169,7 @@ export abstract class EntityFormComponent implements AfterView if (this.selected) { this.restoreFromBackup(); } - alertService.error( `${this.entityName} save failed: ${error}`, 6000); + this.dfAlertService.error(`${this.entityName} save failed: ${error}`, 6000); }, }); } else { diff --git a/src/app/projects/states/dashboard/dashboard.coffee b/src/app/projects/states/dashboard/dashboard.coffee index d4d073c8ad..89633973ae 100644 --- a/src/app/projects/states/dashboard/dashboard.coffee +++ b/src/app/projects/states/dashboard/dashboard.coffee @@ -31,6 +31,12 @@ angular.module('doubtfire.projects.states.dashboard', [ setTaskAbbrAsUrlParams(task) } + # Ensure selection events from the Angular (downgraded) task list update the + # AngularJS scope, so `ng-if="taskData.selectedTask"` panels render. + listeners.push $scope.$on('StudentTaskSelected', (_event, task) -> + $scope.taskData.selectedTask = task + ) + # Sets URL parameters for the task key setTaskAbbrAsUrlParams = (task) -> taskAbbr = if _.isString(task) then task else task?.definition.abbreviation diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css deleted file mode 100644 index cdfe60f7df..0000000000 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.css +++ /dev/null @@ -1,41 +0,0 @@ -/* Task list component styles - styles are inherited from existing task-list mixin */ - -/* Task item hover effects */ -.list-group-item-task { - cursor: pointer; - transition: background-color 0.2s ease; - position: relative; -} - -.list-group-item-task:hover { - background-color: #f5f5f5; -} - -.list-group-item-task.selected { - background-color: #e6f3ff; - border-color: #9ec0ff; -} - -/* Task data hover effects */ -.task-data h4:hover { - color: #337ab7; -} - -/* Ensure task item is fully clickable */ -.list-group-item-task .task-data, -.list-group-item-task .task-badges { - position: relative; - z-index: 1; -} - -/* Make sure badges don't block clicks */ -.task-badges { - pointer-events: none; -} - -/* Ensure entire task item can be clicked */ -.list-group-item-task { - min-height: 60px; - display: block; - width: 100%; -} diff --git a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html index 2adc64aaf5..e492a8cad7 100644 --- a/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html +++ b/src/app/projects/states/dashboard/directives/student-task-list/student-task-list.component.html @@ -24,51 +24,8 @@ [class.selected]="isSelectedTask(task)" *ngFor="let task of filteredTasks; trackBy: trackByTaskId" > -
-

{{task.definition.name}}

-

- - {{task.definition.abbreviation}} - - {{gradeNames[task.definition.targetGrade]}} Task - - {{task.timeToStart()}} - - - {{task.timeToDue()}} - -

-
- -
- -
- - {{task.numNewComments}} - - - - -
-
- {{task.gradeDesc()}} - - {{task.qualityQts}}{{task.definition.maxQualityPts}} - - - - - - - - - ! - -
-
- + +
  • { if (this.taskData?.selectedTask) { @@ -108,12 +108,41 @@ export class StudentTaskListComponent implements OnInit { if (this.taskData.onSelectedTaskChange && typeof this.taskData.onSelectedTaskChange === 'function') { this.taskData.onSelectedTaskChange(task); } + + // Ensure the AngularJS dashboard state sees the selection even if input binding + // semantics differ between AJS/Angular templates. + try { + this.$rootScope?.$broadcast?.('StudentTaskSelected', task); + } catch { + // noop + } + + // This component is used inside an AngularJS template. Click handlers in Angular + // won't automatically trigger an AngularJS digest, so ensure the AJS `ng-if` + // blocks watching `taskData.selectedTask` update immediately. + this.triggerAngularJsDigest(); if (task) { this.scrollToTaskInList(task); } } + private triggerAngularJsDigest() { + try { + if (this.$rootScope?.$applyAsync) { + this.$rootScope.$applyAsync(); + return; + } + + // Fallback for cases where AngularJS services aren't exposed as injectables. + const ng = (globalThis as any)?.angular; + const $rootScope = ng?.element?.(document.body)?.injector?.()?.get?.('$rootScope'); + $rootScope?.$applyAsync?.(); + } catch { + // noop - app can run without angularjs injector in some contexts + } + } + scrollToTaskInList(task: any) { const taskEl = document.querySelector("#" + task.taskKeyToIdString()) as any; if (!taskEl) return; diff --git a/src/app/projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component.scss b/src/app/projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component.scss index bb60a29fd6..8cfd94d991 100644 --- a/src/app/projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component.scss +++ b/src/app/projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component.scss @@ -3,3 +3,20 @@ display: flex; padding-right: 15px; } + +.task-data { + flex: 1; + min-width: 0; +} + +.task-data h4, +.task-data p { + margin-bottom: 0; +} + +/* Keep the metadata line to a single row (2-line layout overall) */ +.task-data p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/src/app/tasks/task-comments-viewer/task-comments-viewer.component.ts b/src/app/tasks/task-comments-viewer/task-comments-viewer.component.ts index d7366dac8d..0d2ab8b1b6 100644 --- a/src/app/tasks/task-comments-viewer/task-comments-viewer.component.ts +++ b/src/app/tasks/task-comments-viewer/task-comments-viewer.component.ts @@ -166,7 +166,13 @@ export class TaskCommentsViewerComponent implements OnChanges, OnInit, OnDestroy this.feedbackTemplateService .query({contextType: 'task_definitions', contextId: task.definition.id}, {}) .subscribe({ - error: () => this.alerts.error('Error loading task feedback templates.'), + error: (err: any) => { + // Feedback templates are optional and may not exist for all tasks/contexts. + // Avoid flashing an error toast on expected "not found"/auth cases. + const status = err?.status; + if (status === 404 || status === 403) return; + this.alerts.error('Error loading task feedback templates.'); + }, }); } }