From bcb751aa6b5f350f2782649b3296d6000069061d Mon Sep 17 00:00:00 2001 From: Rhys Lewis <88314925+RLCorp@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:00:38 +0100 Subject: [PATCH] mes-examinerRecordsLocationListBugFix (#1677) * Fixed a problem where the limited details functionality on the location list would not work properly * removed unnecessary brackets * Changed typing on variables * Moved category and location logic to a new function and included unit tests * Replaced instances of omitting a value from an interface with a new interface that extends from the previosu * optimised loops * Removed remaining omit * removed unneeded comment --- .../__tests__/examiner-records.page.spec.ts | 209 ++++++++++++++--- .../examiner-reports-card.ts | 13 +- .../examiner-records/examiner-records.page.ts | 221 ++++++++++-------- .../examiner-records.selector.ts | 32 +-- .../examiner-records/examiner-records.ts | 4 +- src/components/common/chart/chart.ts | 4 +- 6 files changed, 337 insertions(+), 146 deletions(-) diff --git a/src/app/pages/examiner-records/__tests__/examiner-records.page.spec.ts b/src/app/pages/examiner-records/__tests__/examiner-records.page.spec.ts index 5cb2be72b..88a5fcbcb 100644 --- a/src/app/pages/examiner-records/__tests__/examiner-records.page.spec.ts +++ b/src/app/pages/examiner-records/__tests__/examiner-records.page.spec.ts @@ -25,7 +25,10 @@ import { DASHBOARD_PAGE } from '@pages/page-names.constants'; import { ScreenOrientation } from '@capawesome/capacitor-screen-orientation'; import { ScrollDetail } from '@ionic/core'; import moment from 'moment'; -import { selectCachedExaminerRecords, selectLastCachedDate } from '@store/examiner-records/examiner-records.selectors'; +import { + selectCachedExaminerRecords, + selectLastCachedDate, +} from '@store/examiner-records/examiner-records.selectors'; import { ExaminerRecordModel } from '@dvsa/mes-microservice-common/domain/examiner-records'; import { DateRange } from '@shared/helpers/date-time'; @@ -47,8 +50,8 @@ describe('ExaminerRecordsPage', () => { circuits$: of([]), locationList$: of([]), categoryList$: of([]), - emergencyStops$: of([]) - } + emergencyStops$: of([]), + }; const mockTests: ExaminerRecordModel[] = [ { testCategory: TestCategory.B, @@ -240,11 +243,51 @@ describe('ExaminerRecordsPage', () => { }); describe('ngOnInit', () => { - it('should call setFilterLists', () => { + it('should initialize testResults and update testSubject$', async () => { + spyOn(component, 'removeDuplicatesAndSort').and.returnValue(mockTests); + spyOn(component.testSubject$, 'next'); + + await component.ngOnInit(); + + expect(component.removeDuplicatesAndSort).toHaveBeenCalledWith(component.getLocalResults()); + expect(component.testSubject$.next).toHaveBeenCalledWith(mockTests); + }); + + it('should set default date filter', async () => { + spyOn(component, 'handleDateFilter'); + + await component.ngOnInit(); + + expect(component.handleDateFilter).toHaveBeenCalledWith( + { + detail: { value: component.defaultDate }, + } as CustomEvent); + }); + + it('should set categorySelectPristine to false if categorySubject$ has value', async () => { + component.categorySubject$.next(TestCategory.B); + + await component.ngOnInit(); + + expect(component.categorySelectPristine).toBeFalse(); + }); + + it('should set locationSelectPristine to false if locationSubject$ has value', async () => { + component.locationSubject$.next(1); + + await component.ngOnInit(); + + expect(component.locationSelectPristine).toBeFalse(); + }); + + it('should set location filter and fetch online records', async () => { spyOn(component, 'setLocationFilter'); + spyOn(component, 'getOnlineRecords'); + + await component.ngOnInit(); - component.ngOnInit(); expect(component.setLocationFilter).toHaveBeenCalled(); + expect(component.getOnlineRecords).toHaveBeenCalled(); }); }); @@ -262,6 +305,116 @@ describe('ExaminerRecordsPage', () => { }); }); + describe('setupCategorySelectList', () => { + it('should add every completed category to categoryFilterOptions', () => { + const categories = [ + { item: TestCategory.B, count: 1 }, + { item: TestCategory.C, count: 2 }, + ]; + component.setupCategorySelectList(categories); + expect(component.categoryFilterOptions).toEqual([TestCategory.B, TestCategory.C]); + }); + + it('should set the most common category as the default if current category is not included', () => { + const categories = [ + { item: TestCategory.B, count: 1 }, + { item: TestCategory.C, count: 2 }, + ]; + spyOn(component, 'setDefault').and.returnValue(categories[1]); + spyOn(component, 'handleCategoryFilter'); + + component.categorySubject$.next(TestCategory.A); + component.setupCategorySelectList(categories); + expect(component.categoryPlaceholder).toEqual(TestCategory.C); + expect(component.handleCategoryFilter).toHaveBeenCalledWith(TestCategory.C); + }); + + it('should call changeEligibleTests if current category is included in categoryFilterOptions', () => { + const categories = [ + { item: TestCategory.B, count: 1 }, + { item: TestCategory.C, count: 2 }, + ]; + component.categorySubject$.next(TestCategory.B); + spyOn(component, 'changeEligibleTests'); + component.setupCategorySelectList(categories); + expect(component.changeEligibleTests).toHaveBeenCalled(); + }); + + it('should set categorySelectPristine to true if most common category is set as default', () => { + const categories = [ + { item: TestCategory.B, count: 1 }, + { item: TestCategory.C, count: 2 }, + ]; + spyOn(component, 'setDefault').and.returnValue(categories[1]); + component.categorySubject$.next(TestCategory.A); + component.setupCategorySelectList(categories); + expect(component.categorySelectPristine).toBeTrue(); + }); + + it('should not set categorySelectPristine if current category is included in categoryFilterOptions', () => { + component.categorySelectPristine = false; + const categories = [ + { item: TestCategory.B, count: 1 }, + { item: TestCategory.C, count: 2 }, + ]; + component.categorySubject$.next(TestCategory.B); + spyOn(component, 'changeEligibleTests'); + component.setupCategorySelectList(categories); + expect(component.categorySelectPristine).toEqual(false); + }); + }); + + describe('setupLocationSelectList', () => { + it('should add every visited location to locationFilterOptions', () => { + const locations = [ + { item: { centreName: 'Centre 1', centreId: 1, costCode: 'X1' }, count: 1 }, + { item: { centreName: 'Centre 2', centreId: 2, costCode: 'X2' }, count: 2 }, + ]; + component.setupLocationSelectList(locations); + expect(component.locationFilterOptions).toEqual([ + { centreName: 'Centre 1', centreId: 1, costCode: 'X1' }, + { centreName: 'Centre 2', centreId: 2, costCode: 'X2' }, + ]); + }); + + it('should display cost code or centre id if centre name is not available', () => { + const locations = [ + { item: { centreName: null, centreId: 1, costCode: 'X1' }, count: 1 }, + { item: { centreName: null, centreId: 2, costCode: null }, count: 2 }, + ]; + component.setupLocationSelectList(locations); + expect(component.locationFilterOptions).toEqual([ + { centreName: 'Limited details - X1', centreId: 1, costCode: 'X1' }, + { centreName: 'Limited details - 2', centreId: 2, costCode: null }, + ]); + }); + + it('should set the most common location as the default if current location is not included', () => { + const locations = [ + { item: { centreName: 'Centre 1', centreId: 1, costCode: 'X1' }, count: 1 }, + { item: { centreName: 'Centre 2', centreId: 2, costCode: 'X2' }, count: 2 }, + ]; + spyOn(component, 'setDefault').and.returnValue(locations[1]); + spyOn(component, 'handleLocationFilter'); + + component.locationSubject$.next(3); + component.setupLocationSelectList(locations); + expect(component.locationPlaceholder).toEqual('Centre 2'); + expect(component.handleLocationFilter).toHaveBeenCalledWith(locations[1].item); + }); + + it('should set locationPlaceholder to an empty string if locations array is empty', () => { + const locations = []; + spyOn(component, 'handleLocationFilter'); + + component.setupLocationSelectList(locations); + expect(component.locationPlaceholder).toEqual(''); + expect(component.handleLocationFilter).toHaveBeenCalledWith( + { centreId: null, centreName: '', costCode: '' } + ); + }); + }); + describe('setLocationFilter', () => { it('should set locationFilterOptions to the item property of each object in locationList$', () => { spyOn(component, 'ngOnInit'); @@ -278,7 +431,7 @@ describe('ExaminerRecordsPage', () => { ]); }); it('should set locationPlaceholder to the centreName property ' + - 'of the object in the location array with the highest count', () => { + 'of the object in the location array with the highest count', () => { spyOn(component, 'ngOnInit'); component.locationFilterOptions = null; component.pageState.locationList$ = of([ @@ -289,7 +442,7 @@ describe('ExaminerRecordsPage', () => { expect(component.locationPlaceholder).toEqual('2'); }); it('should call handleLocationFilter with the item of ' + - 'the object in the location array with the highest count', () => { + 'the object in the location array with the highest count', () => { spyOn(component, 'ngOnInit'); spyOn(component, 'handleLocationFilter'); @@ -300,7 +453,9 @@ describe('ExaminerRecordsPage', () => { { item: { centreName: '2', centreId: 2, costCode: 'X2' }, count: 2 }, ]); component.setLocationFilter(); - expect(component.handleLocationFilter).toHaveBeenCalledWith({ centreName: '2', centreId: 2, costCode: 'X2' }); + expect(component.handleLocationFilter).toHaveBeenCalledWith( + { centreName: '2', centreId: 2, costCode: 'X2' } + ); }); }); @@ -320,9 +475,9 @@ describe('ExaminerRecordsPage', () => { { detail: { value: - { - display: '1', - }, + { + display: '1', + }, }, } as CustomEvent, ); @@ -333,10 +488,10 @@ describe('ExaminerRecordsPage', () => { { detail: { value: - { - display: '1', - val: 'today', - }, + { + display: '1', + val: 'today', + }, }, } as CustomEvent, ); @@ -350,9 +505,9 @@ describe('ExaminerRecordsPage', () => { { detail: { value: - { - val: '1', - }, + { + val: '1', + }, }, } as CustomEvent, ); @@ -385,8 +540,8 @@ describe('ExaminerRecordsPage', () => { }); describe('getOnlineRecords', () => { - // eslint-disable-next-line max-len - it('should dispatch LoadingExaminerRecords and GetExaminerRecords actions when cached records are not available or last cached date is different', () => { + it('should dispatch LoadingExaminerRecords and GetExaminerRecords actions when ' + + 'cached records are not available or last cached date is different', () => { store$.overrideSelector(selectCachedExaminerRecords, null); store$.overrideSelector(selectLastCachedDate, 'some other date'); @@ -580,8 +735,8 @@ describe('ExaminerRecordsPage', () => { describe('cardClicked', () => { it('should dispatch the store', () => { - component.cardClicked({isExpanded: false, title: 'test'}); - expect(component.store$.dispatch).toHaveBeenCalledWith(ClickDataCard({isExpanded: false, title: 'test'})); + component.cardClicked({ isExpanded: false, title: 'test' }); + expect(component.store$.dispatch).toHaveBeenCalledWith(ClickDataCard({ isExpanded: false, title: 'test' })); }); }); @@ -599,7 +754,7 @@ describe('ExaminerRecordsPage', () => { emergencyStops: [], circuits: [], locationList: [], - categoryList: [] + categoryList: [], }; expect(component.displayNoDataCard(emptyData)).toBeTrue(); }); @@ -616,8 +771,8 @@ describe('ExaminerRecordsPage', () => { testCount: 1, emergencyStops: [], circuits: [], - locationList: [{item: {centreName: 'Test Centre 1', centreId: 1, costCode: 'TC1'}, count: 1}], - categoryList: [{item: TestCategory.B, count: 1}] + locationList: [{ item: { centreName: 'Test Centre 1', centreId: 1, costCode: 'TC1' }, count: 1 }], + categoryList: [{ item: TestCategory.B, count: 1 }], }; spyOn(component, 'getTotal').and.returnValue(1); @@ -637,7 +792,7 @@ describe('ExaminerRecordsPage', () => { emergencyStops: [], circuits: [], locationList: [], - categoryList: [] + categoryList: [], }; expect(component.displayNoDataCard(data)).toBeTrue(); }); @@ -670,7 +825,7 @@ describe('ExaminerRecordsPage', () => { const expectedText = 'Displaying 2 Category C' + ' tests, from 01/02/2021 to 28/02/2021' + - '
at Test Centre 2' + '
at Test Centre 2'; expect(component.getLabelText()).toEqual(expectedText); }); diff --git a/src/app/pages/examiner-records/components/examiner-reports-card/examiner-reports-card.ts b/src/app/pages/examiner-records/components/examiner-reports-card/examiner-reports-card.ts index 8206d9222..e79754b5e 100644 --- a/src/app/pages/examiner-records/components/examiner-reports-card/examiner-reports-card.ts +++ b/src/app/pages/examiner-records/components/examiner-reports-card/examiner-reports-card.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { AccessibilityService } from '@providers/accessibility/accessibility.service'; -import { ExaminerRecordData } from '@pages/examiner-records/examiner-records.selector'; +import { ExaminerRecordDataWithPercentage } from '@pages/examiner-records/examiner-records.selector'; import { ChartType } from 'ng-apexcharts'; export interface ExaminerReportsCardClick { @@ -20,7 +20,7 @@ export class ExaminerReportsCard { onCardClick: EventEmitter = new EventEmitter(); @Input() - passedData: ExaminerRecordData[] = null; + passedData: ExaminerRecordDataWithPercentage[] = null; @Input() chartID: string = null; @Input() @@ -87,11 +87,11 @@ export class ExaminerReportsCard { * The `count` property is converted to a number before summing. * * @template T - The type of the data contained in the `ExaminerRecordData` objects. - * @param {ExaminerRecordData[]} value - The array of `ExaminerRecordData` objects to be totaled. + * @param {ExaminerRecordDataWithPercentage[]} value - The array of `ExaminerRecordData` objects to be totaled. * @returns {number} The total count of all `ExaminerRecordData` objects. */ getTotal = ( - value: ExaminerRecordData[], + value: ExaminerRecordDataWithPercentage[], ): number => value.reduce((total, val) => total + Number(val.count), 0); /** @@ -102,11 +102,12 @@ export class ExaminerReportsCard { * containing an empty array. * * @template T - The type of the data contained in the `ExaminerRecordData` objects. - * @param {ExaminerRecordData[]} examinerRecordData - The array of `ExaminerRecordData` objects to be formatted. + * @param {ExaminerRecordDataWithPercentage[]} examinerRecordData - The array of `ExaminerRecordData` objects + * to be formatted. * @returns {T[][]} A two-dimensional array where each inner array contains the values of an `ExaminerRecordData` * object. */ - filterDataForGrid(examinerRecordData: ExaminerRecordData[]): T[][] { + filterDataForGrid(examinerRecordData: ExaminerRecordDataWithPercentage[]): T[][] { if (!!examinerRecordData && examinerRecordData.length > 0) { return examinerRecordData.map((obj) => Object.values(obj) as T[]); } diff --git a/src/app/pages/examiner-records/examiner-records.page.ts b/src/app/pages/examiner-records/examiner-records.page.ts index 69a0dbf66..0f0803a4c 100644 --- a/src/app/pages/examiner-records/examiner-records.page.ts +++ b/src/app/pages/examiner-records/examiner-records.page.ts @@ -17,6 +17,7 @@ import { } from '@pages/examiner-records/examiner-records.actions'; import { ExaminerRecordData, + ExaminerRecordDataWithPercentage, getBalanceQuestions, getCategories, getCircuits, @@ -64,35 +65,35 @@ import { import { selectEmployeeId } from '@store/app-info/app-info.selectors'; export interface ExaminerRecordsPageStateData { - routeGrid: ExaminerRecordData[], - manoeuvresGrid: ExaminerRecordData[], - showMeQuestionsGrid: ExaminerRecordData[], - independentDrivingGrid: ExaminerRecordData[], - tellMeQuestionsGrid: ExaminerRecordData[], - safetyGrid: ExaminerRecordData[], - balanceGrid: ExaminerRecordData[], + routeGrid: ExaminerRecordDataWithPercentage[], + manoeuvresGrid: ExaminerRecordDataWithPercentage[], + showMeQuestionsGrid: ExaminerRecordDataWithPercentage[], + independentDrivingGrid: ExaminerRecordDataWithPercentage[], + tellMeQuestionsGrid: ExaminerRecordDataWithPercentage[], + safetyGrid: ExaminerRecordDataWithPercentage[], + balanceGrid: ExaminerRecordDataWithPercentage[], testCount: number, - emergencyStops: ExaminerRecordData[], - circuits: ExaminerRecordData[], - locationList: { item: TestCentre, count: number }[], - categoryList: { item: TestCategory, count: number }[] + emergencyStops: ExaminerRecordDataWithPercentage[], + circuits: ExaminerRecordDataWithPercentage[], + locationList: ExaminerRecordData[], + categoryList: ExaminerRecordData[] } interface ExaminerRecordsState { cachedRecords$: Observable; isLoadingRecords$: Observable; - routeNumbers$: Observable[]>; - manoeuvres$: Observable[]>; - showMeQuestions$: Observable[]>; - tellMeQuestions$: Observable[]>; - safetyQuestions$: Observable[]>; - balanceQuestions$: Observable[]>; - independentDriving$: Observable[]>; + routeNumbers$: Observable[]>; + manoeuvres$: Observable[]>; + showMeQuestions$: Observable[]>; + tellMeQuestions$: Observable[]>; + safetyQuestions$: Observable[]>; + balanceQuestions$: Observable[]>; + independentDriving$: Observable[]>; testCount$: Observable; - locationList$: Observable<{ item: TestCentre, count: number }[]>; - categoryList$: Observable<{ item: TestCategory, count: number }[]>; - emergencyStops$: Observable[]>; - circuits$: Observable[]>; + locationList$: Observable[]>; + categoryList$: Observable[]>; + emergencyStops$: Observable[]>; + circuits$: Observable[]>; } @Component({ @@ -104,14 +105,14 @@ export class ExaminerRecordsPage implements OnInit { merged$: Observable; form: UntypedFormGroup = new UntypedFormGroup({}); - testSubject$ = new BehaviorSubject(null); - testsInRangeSubject$ = new BehaviorSubject(null); - eligTestSubject$ = new BehaviorSubject(null); - rangeSubject$ = new BehaviorSubject(null); - locationSubject$ = new BehaviorSubject(null); - categorySubject$ = new BehaviorSubject(null); + testSubject$: BehaviorSubject = new BehaviorSubject(null); + testsInRangeSubject$: BehaviorSubject = new BehaviorSubject(null); + eligTestSubject$: BehaviorSubject = new BehaviorSubject(null); + rangeSubject$: BehaviorSubject = new BehaviorSubject(null); + locationSubject$: BehaviorSubject = new BehaviorSubject(null); + categorySubject$: BehaviorSubject = new BehaviorSubject(null); pageState: ExaminerRecordsState; - hideMainContent = false; + hideMainContent: boolean = false; colourOption: ColourScheme = this.getColour(this.store$.selectSignal(selectColourScheme)()); categoryPlaceholder: string; locationPlaceholder: string; @@ -295,7 +296,7 @@ export class ExaminerRecordsPage implements OnInit { * @returns {Observable} An observable that emits the result of applying the function `fn` to the eligible tests, * date range, category, and location. */ - private getLocationsByParameters = (fn: ( + getLocationsByParameters = (fn: ( tests: ExaminerRecordModel[], range: DateRange, category: string, @@ -327,7 +328,10 @@ export class ExaminerRecordsPage implements OnInit { * @param {ExaminerRecordModel[]} cachedExaminerRecords - The cached online records to be merged. * @returns {ExaminerRecordModel[]} The merged array of local and cached online records. */ - mergeWithOnlineResults(localRecords: ExaminerRecordModel[], cachedExaminerRecords: ExaminerRecordModel[]) { + mergeWithOnlineResults( + localRecords: ExaminerRecordModel[], + cachedExaminerRecords: ExaminerRecordModel[] + ): ExaminerRecordModel[] { this.cachedExaminerRecords = cachedExaminerRecords; if (!this.cachedExaminerRecords) { this.store$.dispatch(DisplayPartialBanner()) @@ -340,7 +344,7 @@ export class ExaminerRecordsPage implements OnInit { } /** - * Removes duplicate entries from an array and sorts its content by the most recent date. + * Removes duplicate entries from an array and sorts the content by the most recent date. * * This method filters out duplicate entries in the provided array based on the `appRef` property * and then sorts the remaining entries in descending order by the `startDate` property. @@ -398,6 +402,85 @@ export class ExaminerRecordsPage implements OnInit { return result; } + /** + * Sets up the category select list with the provided values. + * + * This method performs the following actions: + * 1. Initializes the `categoryFilterOptions` array. + * 2. Adds every completed category to the `categoryFilterOptions` array. + * 3. Checks if the current category is included in the `categoryFilterOptions`. + * - If not, finds the most common category and sets it as the default. + * - If yes, calls `changeEligibleTests` to update the eligible tests. + * + * @param {ExaminerRecordData[]} categories - The array of category data to + * set up the select list. + */ + setupCategorySelectList(categories: ExaminerRecordData[]) { + this.categoryFilterOptions = []; + + //add every completed category to category array + this.categoryFilterOptions = categories.map((val) => val.item); + + if (!this.categoryFilterOptions.includes(this.categorySubject$.value)) { + //find most common category and set it as the default + const mostUsed: ExaminerRecordData = this.setDefault(categories); + + if (!!mostUsed) { + this.categoryPlaceholder = mostUsed.item; + this.handleCategoryFilter(mostUsed.item); + this.categorySelectPristine = true; + } + } else { + this.changeEligibleTests(); + } + } + + /** + * Sets up the location select list with the provided values. + * + * This method performs the following actions: + * 1. Initializes the `locationFilterOptions` array. + * 2. Adds every visited location to the `locationFilterOptions` array. + * 3. Checks if the current location is included in the `locationFilterOptions`. + * - If not, finds the most common location and sets it as the default. + * + * @param {ExaminerRecordData[]} locations - The array of location data to + * set up the select list. + */ + setupLocationSelectList(locations: ExaminerRecordData[]) { + this.locationFilterOptions = []; + + //add every visited location to location array + locations.forEach((val: ExaminerRecordData) => { + if (!val.item?.centreName) { + // Should there be no centre name available, display cost code or centre id, + // depending on whether cost code is available + val.item = { + ...val.item, + centreName: `Limited details - ${ + !!val.item.costCode ? val.item.costCode : val.item.centreId.toString() + }` + } + } + this.locationFilterOptions.push(val.item); + }); + + if (!this.locationFilterOptions.map(({ centreId }) => centreId) + .includes(this.locationSubject$.value)) { + //find most common location and set it as the default + const mostUsed: ExaminerRecordData = this.setDefault(locations); + if (!!mostUsed) { + this.locationPlaceholder = mostUsed.item.centreName; + this.handleLocationFilter(mostUsed.item); + this.locationSelectPristine = true; + } else if (locations.length === 0) { + this.locationPlaceholder = ''; + this.handleLocationFilter({ centreId: null, centreName: '', costCode: '' }); + this.locationSelectPristine = true; + } + } + } + /** * Initializes the component and sets up the necessary data and subscriptions. * @@ -438,61 +521,14 @@ export class ExaminerRecordsPage implements OnInit { circuits$: this.getTestsByParameters(getCircuits), locationList$: this.getLocationsByParameters(getLocations) .pipe( - tap((value) => { - this.locationFilterOptions = []; - - //add every visited location to location array - value.forEach((val) => { - if (!(val.item.centreName)) { - // Should there be no centre name available, display cost code or centre id, - // depending on whether cost code is available - val.item.centreName = `Limited details - ${ - !!val.item.costCode ? val.item.costCode : val.item.centreId.toString() - }` - } - this.locationFilterOptions.push(val.item); - }); - - - if (!this.locationFilterOptions.map(({ centreId }) => centreId) - .includes(this.locationSubject$.value)) { - //find most common location and set it as the default - const mostUsed = this.setDefault(value); - if (!!mostUsed) { - this.locationPlaceholder = mostUsed.item.centreName; - this.handleLocationFilter(mostUsed.item); - this.locationSelectPristine = true; - } else if (value.length === 0) { - this.locationPlaceholder = ''; - this.handleLocationFilter({ centreId: null, centreName: '', costCode: '' }); - this.locationSelectPristine = true; - } - } - + tap((value: ExaminerRecordData[]) => { + this.setupLocationSelectList(value); }), ), categoryList$: this.getCategoriesByParameters(getCategories) .pipe( - tap((value: Omit, 'percentage'>[]) => { - this.categoryFilterOptions = []; - - //add every completed category to category array - value.forEach((val) => { - this.categoryFilterOptions.push(val.item); - }); - - if (!this.categoryFilterOptions.includes(this.categorySubject$.value)) { - //find most common category and set it as the default - const mostUsed = this.setDefault(value); - - if (!!mostUsed) { - this.categoryPlaceholder = mostUsed.item; - this.handleCategoryFilter(mostUsed.item); - this.categorySelectPristine = true; - } - } else { - this.changeEligibleTests(); - } + tap((value: ExaminerRecordData[]) => { + this.setupCategorySelectList(value) }), ), emergencyStops$: this.getTestsByParameters(getStartedTestCount) @@ -585,12 +621,11 @@ export class ExaminerRecordsPage implements OnInit { let mostUsed = null; this.pageState.locationList$.subscribe(value => { - value.forEach((val) => { - this.locationFilterOptions.push(val.item); - }); + //populate location filter options + this.locationFilterOptions = value.map((val) => val.item); + //find most common location and set it as the default mostUsed = this.setDefault(value); - }) - .unsubscribe(); + }).unsubscribe(); if (!!mostUsed) { this.locationPlaceholder = mostUsed.item.centreName; @@ -607,11 +642,11 @@ export class ExaminerRecordsPage implements OnInit { * 2. Uses the reduce function to find the element with the highest count. * * @template T The type of the elements in the data array. - * @param {Omit, 'percentage'>[]} data - The array of data elements to search. - * @returns {Omit, 'percentage'> | null} The element with the highest count, or null if the + * @param {ExaminerRecordData[]} data - The array of data elements to search. + * @returns {ExaminerRecordData} The element with the highest count, or null if the * input data is null or empty. */ - setDefault(data: Omit, 'percentage'>[]): Omit, 'percentage'> { + setDefault(data: ExaminerRecordData[]): ExaminerRecordData { if (!data || data?.length === 0) { return null; } @@ -820,11 +855,11 @@ export class ExaminerRecordsPage implements OnInit { * by summing up the `count` property of each object in the array. * * @template T The type of the data in the `ExaminerRecordData` objects. - * @param {ExaminerRecordData[]} value - The array of `ExaminerRecordData` objects. + * @param {ExaminerRecordDataWithPercentage[]} value - The array of `ExaminerRecordData` objects. * @returns {number} The total count of individual instances of data. */ getTotal = ( - value: ExaminerRecordData[], + value: ExaminerRecordDataWithPercentage[], ): number => value.reduce((total, val) => total + Number(val.count), 0); /** diff --git a/src/app/pages/examiner-records/examiner-records.selector.ts b/src/app/pages/examiner-records/examiner-records.selector.ts index 21a36b6f6..74b9ed13b 100644 --- a/src/app/pages/examiner-records/examiner-records.selector.ts +++ b/src/app/pages/examiner-records/examiner-records.selector.ts @@ -10,12 +10,15 @@ import { manoeuvreTypeLabels as manoeuvreTypeLabelsCatBE } from '@shared/constan import { manoeuvreTypeLabels as manoeuvreTypeLabelsCatADI2 } from '@shared/constants/competencies/catadi2-manoeuvres'; import { isAnyOf } from '@shared/helpers/simplifiers'; import { ExaminerRecordModel } from '@dvsa/mes-microservice-common/domain/examiner-records'; -import { ExaminerRecordsRange } from '@providers/examiner-records/examiner-records'; // Generic `T` is the configurable type of the item export interface ExaminerRecordData { item: T; count: number; +} + +// Generic `T` is the configurable type of the item +export interface ExaminerRecordDataWithPercentage extends ExaminerRecordData{ percentage: string; } @@ -57,7 +60,7 @@ export const getIndex = (item: string) => { export const getEligibleTests = ( startedTests: ExaminerRecordModel[], category: TestCategory = null, - range: ExaminerRecordsRange = null, + range: DateRange = null, centreId: number = null, filterByLocation: boolean = true, filterByCategory: boolean = true, @@ -96,14 +99,13 @@ export const getEmergencyStopCount = ( */ export const getLocations = ( startedTests: ExaminerRecordModel[], - range: ExaminerRecordsRange = null, - // Omit is a TS type, to remove a property from an interface -): Omit, 'percentage'>[] => { + range: DateRange = null, +): ExaminerRecordData[] => { if (startedTests) { const data: ExaminerRecordModel[] = getEligibleTests(startedTests, null, range, null) .filter((record) => !!get(record, 'testCentre', null).centreId); - return uniqBy(data.map(({ testCentre }) => { + return uniqBy(data.map(({ testCentre }): ExaminerRecordData => { return { item: testCentre, count: data.filter((val) => val.testCentre.centreId === testCentre.centreId).length, @@ -122,7 +124,7 @@ export const getLocations = ( export const getIndependentDrivingStats = ( startedTests: ExaminerRecordModel[], category: TestCategory, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { //IndependentDriving is not applicable to the following categories, and so we can avoid the entire function if (!startedTests || !category || isAnyOf(category, [ TestCategory.ADI3, TestCategory.SC, @@ -166,7 +168,7 @@ export const getIndependentDrivingStats = ( export const getCircuits = ( startedTests: ExaminerRecordModel[], category: TestCategory, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { //getCircuits is only applicable to the following categories, and so we can avoid the entire function if (!startedTests || !category || !isAnyOf(category, [ TestCategory.EUA1M1, TestCategory.EUA2M1, TestCategory.EUAM1, TestCategory.EUAMM1, @@ -198,7 +200,7 @@ export const getCircuits = ( */ export const getCategories = ( startedTests: ExaminerRecordModel[], - range: ExaminerRecordsRange, + range: DateRange, category: TestCategory, centreId: number, ): { @@ -239,7 +241,7 @@ export const getStartedTestCount = ( */ export const getRouteNumbers = ( startedTests: ExaminerRecordModel[], -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { if (startedTests) { const data = (startedTests) .filter((record: ExaminerRecordModel) => get(record, 'routeNumber', null) !== null); @@ -265,7 +267,7 @@ export const getRouteNumbers = ( export const getSafetyQuestions = ( startedTests: ExaminerRecordModel[], category: TestCategory = null, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { const qp = new QuestionProvider(); if (startedTests) { @@ -306,7 +308,7 @@ export const getSafetyQuestions = ( export const getBalanceQuestions = ( startedTests: ExaminerRecordModel[], category: TestCategory = null, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { const qp = new QuestionProvider(); if (startedTests) { @@ -345,7 +347,7 @@ export const getBalanceQuestions = ( export const getShowMeQuestions = ( startedTests: ExaminerRecordModel[], category: TestCategory = null, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { const qp = new QuestionProvider(); if (startedTests) { @@ -377,7 +379,7 @@ export const getShowMeQuestions = ( export const getTellMeQuestions = ( startedTests: ExaminerRecordModel[], category: TestCategory = null, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { const qp = new QuestionProvider(); if (startedTests) { @@ -423,7 +425,7 @@ export const getManoeuvreTypeLabels = (category: TestCategory, type?: ManoeuvreT export const getManoeuvresUsed = ( startedTests: ExaminerRecordModel[], category: TestCategory = null, -): ExaminerRecordData[] => { +): ExaminerRecordDataWithPercentage[] => { let faultsEncountered: string[] = []; let manoeuvreTypeLabels: string[] = []; if (category) { diff --git a/src/app/providers/examiner-records/examiner-records.ts b/src/app/providers/examiner-records/examiner-records.ts index c9872624b..11ac35bb4 100644 --- a/src/app/providers/examiner-records/examiner-records.ts +++ b/src/app/providers/examiner-records/examiner-records.ts @@ -28,11 +28,9 @@ export const enum ColourEnum { GREYSCALE = 'Greyscale', } -export type ExaminerRecordsRange = DateRange; - export interface SelectableDateRange { display: string; - val: ExaminerRecordsRange; + val: DateRange; } export type DESChartTypes = Extract; @Injectable() diff --git a/src/components/common/chart/chart.ts b/src/components/common/chart/chart.ts index 96e9101b1..e344eb837 100644 --- a/src/components/common/chart/chart.ts +++ b/src/components/common/chart/chart.ts @@ -2,7 +2,7 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/cor import ApexCharts from 'apexcharts'; import { ApexAxisChartSeries, ApexNonAxisChartSeries, ApexOptions, ChartType } from 'ng-apexcharts'; import { isEqual } from 'lodash-es'; -import { ExaminerRecordData } from '@pages/examiner-records/examiner-records.selector'; +import { ExaminerRecordDataWithPercentage } from '@pages/examiner-records/examiner-records.selector'; @Component({ selector: 'chart', @@ -19,7 +19,7 @@ export class ChartComponent implements OnInit, OnChanges { public chartType: ChartType = 'pie'; @Input() - public passedData: ExaminerRecordData[] = null; + public passedData: ExaminerRecordDataWithPercentage[] = null; @Input() public showLegend: boolean = false;