diff --git a/src/app/projects/states/index/index.coffee b/src/app/projects/states/index/index.coffee index d77b2be900..8a0f4dfde8 100644 --- a/src/app/projects/states/index/index.coffee +++ b/src/app/projects/states/index/index.coffee @@ -13,15 +13,16 @@ angular.module('doubtfire.projects.states.index', []) templateUrl: "units/states/index/index.tpl.html" # We can re-use unit's index here data: pageTitle: "_Home_" + roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'] } ) -.controller("ProjectsIndexStateCtrl", ($scope, $rootScope, $state, $stateParams, newProjectService, listenerService, globalStateService) -> +.controller("ProjectsIndexStateCtrl", ($scope, $rootScope, $state, $stateParams, newProjectService, listenerService, GlobalStateService) -> # Error - required projectId is missing! projectId = +$stateParams.projectId return $state.go('home') unless projectId - globalStateService.onLoad () -> + GlobalStateService.onLoad () -> # Load in project newProjectService.get(projectId, { # Ensure that we cache queries here... so that we get any projects we are in @@ -38,7 +39,7 @@ angular.module('doubtfire.projects.states.index', []) $scope.project = project $scope.unit = project.unit if project.unit.taskDefinitions.length > 0 && project.tasks.length == project.unit.taskDefinitions.length - globalStateService.setView('PROJECT', $scope.project) + GlobalStateService.setView('PROJECT', $scope.project) # Go home if no project was found return $state.go('home') unless project? diff --git a/src/app/projects/states/index/index.component.html b/src/app/projects/states/index/index.component.html new file mode 100644 index 0000000000..0a6492e0fa --- /dev/null +++ b/src/app/projects/states/index/index.component.html @@ -0,0 +1,37 @@ +
+ +

Projects Index

+ + +
+

+ Project: {{ project.name }} +

+

+ Unit: {{ unit?.name }} +

+ + +

Tasks

+ + + + + +

No tasks found for this project.

+
+
+ + + +
Loading project...
+
Error loading project.
+
\ No newline at end of file diff --git a/src/app/projects/states/index/index.component.ts b/src/app/projects/states/index/index.component.ts new file mode 100644 index 0000000000..0a077f73c9 --- /dev/null +++ b/src/app/projects/states/index/index.component.ts @@ -0,0 +1,113 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router, ParamMap } from '@angular/router'; +import { Subject, EMPTY } from 'rxjs'; +import { switchMap, takeUntil } from 'rxjs/operators'; +import { GlobalStateService } from './global-state.service'; +import { ProjectService } from '../../services/project.service'; +import { ViewType } from '../../common/types/view-type'; + +// ---------------------- +// Added Interfaces +// ---------------------- +interface Unit { + id: number; + name: string; + taskDefinitions?: any[]; +} + +interface Project { + id: number; + name: string; + tasks?: any[]; + unit?: Unit; +} + +@Component({ + selector: 'app-projects-index', + templateUrl: './index.component.html', + styleUrls: ['./index.component.scss'] +}) +export class ProjectsIndexComponent implements OnInit, OnDestroy { + projectId!: number; + project: Project | null = null; + unit: Unit | null = null; + isLoading = true; + hasError = false; + + private destroy$ = new Subject(); + + constructor( + private route: ActivatedRoute, + private router: Router, + private projectService: ProjectService, + private globalStateService: GlobalStateService + ) { } + + ngOnInit(): void { + // Wait for global state to be ready, then react to route changes + this.globalStateService.onLoad(() => { + this.route.paramMap + .pipe( + takeUntil(this.destroy$), + switchMap((params: ParamMap) => { + const idParam = params.get('projectId'); + this.projectId = Number(idParam); + + if (!this.projectId || Number.isNaN(this.projectId)) { + this.hasError = true; + this.isLoading = false; + this.router.navigate(['/home']); + // Use EMPTY instead of new Subject() + return EMPTY; + } + + this.isLoading = true; + this.hasError = false; + + return this.projectService.get(this.projectId, { + cacheBehaviourOnGet: 'cacheQuery', + mappingCompleteCallback: (project: Project) => { + this.unit = project?.unit ?? null; + } + }); + }) + ) + .subscribe({ + next: (project: Project) => { + this.project = project; + + // Guard against null/undefined project + if (!project) { + this.hasError = true; + this.isLoading = false; + this.router.navigate(['/home']); + return; + } + + // Ensure unit is set when tasks match task definitions + if ( + project.unit?.taskDefinitions?.length > 0 && + project.tasks?.length === project.unit.taskDefinitions.length + ) { + this.unit = project.unit; + } + + // Use enum for view type + this.globalStateService.setView(ViewType.PROJECT, this.project); + + this.isLoading = false; + }, + error: () => { + this.isLoading = false; + this.hasError = true; + this.router.navigate(['/home']); + } + }); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} \ No newline at end of file