From 6dae8208b073e1a51960e8effffd64245ea22cfa Mon Sep 17 00:00:00 2001 From: jeffy Date: Mon, 15 Dec 2025 02:13:26 +1100 Subject: [PATCH 1/6] refactor(units): migrate units index to Angular component --- src/app/doubtfire-angular.module.ts | 3 + src/app/doubtfire-angularjs.module.ts | 8 ++ .../units/states/index/index.component.html | 10 +++ .../units/states/index/index.component.scss | 0 .../states/index/index.component.spec.ts | 23 +++++ src/app/units/states/index/index.component.ts | 90 +++++++++++++++++++ src/app/units/states/index/index.tpl.html | 8 +- 7 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 src/app/units/states/index/index.component.html create mode 100644 src/app/units/states/index/index.component.scss create mode 100644 src/app/units/states/index/index.component.spec.ts create mode 100644 src/app/units/states/index/index.component.ts diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index 9284745988..7d3cf3d6d7 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -224,6 +224,8 @@ import {FTaskSheetViewComponent} from './units/states/tasks/viewer/directives/f- import {TasksViewerComponent} from './units/states/tasks/tasks-viewer/tasks-viewer.component'; import {UnitCodeComponent} from './common/unit-code/unit-code.component'; import {GradeService} from './common/services/grade.service'; +import { IndexComponent as UnitsIndexComponent } from './units/states/index/index.component'; + @NgModule({ // Components we declare @@ -325,6 +327,7 @@ import {GradeService} from './common/services/grade.service'; FUsersComponent, FTaskBadgeComponent, FUnitsComponent, + UnitsIndexComponent, ], // Services we provide providers: [ diff --git a/src/app/doubtfire-angularjs.module.ts b/src/app/doubtfire-angularjs.module.ts index 868e381213..8ad419856d 100644 --- a/src/app/doubtfire-angularjs.module.ts +++ b/src/app/doubtfire-angularjs.module.ts @@ -225,6 +225,8 @@ import {FUnitsComponent} from './admin/states/f-units/f-units.component'; import {MarkedPipe} from './common/pipes/marked.pipe'; import {AlertService} from './common/services/alert.service'; import {GradeService} from './common/services/grade.service'; +import {IndexComponent as UnitsIndexComponent} from './units/states/index/index.component'; + export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.config', 'doubtfire.sessions', @@ -237,6 +239,12 @@ export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.visualisations', ]); +DoubtfireAngularJSModule.directive( + 'fUnitsIndex', + downgradeComponent({component: UnitsIndexComponent}), +); + + // Downgrade angular modules that we need... // factory -> service DoubtfireAngularJSModule.factory('AboutDoubtfireModal', downgradeInjectable(AboutDoubtfireModal)); diff --git a/src/app/units/states/index/index.component.html b/src/app/units/states/index/index.component.html new file mode 100644 index 0000000000..7ed77ff8da --- /dev/null +++ b/src/app/units/states/index/index.component.html @@ -0,0 +1,10 @@ + +
+ +

Loading unit details...

