diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index 9284745988..b5fb34d461 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -224,7 +224,7 @@ 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 { TargetGradeHistoryComponent } from './projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component'; @NgModule({ // Components we declare declarations: [ @@ -325,6 +325,7 @@ import {GradeService} from './common/services/grade.service'; FUsersComponent, FTaskBadgeComponent, FUnitsComponent, + TargetGradeHistoryComponent, ], // Services we provide providers: [ diff --git a/src/app/doubtfire-angularjs.module.ts b/src/app/doubtfire-angularjs.module.ts index 868e381213..92cead1df5 100644 --- a/src/app/doubtfire-angularjs.module.ts +++ b/src/app/doubtfire-angularjs.module.ts @@ -225,6 +225,9 @@ 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 {TargetGradeHistoryComponent} from './projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component'; + export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.config', 'doubtfire.sessions', @@ -464,6 +467,12 @@ DoubtfireAngularJSModule.directive( ); DoubtfireAngularJSModule.directive('newFUnits', downgradeComponent({component: FUnitsComponent})); +DoubtfireAngularJSModule.directive( + 'targetGradeHistory', + downgradeComponent({ + component: TargetGradeHistoryComponent + }) +); // Global configuration // If the user enters a URL that doesn't match any known URL (state), send them to `/home` diff --git a/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.coffee b/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.coffee index 0a82f1ab9a..4c53c74ee0 100644 --- a/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.coffee +++ b/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.coffee @@ -1,19 +1,17 @@ angular.module('doubtfire.projects.states.dashboard.directives.progress-dashboard', []) -# -# Summary dashboard showing some graphs and way to change the -# current target grade -# -.directive('progressDashboard', -> +.directive 'progressDashboard', -> restrict: 'E' templateUrl: 'projects/states/dashboard/directives/progress-dashboard/progress-dashboard.tpl.html' scope: project: '=' onUpdateTargetGrade: '=' - controller: ($scope, $stateParams, newProjectService, gradeService, analyticsService, alertService) -> + controller: ($scope, $stateParams, newProjectService, gradeService, analyticsService, alertService, $http, DoubtfireConstants) -> + # Is the current user a tutor? $scope.tutor = $stateParams.tutor + # Number of tasks completed and remaining - updateTaskCompletionValues = -> + updateTaskCompletionValues = -> completedTasks = $scope.project.numberTasks("complete") $scope.numberOfTasks = completed: completedTasks @@ -25,6 +23,15 @@ angular.module('doubtfire.projects.states.dashboard.directives.progress-dashboar names: gradeService.grades values: gradeService.gradeValues + # Fetch Target Grade History + $scope.targetGradeHistory = [] + + $http.get("#{DoubtfireConstants.API_URL}/projects/#{$scope.project.id}") + .then (response) -> + $scope.targetGradeHistory = response.data.target_grade_histories + .catch (error) -> + alertService.error("Failed to load target grade history", 4000) + $scope.updateTargetGrade = (newGrade) -> $scope.project.targetGrade = newGrade newProjectService.update($scope.project).subscribe( @@ -35,10 +42,21 @@ angular.module('doubtfire.projects.states.dashboard.directives.progress-dashboar updateTaskCompletionValues() $scope.renderTaskStatusPieChart?() $scope.onUpdateTargetGrade?() - analyticsService.event("Student Project View - Progress Dashboard", "Grade Changed", $scope.grades.names[newGrade]) - alertService.success( "Updated target grade successfully", 2000) + analyticsService.event( + "Student Project View - Progress Dashboard", + "Grade Changed", + $scope.grades.names[newGrade] + ) + + # Fetch updated target grade history + $http.get("#{DoubtfireConstants.API_URL}/projects/#{$scope.project.id}") + .then (response) -> + $scope.targetGradeHistory = response.data.target_grade_histories + .catch (error) -> + alertService.error("Failed to reload target grade history", 4000) + + alertService.success("Updated target grade successfully", 2000) - (failure) -> - alertService.error( "Failed to update target grade", 4000) + , (failure) -> + alertService.error("Failed to update target grade", 4000) ) -) diff --git a/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.tpl.html b/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.tpl.html index 53269a2a95..fc37ddf176 100644 --- a/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.tpl.html +++ b/src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.tpl.html @@ -5,54 +5,73 @@

-
-
-
-

Target Grade

-
-
- -
-
-
-
-

Progress Burndown

-
- The burndown chart shows how much work remains for you to achieve your target grade. -
-
-
- -
- -
-
-
-
-
-

Task Statuses

-
- Breakdown summary of each of your task statuses. -
-
-
- - -
-
-
+
+ +
+ +
+
+

Target Grade

+
+
+ +
+
+ + +
+
+

Progress Burndown

+
+ The burndown chart shows how much work remains for you to achieve your target grade. +
+
+
+ +
+ +
+
+ + +
+ +
+
+

Task Statuses

+
+ Breakdown summary of each of your task statuses. +
+
+
+ + +
+
+ + + + + + +
+
- + \ No newline at end of file diff --git a/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.html b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.html new file mode 100644 index 0000000000..4aea714f72 --- /dev/null +++ b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.html @@ -0,0 +1,59 @@ +
+
+

Target Grade Change History

+
+
+
Loading history...
+
+ {{ error }} +
+
+ No grade history available +
+ + + + + + + + + + + + + + + + + +
Changed AtPrevious GradeNew GradeChanged By
{{ history.changed_at | date: 'medium' }}{{ history.previous_grade }}{{ history.new_grade }}{{ history.changed_by?.first_name }} {{ history.changed_by?.last_name }}
+
+ + + +
+
+
diff --git a/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.scss b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.spec.ts b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.spec.ts new file mode 100644 index 0000000000..972bab2ed8 --- /dev/null +++ b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TargetGradeHistoryComponent } from './target-grade-history.component'; + +describe('TargetGradeHistoryComponent', () => { + let component: TargetGradeHistoryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TargetGradeHistoryComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TargetGradeHistoryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.ts b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.ts new file mode 100644 index 0000000000..5bdb384dcf --- /dev/null +++ b/src/app/projects/states/dashboard/directives/progress-dashboard/target-grade-history/target-grade-history.component.ts @@ -0,0 +1,115 @@ +import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { catchError } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { GradeService } from 'src/app/common/services/grade.service'; +import { DoubtfireConstants } from 'src/app/config/constants/doubtfire-constants'; + +interface User { + id: number; + email: string; + first_name: string; + last_name: string; + username: string; + nickname: string; +} + +interface TargetGradeHistory { + id: number; + previous_grade: string | number; + new_grade: string | number; + changed_at: string; + changed_by: User; +} + +@Component({ + selector: 'f-target-grade-history', + templateUrl: './target-grade-history.component.html', + styleUrls: ['./target-grade-history.component.scss'] +}) +export class TargetGradeHistoryComponent implements OnInit, OnChanges { + @Input() projectId!: number; + @Input() targetGrade?: string; + + // Data + targetGradeHistory: TargetGradeHistory[] = []; + paginatedGradeHistory: TargetGradeHistory[] = []; + + // UI State + loading = false; + error: string | null = null; + + currentPage = 1; + itemsPerPage = 10; + totalPages = 1; + + constructor( + private http: HttpClient, + private gradeService: GradeService, + private constants: DoubtfireConstants + ) {} + + ngOnInit(): void { + if (this.projectId) { + this.loadTargetGradeHistory(); + } + } + + + ngOnChanges(changes: SimpleChanges): void { + if (changes.projectId && !changes.projectId.firstChange) { + this.currentPage = 1; + this.loadTargetGradeHistory(); + } + + if (changes.targetGrade && !changes.targetGrade.firstChange) { + this.loadTargetGradeHistory(); + } + } + + + private loadTargetGradeHistory(): void { + if (!this.projectId) { + console.error('No projectId provided to TargetGradeHistoryComponent'); + this.error = 'Project ID is required'; + return; + } + + this.loading = true; + this.error = null; + + const url = `${this.constants.API_URL}/projects/${this.projectId}/target_grade_histories` + + `?page=${this.currentPage}&limit=${this.itemsPerPage}`; + + this.http.get(url).pipe( + catchError((err) => { + console.error('Error fetching target grade history:', err); + this.error = 'Failed to load target grade history'; + return of({ target_grade_histories: [], total_histories: 0 }); + }) + ).subscribe(response => { + const histories = response.target_grade_histories || []; + const totalCount = response.total_histories || 0; + + this.targetGradeHistory = histories.map((h: any) => ({ + ...h, + previous_grade: this.gradeService.grades[h.previous_grade] || h.previous_grade, + new_grade: this.gradeService.grades[h.new_grade] || h.new_grade + })); + + + this.paginatedGradeHistory = this.targetGradeHistory; + this.totalPages = Math.ceil(totalCount / this.itemsPerPage); + + this.loading = false; + }); + } + + + public goToPage(page: number): void { + if (page >= 1 && page <= this.totalPages) { + this.currentPage = page; + this.loadTargetGradeHistory(); + } + } +} \ No newline at end of file