diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index fa5708f4d9..d14713cae5 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -265,7 +265,8 @@ import {PrivacyPolicy} from './config/privacy-policy/privacy-policy'; import {UnitStaffEditorComponent} from './units/states/edit/directives/unit-staff-editor/unit-staff-editor.component'; import {PortfolioGradeSelectStepComponent} from './projects/states/portfolio/directives/portfolio-grade-select-step/portfolio-grade-select-step.component'; import {GroupMemberContributionAssignerComponent} from './groups/group-member-contribution-assigner/group-member-contribution-assigner.component'; - +import {TaskIloAlignmentModalComponent} from './tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component'; +import {TaskIloAlignmentModalService} from './tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.service'; // See https://stackoverflow.com/questions/55721254/how-to-change-mat-datepicker-date-format-to-dd-mm-yyyy-in-simplest-way/58189036#58189036 const MY_DATE_FORMAT = { parse: { @@ -440,6 +441,7 @@ import {ProjectGroupsComponent} from './projects/states/groups/project-groups/pr RolloverComponent, ProjectGroupsComponent, GroupMemberContributionAssignerComponent, + TaskIloAlignmentModalComponent, ], // Services we provide providers: [ @@ -472,6 +474,7 @@ import {ProjectGroupsComponent} from './projects/states/groups/project-groups/pr FileDownloaderService, CheckForUpdateService, TaskOutcomeAlignmentService, + TaskIloAlignmentModalService, visualisationsProvider, commentsModalProvider, rootScopeProvider, diff --git a/src/app/doubtfire-angularjs.module.ts b/src/app/doubtfire-angularjs.module.ts index 4855d04ad6..0d0d5e2026 100644 --- a/src/app/doubtfire-angularjs.module.ts +++ b/src/app/doubtfire-angularjs.module.ts @@ -41,8 +41,8 @@ import 'build/src/app/tasks/modals/modals.js'; import 'build/src/app/tasks/tasks.js'; import 'build/src/app/tasks/task-ilo-alignment/task-ilo-alignment.js'; import 'build/src/app/tasks/task-ilo-alignment/task-ilo-alignment-rater/task-ilo-alignment-rater.js'; -import 'build/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment.js'; -import 'build/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.js'; +// import 'build/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment.js'; +// import 'build/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.js'; import 'build/src/app/tasks/task-ilo-alignment/task-ilo-alignment-editor/task-ilo-alignment-editor.js'; import 'build/src/app/tasks/task-ilo-alignment/task-ilo-alignment-viewer/task-ilo-alignment-viewer.js'; import 'build/src/app/config/runtime/runtime.js'; @@ -209,6 +209,7 @@ import {PortfolioAddExtraFilesStepComponent} from './projects/states/portfolio/d import {UnitGroupsComponent} from './units/states/groups/unit-groups/unit-groups.component'; import {ProjectGroupsComponent} from './projects/states/groups/project-groups/project-groups.component'; import {GroupMemberContributionAssignerComponent} from './groups/group-member-contribution-assigner/group-member-contribution-assigner.component'; +import {TaskIloAlignmentModalService} from './tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.service'; export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.config', @@ -295,6 +296,10 @@ DoubtfireAngularJSModule.factory( downgradeInjectable(UnitStudentEnrolmentModalService), ); DoubtfireAngularJSModule.factory('PrivacyPolicy', downgradeInjectable(PrivacyPolicy)); +DoubtfireAngularJSModule.factory( + 'TaskILOAlignmentModal', + downgradeInjectable(TaskIloAlignmentModalService), +); // directive -> component DoubtfireAngularJSModule.directive( @@ -488,6 +493,9 @@ DoubtfireAngularJSModule.directive( ); // Global configuration +// To compensate for rest unmigrated components +angular.module('doubtfire.tasks.task-ilo-alignment.modals', []); +angular.module('doubtfire.tasks.task-ilo-alignment.modals.task-ilo-alignment-modal', []); // If the user enters a URL that doesn't match any known URL (state), send them to `/home` const otherwiseConfigBlock = [ diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.coffee b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.coffee deleted file mode 100644 index 51633d4fb6..0000000000 --- a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.coffee +++ /dev/null @@ -1,93 +0,0 @@ -angular.module('doubtfire.tasks.task-ilo-alignment.modals.task-ilo-alignment-modal', []) - -# -# Shows a modal where users can align tasks to ILOs -# -.factory('TaskILOAlignmentModal', ($modal) -> - TaskILOAlignmentModal = {} - - TaskILOAlignmentModal.show = (task, ilo, alignment, unit, project, source) -> - $modal.open - controller: 'TaskILOAlignmentModalCtrl' - templateUrl: 'tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.tpl.html' - resolve: - task: -> task - ilo: -> ilo - alignment: -> alignment - unit: -> unit - project: -> project - source: -> source - - TaskILOAlignmentModal -) - -.controller('TaskILOAlignmentModalCtrl', ($scope, $rootScope, $modalInstance, alertService, newTaskOutcomeAlignmentService, task, ilo, alignment, unit, project, source) -> - $scope.source = source - $scope.unit = unit - $scope.task = task - $scope.ilo = ilo - $scope.alignment = alignment - $scope.project = project - - if !$scope.alignment - $scope.alignment = newTaskOutcomeAlignmentService.createInstanceFrom({}, $scope.source) - $scope.alignment.learningOutcome = $scope.ilo - $scope.alignment.taskDefinition = task.definition - $scope.alignment.rating = 0 - $scope.alignment.description = "" - - $scope.editingRationale = false - - $scope.toggleEditRationale = -> - if $scope.editingRationale - updateAlignment() - $scope.editingRationale = !$scope.editingRationale - - $scope.removeAlignmentItem = -> - if $scope.project? - params = { - project_id: $scope.project.id - } - newTaskOutcomeAlignmentService.delete($scope.alignment, {cache: $scope.alignment.within.taskOutcomeAlignmentsCache, params: params}).subscribe({ - next: (response) -> - alertService.success( "Task - Outcome alignment rating removed", 2000) - $rootScope.$broadcast('UpdateAlignmentChart') - $modalInstance.close $scope.alignment - error: (message) -> alertService.error( message, 6000) - }) - - updateAlignment = -> - if $scope.project? - params = { - project_id: $scope.project.id - } - newTaskOutcomeAlignmentService.update($scope.alignment, {cache: $scope.alignment.within.taskOutcomeAlignmentsCache, params: params}).subscribe({ - next: (response) -> - alertService.success( "Task - Outcome alignment rating saved", 2000) - $rootScope.$broadcast('UpdateAlignmentChart') - error: (message) -> alertService.error( message, 6000) - }) - - addAlignment = -> - if $scope.project? - params = { - project_id: $scope.project.id - } - newTaskOutcomeAlignmentService.store($scope.alignment, {cache: $scope.source.taskOutcomeAlignmentsCache, constructorParams: $scope.source, params: params}).subscribe({ - next: (response) -> - $scope.alignment = response - $rootScope.$broadcast('UpdateAlignmentChart') - error: (message) -> alertService.error( message, 6000) - }) - - $scope.updateRating = (alignment) -> - unless $scope.alignment.id? - addAlignment alignment - else - updateAlignment alignment - - $scope.closeModal = -> - if $scope.editingRationale - $scope.updateRating $scope.alignment - $modalInstance.close $scope.alignment -) diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.html b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.html new file mode 100644 index 0000000000..1d9cfa7b10 --- /dev/null +++ b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.html @@ -0,0 +1,92 @@ +