+
+ + +
+ +
diff --git a/src/app/units/states/index/index.component.scss b/src/app/units/states/index/index.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/units/states/index/index.component.spec.ts b/src/app/units/states/index/index.component.spec.ts new file mode 100644 index 0000000000..2e2d8eabd8 --- /dev/null +++ b/src/app/units/states/index/index.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IndexComponent } from './index.component'; + +describe('IndexComponent', () => { + let component: IndexComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [IndexComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(IndexComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/units/states/index/index.component.ts b/src/app/units/states/index/index.component.ts new file mode 100644 index 0000000000..386a732d0a --- /dev/null +++ b/src/app/units/states/index/index.component.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import {Component, Inject, OnInit} from '@angular/core'; + +@Component({ + selector: 'f-units-index', + templateUrl: './index.component.html', + styleUrls: ['./index.component.scss'], +}) +export class IndexComponent implements OnInit { + // Variables that replace $scope + unitRole: any; + unit: any; + unitId!: number; + project: any; + + constructor( + @Inject('$state') private $state: any, + @Inject('$stateParams') private $stateParams: any, + @Inject('newUnitService') private newUnitService: any, + @Inject('newProjectService') private newProjectService: any, + @Inject('listenerService') private listenerService: any, + @Inject('GlobalStateService') private globalStateService: any, + @Inject('newUserService') private newUserService: any, + @Inject('alertService') private alertService: any, + ) {} + + ngOnInit(): void { + // Get unitId from URL params + this.unitId = +this.$stateParams.unitId; + + // If no unitId → redirect to home + if (!this.unitId) { + this.$state.go('home'); + return; + } + + // Wait for global state to load + this.globalStateService.onLoad(() => { + // Find the role for this unit + this.unitRole = + this.globalStateService.loadedUnitRoles.currentValues.find( + (unitRole: any) => unitRole.unit.id === this.unitId, + ); + + // Admin / Auditor fallback + if ( + !this.unitRole && + (this.newUserService.currentUser.role === 'Admin' || + this.newUserService.currentUser.role === 'Auditor') + ) { + this.unitRole = this.newUserService.adminOrAuditorRoleFor( + this.newUserService.currentUser.role, + this.unitId, + this.newUserService.currentUser, + ); + } + + // If still no role, redirect to home + if (!this.unitRole) { + this.$state.go('home'); + return; + } + + // Set the app to "UNIT view" + this.globalStateService.setView('UNIT', this.unitRole); + + // Load the unit and students + this.newUnitService.get(this.unitId).subscribe({ + next: (unit: any) => { + this.newProjectService.loadStudents(unit).subscribe({ + next: (_students: any) => { + this.unit = unit; + }, + error: (err: any) => { + this.alertService.error( + 'Error loading students: ' + err, + 8000, + ); + setTimeout(() => this.$state.go('home'), 5000); + }, + }); + }, + error: (err: any) => { + this.alertService.error('Error loading unit: ' + err, 8000); + setTimeout(() => this.$state.go('home'), 5000); + }, + }); + }); + } +} diff --git a/src/app/units/states/index/index.tpl.html b/src/app/units/states/index/index.tpl.html index 795574f613..3b286bc891 100644 --- a/src/app/units/states/index/index.tpl.html +++ b/src/app/units/states/index/index.tpl.html @@ -1,7 +1 @@ -
- -

Loading unit details...

-
-
- -
+ From c28745e08436cec7d3ba3984a58dd97ce004eef1 Mon Sep 17 00:00:00 2001 From: jeffy Date: Fri, 19 Dec 2025 16:19:55 +1100 Subject: [PATCH 2/6] chore(units): remove empty index component scss file --- src/app/units/states/index/index.component.scss | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/app/units/states/index/index.component.scss diff --git a/src/app/units/states/index/index.component.scss b/src/app/units/states/index/index.component.scss deleted file mode 100644 index e69de29bb2..0000000000 From bb972906ca65f194d121e6f69e2fe520588f2e23 Mon Sep 17 00:00:00 2001 From: jeffy Date: Fri, 19 Dec 2025 16:41:05 +1100 Subject: [PATCH 3/6] refactor(units): use angular wrapper template for units index --- src/app/units/states/index/index.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/units/states/index/index.component.ts b/src/app/units/states/index/index.component.ts index 386a732d0a..a19dbc24e1 100644 --- a/src/app/units/states/index/index.component.ts +++ b/src/app/units/states/index/index.component.ts @@ -4,7 +4,6 @@ import {Component, Inject, OnInit} from '@angular/core'; @Component({ selector: 'f-units-index', templateUrl: './index.component.html', - styleUrls: ['./index.component.scss'], }) export class IndexComponent implements OnInit { // Variables that replace $scope From 099c8971789c6f13b2f76519f914a5ceca93acc5 Mon Sep 17 00:00:00 2001 From: jeffy Date: Tue, 30 Dec 2025 17:06:20 +1100 Subject: [PATCH 4/6] refactor(units): remove legacy CoffeeScript index state --- src/app/units/states/index/index.coffee | 50 ------------------------- 1 file changed, 50 deletions(-) delete mode 100644 src/app/units/states/index/index.coffee diff --git a/src/app/units/states/index/index.coffee b/src/app/units/states/index/index.coffee deleted file mode 100644 index 7a7346a319..0000000000 --- a/src/app/units/states/index/index.coffee +++ /dev/null @@ -1,50 +0,0 @@ -angular.module('doubtfire.units.states.index', []) - -# -# Root state for units -# -.config(($stateProvider) -> - $stateProvider.state 'units/index', { - url: "/units/:unitId" - abstract: true - views: - main: - controller: "UnitsIndexStateCtrl" - templateUrl: "units/states/index/index.tpl.html" - data: - pageTitle: "_Home_" - roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'] - } -) - -.controller("UnitsIndexStateCtrl", ($scope, $rootScope, $state, $stateParams, newUnitService, newProjectService, listenerService, GlobalStateService, newUserService, alertService) -> - # Error - required unitId is missing! - unitId = +$stateParams.unitId - return $state.go('home') unless unitId - - GlobalStateService.onLoad () -> - # Load assessing unit role - $scope.unitRole = GlobalStateService.loadedUnitRoles.currentValues.find((unitRole) -> unitRole.unit.id == unitId) - - if (! $scope.unitRole?) && ( newUserService.currentUser.role == "Admin" || newUserService.currentUser.role == "Auditor" ) - $scope.unitRole = newUserService.adminOrAuditorRoleFor(newUserService.currentUser.role, unitId, newUserService.currentUser) - - # Go home if no unit role was found - return $state.go('home') unless $scope.unitRole? - - GlobalStateService.setView("UNIT", $scope.unitRole) - - newUnitService.get(unitId).subscribe({ - next: (unit)-> - newProjectService.loadStudents(unit).subscribe({ - next: (students)-> - $scope.unit = unit - error: (err)-> - alertService.error( "Error loading students: " + err, 8000) - setTimeout((()-> $state.go('home')), 5000) - }) - error: (err)-> - alertService.error( "Error loading unit: " + err, 8000) - setTimeout((()-> $state.go('home')), 5000) - }) -) From 16ce55da06a416802aab557c48cd72e52499dd99 Mon Sep 17 00:00:00 2001 From: jeffy Date: Sun, 4 Jan 2026 21:29:31 +1100 Subject: [PATCH 5/6] refactor: migrate units index state to angular --- src/app/doubtfire-angularjs.module.ts | 19 +-- src/app/doubtfire.states.ts | 145 ++---------------- src/app/projects/states/index/index.coffee | 3 +- .../units/states/index/index.ajs.module.ts | 9 ++ .../units/states/index/index.component.html | 2 +- .../units/states/index/index.component.scss | 4 + .../states/index/index.component.spec.ts | 23 --- src/app/units/states/index/index.component.ts | 72 +-------- src/app/units/states/index/index.state.ts | 21 +++ src/app/units/states/index/index.tpl.html | 1 - 10 files changed, 66 insertions(+), 233 deletions(-) create mode 100644 src/app/units/states/index/index.ajs.module.ts create mode 100644 src/app/units/states/index/index.component.scss delete mode 100644 src/app/units/states/index/index.component.spec.ts create mode 100644 src/app/units/states/index/index.state.ts delete mode 100644 src/app/units/states/index/index.tpl.html diff --git a/src/app/doubtfire-angularjs.module.ts b/src/app/doubtfire-angularjs.module.ts index 8ad419856d..7e3b47b092 100644 --- a/src/app/doubtfire-angularjs.module.ts +++ b/src/app/doubtfire-angularjs.module.ts @@ -1,9 +1,5 @@ -// # -// # Doubtfire - A lightweight, modern learning management system -// # -// # Doubtfire is modularised into many modules, as indicated by the directory -// # tree inside app/ -// # + +import './units/states/index/index.ajs.module'; import * as angular from 'angular'; import {downgradeInjectable, downgradeComponent} from '@angular/upgrade/static'; @@ -27,7 +23,7 @@ import 'angulartics/dist/angulartics.min.js'; import 'angulartics-google-analytics/lib/angulartics-google-analytics.js'; import 'angular-md5/angular-md5.js'; -// Ok... here is what we need to convert! + import 'build/templates-app.js'; import 'build/assets/wav-worker.js'; @@ -113,7 +109,6 @@ import 'build/src/app/units/states/edit/edit.js'; import 'build/src/app/units/states/rollover/directives/unit-dates-selector/unit-dates-selector.js'; import 'build/src/app/units/states/rollover/directives/directives.js'; import 'build/src/app/units/states/rollover/rollover.js'; -import 'build/src/app/units/states/index/index.js'; import 'build/src/app/units/states/students-list/students-list.js'; import 'build/src/app/units/states/analytics/analytics.js'; import 'build/src/app/common/filters/filters.js'; @@ -225,7 +220,7 @@ import {FUnitsComponent} from './admin/states/f-units/f-units.component'; import {MarkedPipe} from './common/pipes/marked.pipe'; import {AlertService} from './common/services/alert.service'; import {GradeService} from './common/services/grade.service'; -import {IndexComponent as UnitsIndexComponent} from './units/states/index/index.component'; +// export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.config', @@ -239,13 +234,9 @@ export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.visualisations', ]); -DoubtfireAngularJSModule.directive( - 'fUnitsIndex', - downgradeComponent({component: UnitsIndexComponent}), -); +// -// Downgrade angular modules that we need... // factory -> service DoubtfireAngularJSModule.factory('AboutDoubtfireModal', downgradeInjectable(AboutDoubtfireModal)); DoubtfireAngularJSModule.factory( diff --git a/src/app/doubtfire.states.ts b/src/app/doubtfire.states.ts index b9f95e88af..b95c7a4058 100644 --- a/src/app/doubtfire.states.ts +++ b/src/app/doubtfire.states.ts @@ -1,4 +1,5 @@ import {NgHybridStateDeclaration} from '@uirouter/angular-hybrid'; + import {InstitutionSettingsComponent} from './admin/institution-settings/institution-settings.component'; import {HomeComponent} from './home/states/home/home.component'; import {WelcomeComponent} from './welcome/welcome.component'; @@ -9,19 +10,18 @@ import {AcceptEulaComponent} from './eula/accept-eula/accept-eula.component'; import {FUsersComponent} from './admin/states/f-users/f-users.component'; import {FUnitsComponent} from './admin/states/f-units/f-units.component'; +/* IMPORT */ +import {unitsIndexState} from './units/states/index/index.state'; + /* * Use this file to store any states that are sourced by angular components. */ -/** - * Define the institution settings state - used to edit campus data. - */ const institutionSettingsState: NgHybridStateDeclaration = { - name: 'institutionsettings', // This is the name of the state to jump to - so ui-sref="institutionsettings" to jump here - url: '/admin/institution-settings', // You get here with this url + name: 'institutionsettings', + url: '/admin/institution-settings', views: { main: { - // Main body links to angular component component: InstitutionSettingsComponent, }, }, @@ -32,11 +32,10 @@ const institutionSettingsState: NgHybridStateDeclaration = { }; const usersState: NgHybridStateDeclaration = { - name: 'admin/users', // This is the name of the state to jump to - so ui-sref="users" to jump here - url: '/admin/users', // You get here with this url + name: 'admin/users', + url: '/admin/users', views: { main: { - // Main body links to angular component component: FUsersComponent, }, }, @@ -46,9 +45,6 @@ const usersState: NgHybridStateDeclaration = { }, }; -/** - * Define the new home state. - */ const HomeState: NgHybridStateDeclaration = { name: 'home', url: '/home', @@ -63,98 +59,6 @@ const HomeState: NgHybridStateDeclaration = { }, }; -// const unitParentState: NgHybridStateDeclaration = { -// name: 'units', -// url: '/units/:unit_id', -// // template for the parent state -// views: { -// main: { -// component: IndexComponent, -// }, -// }, -// resolve: { -// unit: function ($stateParams) { -// const unitService = AppInjector.get(UnitService); -// const globalState = AppInjector.get(GlobalStateService); -// globalState.onLoad(() => {}); -// console.log($stateParams); -// unitService.query({ id: $stateParams.unit_id }).subscribe((unit) => { -// console.log($stateParams.unit_id); -// console.log(unit); -// return unit; -// }); -// }, -// unitRole: function ($stateParams) { -// const globalStateService = AppInjector.get(GlobalStateService); - -// globalStateService.unitRolesSubject.subscribe((unitRoles) => { -// return unitRoles.find((unitRole) => unitRole.id === $stateParams.unit_id); -// }); -// }, -// }, -// }; - -/** - * Define the new home state. - */ -// const InboxState: NgHybridStateDeclaration = { -// name: 'inbox', -// url: '/units/:unit_id/inbox/:task_key', - -// params: { -// // unitRole: UnitRole, -// // taskKey: null, -// // taskData: -// // unit, -// }, -// views: { -// main: { -// component: InboxComponent, -// }, -// }, -// data: { -// task: 'Task Inbox', -// pageTitle: '_Home_', -// roleWhitelist: ['Tutor', 'Convenor', 'Admin'], -// }, -// resolve: { -// unit$: function ($stateParams) { -// const unitService = AppInjector.get(UnitService); -// const globalState = AppInjector.get(GlobalStateService); -// globalState.onLoad(() => {}); -// console.log($stateParams); -// return unitService.get({ id: $stateParams.unit_id }); -// }, -// unitRole$: function ($stateParams) { -// const globalStateService = AppInjector.get(GlobalStateService); - -// const result = globalStateService.loadedUnitRoles.values.pipe( -// map((unitRoles) => unitRoles.find((unitRole) => unitRole.id == $stateParams.unit_id)) -// ); -// return result; -// }, -// taskData$: function () { -// const taskService = AppInjector.get(TaskService); -// const taskData = { -// taskKey: null, -// source: null, -// selectedTask: null, -// onSelectedTaskChange: (task) =>{ -// const taskKey = task?.taskKey() -// $scope.taskData.taskKey = taskKey -// setTaskKeyAsUrlParams(task); -// } -// } -// taskData.source = taskService.queryTasksForTaskInbox.bind(taskService); -// taskData.taskDefMode = false; -// return of(taskData); -// }, -// }, -// }; - -/** - * Define the welcome state. - */ const WelcomeState: NgHybridStateDeclaration = { name: 'welcome', url: '/welcome', @@ -169,9 +73,6 @@ const WelcomeState: NgHybridStateDeclaration = { }, }; -/** - * Define the Sign In state. - */ const SignInState: NgHybridStateDeclaration = { name: 'sign_in', url: '/sign_in?dest¶ms&authToken&username', @@ -185,9 +86,6 @@ const SignInState: NgHybridStateDeclaration = { }, }; -/** - * Define the Edit Profile state. - */ const EditProfileState: NgHybridStateDeclaration = { name: 'edit_profile', url: '/edit_profile', @@ -234,9 +132,7 @@ const ViewAllProjectsState: NgHybridStateDeclaration = { name: 'view-all-projects', url: '/view-all-projects', resolve: { - 'mode': function () { - return 'student'; - }, + mode: () => 'student', }, views: { main: { @@ -250,16 +146,13 @@ const ViewAllProjectsState: NgHybridStateDeclaration = { }; const AdministerUnits: NgHybridStateDeclaration = { - name: 'admin/units', // This is the name of the state to jump to - so ui-sref="users" to jump here - url: '/admin/units', // You get here with this url + name: 'admin/units', + url: '/admin/units', resolve: { - 'mode': function () { - return 'admin'; - }, + mode: () => 'admin', }, views: { main: { - // Main body links to angular component component: FUnitsComponent, }, }, @@ -269,15 +162,11 @@ const AdministerUnits: NgHybridStateDeclaration = { }, }; - const ViewAllUnits: NgHybridStateDeclaration = { name: 'view-all-units', url: '/view-all-units', - // passes 'mode' as @Input to the component resolve: { - 'mode': function () { - return 'tutor'; - }, + mode: () => 'tutor', }, views: { main: { @@ -286,14 +175,11 @@ const ViewAllUnits: NgHybridStateDeclaration = { }, data: { pageTitle: 'Teaching Periods', - mode: 'tutor', roleWhitelist: ['Tutor', 'Convenor', 'Admin'], }, }; -/** - * Export the list of states we have created in angular - */ +/* FINAL EXPORTED STATE LIST */ export const doubtfireStates = [ institutionSettingsState, TeachingPeriodsState, @@ -306,4 +192,7 @@ export const doubtfireStates = [ ViewAllProjectsState, ViewAllUnits, AdministerUnits, + + /* STRICT MIGRATION STATE */ + unitsIndexState, ]; diff --git a/src/app/projects/states/index/index.coffee b/src/app/projects/states/index/index.coffee index 8a0f4dfde8..c1c1cca96f 100644 --- a/src/app/projects/states/index/index.coffee +++ b/src/app/projects/states/index/index.coffee @@ -10,7 +10,8 @@ angular.module('doubtfire.projects.states.index', []) views: main: controller: "ProjectsIndexStateCtrl" - templateUrl: "units/states/index/index.tpl.html" # We can re-use unit's index here + template: "" # Unit index is now handled by Angular state units/index + data: pageTitle: "_Home_" roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'] diff --git a/src/app/units/states/index/index.ajs.module.ts b/src/app/units/states/index/index.ajs.module.ts new file mode 100644 index 0000000000..33164abc2c --- /dev/null +++ b/src/app/units/states/index/index.ajs.module.ts @@ -0,0 +1,9 @@ +import angular from 'angular'; + +/** + + * + * i have kept the module name so AngularJS dependency loading does not crash, + * but the actual state is now defined in Angular (see index.state.ts). + */ +export const UnitsIndexAjsModule = angular.module('doubtfire.units.states.index', []); diff --git a/src/app/units/states/index/index.component.html b/src/app/units/states/index/index.component.html index 7ed77ff8da..997583f46a 100644 --- a/src/app/units/states/index/index.component.html +++ b/src/app/units/states/index/index.component.html @@ -1,4 +1,4 @@ - +

Loading unit details...

diff --git a/src/app/units/states/index/index.component.scss b/src/app/units/states/index/index.component.scss new file mode 100644 index 0000000000..4ff4cc7b23 --- /dev/null +++ b/src/app/units/states/index/index.component.scss @@ -0,0 +1,4 @@ +.large-notice-block { + text-align: center; + padding: 2rem; +} diff --git a/src/app/units/states/index/index.component.spec.ts b/src/app/units/states/index/index.component.spec.ts deleted file mode 100644 index 2e2d8eabd8..0000000000 --- a/src/app/units/states/index/index.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { IndexComponent } from './index.component'; - -describe('IndexComponent', () => { - let component: IndexComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [IndexComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(IndexComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/units/states/index/index.component.ts b/src/app/units/states/index/index.component.ts index a19dbc24e1..3c79fcc01b 100644 --- a/src/app/units/states/index/index.component.ts +++ b/src/app/units/states/index/index.component.ts @@ -1,89 +1,31 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {Component, Inject, OnInit} from '@angular/core'; +import {Component, OnInit} from '@angular/core'; +import {StateService, UIRouterGlobals} from '@uirouter/angular'; @Component({ selector: 'f-units-index', templateUrl: './index.component.html', + styleUrls: ['./index.component.scss'], }) export class IndexComponent implements OnInit { - // Variables that replace $scope unitRole: any; unit: any; unitId!: number; project: any; constructor( - @Inject('$state') private $state: any, - @Inject('$stateParams') private $stateParams: any, - @Inject('newUnitService') private newUnitService: any, - @Inject('newProjectService') private newProjectService: any, - @Inject('listenerService') private listenerService: any, - @Inject('GlobalStateService') private globalStateService: any, - @Inject('newUserService') private newUserService: any, - @Inject('alertService') private alertService: any, + private state: StateService, + private globals: UIRouterGlobals, ) {} ngOnInit(): void { - // Get unitId from URL params - this.unitId = +this.$stateParams.unitId; + this.unitId = +this.globals.params['unitId']; - // If no unitId → redirect to home if (!this.unitId) { - this.$state.go('home'); + this.state.go('home'); return; } - // Wait for global state to load - this.globalStateService.onLoad(() => { - // Find the role for this unit - this.unitRole = - this.globalStateService.loadedUnitRoles.currentValues.find( - (unitRole: any) => unitRole.unit.id === this.unitId, - ); - // Admin / Auditor fallback - if ( - !this.unitRole && - (this.newUserService.currentUser.role === 'Admin' || - this.newUserService.currentUser.role === 'Auditor') - ) { - this.unitRole = this.newUserService.adminOrAuditorRoleFor( - this.newUserService.currentUser.role, - this.unitId, - this.newUserService.currentUser, - ); - } - - // If still no role, redirect to home - if (!this.unitRole) { - this.$state.go('home'); - return; - } - - // Set the app to "UNIT view" - this.globalStateService.setView('UNIT', this.unitRole); - - // Load the unit and students - this.newUnitService.get(this.unitId).subscribe({ - next: (unit: any) => { - this.newProjectService.loadStudents(unit).subscribe({ - next: (_students: any) => { - this.unit = unit; - }, - error: (err: any) => { - this.alertService.error( - 'Error loading students: ' + err, - 8000, - ); - setTimeout(() => this.$state.go('home'), 5000); - }, - }); - }, - error: (err: any) => { - this.alertService.error('Error loading unit: ' + err, 8000); - setTimeout(() => this.$state.go('home'), 5000); - }, - }); - }); } } diff --git a/src/app/units/states/index/index.state.ts b/src/app/units/states/index/index.state.ts new file mode 100644 index 0000000000..2a959e16ba --- /dev/null +++ b/src/app/units/states/index/index.state.ts @@ -0,0 +1,21 @@ +import {NgHybridStateDeclaration} from '@uirouter/angular-hybrid'; +import {IndexComponent} from './index.component'; + +/** + * STRICT Angular replacement for: + * src/app/units/states/index/index.coffee + */ +export const unitsIndexState: NgHybridStateDeclaration = { + name: 'units/index', + url: '/units/:unitId', + abstract: true, + views: { + main: { + component: IndexComponent, + }, + }, + data: { + pageTitle: '_Home_', + roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'], + }, +}; diff --git a/src/app/units/states/index/index.tpl.html b/src/app/units/states/index/index.tpl.html deleted file mode 100644 index 3b286bc891..0000000000 --- a/src/app/units/states/index/index.tpl.html +++ /dev/null @@ -1 +0,0 @@ - From 0c075da0f7576c1d8363c6dc8143be0384aa4973 Mon Sep 17 00:00:00 2001 From: jeffy Date: Tue, 6 Jan 2026 17:58:03 +1100 Subject: [PATCH 6/6] refactor: finalize units index angular component --- src/app/units/states/index/index.component.ts | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/app/units/states/index/index.component.ts b/src/app/units/states/index/index.component.ts index 3c79fcc01b..d37f9d1d1d 100644 --- a/src/app/units/states/index/index.component.ts +++ b/src/app/units/states/index/index.component.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {Component, OnInit} from '@angular/core'; +import {Component, Inject, OnInit} from '@angular/core'; import {StateService, UIRouterGlobals} from '@uirouter/angular'; @Component({ @@ -16,16 +16,73 @@ export class IndexComponent implements OnInit { constructor( private state: StateService, private globals: UIRouterGlobals, + + // These are still AngularJS services in the hybrid app, so inject by string token: + @Inject('GlobalStateService') private globalStateService: any, + @Inject('newUserService') private newUserService: any, + @Inject('newUnitService') private newUnitService: any, + @Inject('newProjectService') private newProjectService: any, + @Inject('alertService') private alertService: any, ) {} ngOnInit(): void { + // Get unitId from URL params this.unitId = +this.globals.params['unitId']; + // If no unitId → redirect to home if (!this.unitId) { this.state.go('home'); return; } + // Wait for global state to load (same behaviour as old Coffee/AngularJS) + this.globalStateService.onLoad(() => { + // Find the role for this unit + this.unitRole = + this.globalStateService.loadedUnitRoles?.currentValues?.find( + (unitRole: any) => unitRole?.unit?.id === this.unitId, + ); + + // Admin / Auditor fallback + if ( + !this.unitRole && + (this.newUserService?.currentUser?.role === 'Admin' || + this.newUserService?.currentUser?.role === 'Auditor') + ) { + this.unitRole = this.newUserService.adminOrAuditorRoleFor( + this.newUserService.currentUser.role, + this.unitId, + this.newUserService.currentUser, + ); + } + + // If still no role, redirect to home + if (!this.unitRole) { + this.state.go('home'); + return; + } + + // Set the app to "UNIT view" + this.globalStateService.setView('UNIT', this.unitRole); + // Load the unit and students (same as old behaviour) + this.newUnitService.get(this.unitId).subscribe({ + next: (unit: any) => { + this.newProjectService.loadStudents(unit).subscribe({ + next: () => { + this.unit = unit; + }, + error: (err: any) => { + this.alertService.error('Error loading students: ' + err, 8000); + setTimeout(() => this.state.go('home'), 5000); + }, + }); + }, + error: (err: any) => { + this.alertService.error('Error loading unit: ' + err, 8000); + setTimeout(() => this.state.go('home'), 5000); + }, + }); + }); } }