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
+
+
0; else noTasks" class="space-y-2">
+ -
+
+ {{ task.name }} – Status:
+ {{ task.status }}
+
+
+
+
+
+
+ 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