+ How much does + + {{ data.task.definition.abbreviation }} + + relate to + + {{ data.ilo.abbreviation }} ? +

+ + +
+
+

Select Rating

+ + +
+ + +
+ + +
+ + {{ alignmentLabels[alignment.rating] }} + +
+
+ + +
+ +
+ + Provided Rationale + + +
+ +
+ Click to add a rationale... +
+
+ + +
+ + Rationale + + + +
+
+
+
+ + + + + + diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.scss b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.scss new file mode 100644 index 0000000000..8c1bfb60c4 --- /dev/null +++ b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.scss @@ -0,0 +1,78 @@ +@import '../../../../../theme.scss'; + +.label-info { + background-color: #17a2b8; + color: white; + padding: 0.3em 0.8em; + font-size: 1rem; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25em; +} + +.rating-container { + display: flex; + gap: 12px; + justify-content: center; + align-items: center; + padding: 10px; +} + +.rating-circle { + width: 40px; + height: 40px; + border-radius: 50%; + border: none; + cursor: pointer; + transition: all 0.2s ease-in-out; + + background-color: #e0e0e0; + + &:hover { + transform: scale(1.1); + background-color: mix(#3f51b5, #e0e0e0, 20%); + } + + // Filled state + &.filled { + background-color: #3f51b5; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + + &:hover { + background-color: darken(#3f51b5, 5%); + } + } +} + +.rating-label-badge { + background-color: #303f9f; + color: white; + padding: 8px 20px; + border-radius: 20px; + font-size: 1.5rem; + font-weight: 500; + display: inline-block; + text-align: center; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + line-height: 1.4; +} + +mat-dialog-content { + min-height: 350px; + overflow-y: auto; + padding-bottom: 20px; +} + +// Rationale Text Area +.rationale-display, .markdown-content { + font-size: 1rem; + line-height: 1.6; +} + +.rationale-header { + font-size: 0.85rem; + letter-spacing: 0.05em; +} diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.ts b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.ts new file mode 100644 index 0000000000..b77b9aaeb2 --- /dev/null +++ b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.component.ts @@ -0,0 +1,118 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {TaskIloAlignmentModalData} from './task-ilo-alignment-modal.service'; +import {TaskOutcomeAlignmentService} from 'src/app/api/services/task-outcome-alignment.service'; +import {AlertService} from 'src/app/common/services/alert.service'; +import {TaskOutcomeAlignment} from 'src/app/api/models/task-outcome-alignment'; + +@Component({ + selector: 'f-task-ilo-alignment-modal', + templateUrl: 'task-ilo-alignment-modal.component.html', + styleUrls: ['task-ilo-alignment-modal.component.scss'], +}) +export class TaskIloAlignmentModalComponent implements OnInit { + public alignment: TaskOutcomeAlignment; + public editingRationale = false; + + // Ported from outcome-service.coffee + // Index 0 matches "0" rating (which we don't show as a button, but exists as state) + public alignmentLabels = [ + 'The task is not related to this outcome at all', + 'The task is slightly related to this outcome', + 'The task is related to this outcome', + 'The task is a reasonable example for this outcome', + 'The task is a strong example of this outcome', + 'The task is the best example of this outcome', + ]; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: TaskIloAlignmentModalData, + private alignmentService: TaskOutcomeAlignmentService, + private alertService: AlertService, + ) {} + + ngOnInit(): void { + if (!this.data.alignment) { + this.alignment = this.alignmentService.createInstanceFrom({}, this.data.source); + this.alignment.learningOutcome = this.data.ilo; + this.alignment.taskDefinition = this.data.task.definition; + this.alignment.rating = 0; + this.alignment.description = ''; + this.alignment.within = this.data.source; + } else { + this.alignment = this.data.alignment; + } + + this.alignment.within = this.data.source; + } + + toggleEditRationale(): void { + if (this.editingRationale) { + this.save(); + } + this.editingRationale = !this.editingRationale; + } + + onRatingChange(val: number): void { + this.alignment.rating = val; + this.save(); + } + + removeAlignmentItem(): void { + const params = this.data.project ? {project_id: this.data.project.id} : undefined; + + this.alignmentService + .delete(this.alignment, { + cache: this.alignment.within.taskOutcomeAlignmentsCache, + params: params, + }) + .subscribe({ + next: () => { + this.alertService.success('Alignment removed'); + this.dialogRef.close(this.alignment); + }, + error: (msg) => this.alertService.error(msg), + }); + } + + save(): void { + const params = this.data.project ? {project_id: this.data.project.id} : undefined; + + if (!this.alignment.id) { + this.alignmentService + .store(this.alignment, { + cache: this.data.source.taskOutcomeAlignmentsCache, + constructorParams: this.data.source, + params: params, + }) + .subscribe({ + next: (response) => { + this.alignment = response; + this.alignment.within = this.data.source; + this.alertService.success('Alignment saved'); + }, + error: (msg) => this.alertService.error(msg), + }); + } else { + this.alignmentService + .update(this.alignment, { + cache: this.alignment.within.taskOutcomeAlignmentsCache, + params: params, + }) + .subscribe({ + next: () => { + this.alertService.success('Alignment updated'); + }, + error: (msg) => this.alertService.error(msg), + }); + } + } + + closeModal(): void { + if (this.editingRationale) { + this.save(); + } + this.dialogRef.close(this.alignment); + } +} diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.service.ts b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.service.ts new file mode 100644 index 0000000000..45651ea50a --- /dev/null +++ b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.service.ts @@ -0,0 +1,43 @@ +import {Injectable} from '@angular/core'; +import {MatDialog} from '@angular/material/dialog'; +import {TaskIloAlignmentModalComponent} from './task-ilo-alignment-modal.component'; +import { + Task, + LearningOutcome, + TaskOutcomeAlignment, + Unit, + Project, +} from 'src/app/api/models/doubtfire-model'; +import {Observable} from 'rxjs'; + +export interface TaskIloAlignmentModalData { + task: Task; + ilo: LearningOutcome; + alignment: TaskOutcomeAlignment; + unit: Unit; + project?: Project; + source: Unit | Project; +} + +@Injectable({ + providedIn: 'root', +}) +export class TaskIloAlignmentModalService { + constructor(private dialog: MatDialog) {} + + public show( + task: Task, + ilo: LearningOutcome, + alignment: TaskOutcomeAlignment, + unit: Unit, + project: Project, + source: Unit | Project, + ): Observable { + const dialogRef = this.dialog.open(TaskIloAlignmentModalComponent, { + width: '600px', + data: {task, ilo, alignment, unit, project, source}, + }); + + return dialogRef.afterClosed(); + } +} diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.tpl.html b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.tpl.html deleted file mode 100644 index a89b929a99..0000000000 --- a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.tpl.html +++ /dev/null @@ -1,40 +0,0 @@ -
- - - -
diff --git a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment.coffee b/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment.coffee deleted file mode 100644 index 8217d7b43f..0000000000 --- a/src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment.coffee +++ /dev/null @@ -1,3 +0,0 @@ -angular.module('doubtfire.tasks.task-ilo-alignment.modals', [ - 'doubtfire.tasks.task-ilo-alignment.modals.task-ilo-alignment-modal' -])