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
+
+
+
+
+
+
+
+
+
+ 0">
+ {{ alignmentLabels[alignment.rating] }}
+
+
+
+
+
+
0">
+
+
+
+
+
+
+
+ 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 @@
-
-
-
-
- Hover and click over a rating value below
-
-
-
-
-
-
-
Click to add one
-
-
-
-
-
-
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'
-])