diff --git a/package-lock.json b/package-lock.json index 9363b1afc2..1be11df960 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@angular/router": "^17.2.1", "@azure/msal-angular": "^3.0.13", "@azure/msal-browser": "^3.10.0", - "@dvsa/cvs-type-definitions": "^6.1.0", + "@dvsa/cvs-type-definitions": "^6.3.0", "@ngrx/effects": "^17.1.0", "@ngrx/entity": "^17.1.0", "@ngrx/router-store": "^17.1.0", @@ -3723,9 +3723,9 @@ } }, "node_modules/@dvsa/cvs-type-definitions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@dvsa/cvs-type-definitions/-/cvs-type-definitions-6.1.0.tgz", - "integrity": "sha512-NPS0mvEfRAiwCuLNHdq5Ip+dmx9KJQKX0RkHV6g0lmsDFwPasL+91R716Mgo44O/shBkeT9XsIQq3ZSoA3iOFA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@dvsa/cvs-type-definitions/-/cvs-type-definitions-6.3.0.tgz", + "integrity": "sha512-wWeGVDdfacUnO+aM1a/42k72XJjPwWIT/9Fc94hJATdUeF8b4rd7DefDXHEzlP+7uZr7CwtVn0/bLStJ7n35Tw==", "dependencies": { "ajv": "^8.12.0", "json-schema-deref-sync": "^0.14.0", diff --git a/package.json b/package.json index f125fc4f3c..01bfaf4df4 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@angular/router": "^17.2.1", "@azure/msal-angular": "^3.0.13", "@azure/msal-browser": "^3.10.0", - "@dvsa/cvs-type-definitions": "^6.1.0", + "@dvsa/cvs-type-definitions": "^6.3.0", "@ngrx/effects": "^17.1.0", "@ngrx/entity": "^17.1.0", "@ngrx/router-store": "^17.1.0", diff --git a/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.html b/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.html index 2c6299caad..3d5c835d55 100644 --- a/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.html +++ b/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.html @@ -1,4 +1,10 @@
+

{{ makeAndModel }}

diff --git a/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.spec.ts b/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.spec.ts index 584608d6d5..962cf5989f 100644 --- a/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.spec.ts +++ b/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.spec.ts @@ -1,6 +1,4 @@ -import { - ComponentFixture, fakeAsync, TestBed, -} from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, ActivatedRouteSnapshot, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; @@ -31,6 +29,7 @@ const mockTechRecordService = { validateVrmDoesNotExist: jest.fn(), validateVrmForCherishedTransfer: jest.fn(), checkVrmNotActive: jest.fn(), + getVehicleTypeWithSmallTrl: jest.fn(), }; const mockDynamicFormService = { @@ -170,4 +169,34 @@ describe('TechRecordChangeVrmComponent', () => { ); }); }); + + describe('showWarning', () => { + it('should return true if the vehicle type is a psv', () => { + component.techRecord = { techRecord_vehicleType: 'psv' } as VehiclesOtherThan<'trl'>; + mockTechRecordService.getVehicleTypeWithSmallTrl.mockReturnValue('psv'); + + expect(component.showWarning).toBe(true); + }); + + it('should return true if the vehicle type is a hgv', () => { + component.techRecord = { techRecord_vehicleType: 'hgv' } as VehiclesOtherThan<'trl'>; + mockTechRecordService.getVehicleTypeWithSmallTrl.mockReturnValue('hgv'); + + expect(component.showWarning).toBe(true); + }); + + it('should return false if the vehicle type is not a psv or hgv', () => { + component.techRecord = { techRecord_vehicleType: 'lgv' } as VehiclesOtherThan<'trl'>; + mockTechRecordService.getVehicleTypeWithSmallTrl.mockReturnValue('lgv'); + + expect(component.showWarning).toBe(false); + }); + + it('should default to false if the vehicle type is not present', () => { + component.techRecord = undefined; + mockTechRecordService.getVehicleTypeWithSmallTrl.mockReturnValue(undefined); + + expect(component.showWarning).toBe(false); + }); + }); }); diff --git a/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.ts b/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.ts index a3a23fdfde..90cf3ceed4 100644 --- a/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.ts +++ b/src/app/features/tech-record/components/tech-record-amend-vrm/tech-record-amend-vrm.component.ts @@ -169,4 +169,11 @@ export class AmendVrmComponent implements OnDestroy, OnInit { } return true; } + + get showWarning(): boolean { + if (this.vehicleType) { + return this.vehicleType === 'psv' || this.vehicleType === 'hgv'; + } + return false; + } } diff --git a/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.html b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.html new file mode 100644 index 0000000000..5efaac35ec --- /dev/null +++ b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.html @@ -0,0 +1,31 @@ +
+
+
+
+

Edit Additional Examiner Note

+
+ Date + {{ examinerNoteObj.createdAtDate | date: 'dd/MM/yyyy' | defaultNullOrEmpty }} + Created by + {{ examinerNoteObj.lastUpdatedBy }} +
+
+
+ + +
+
+
+
+
+ + Save + Cancel + +
diff --git a/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.scss b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.scss new file mode 100644 index 0000000000..bd6134d76f --- /dev/null +++ b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.scss @@ -0,0 +1,8 @@ +.parent-div { + display: flex; + flex-direction: row; +} +.table-value { + margin-left: 1%; + margin-right: 1%; +} diff --git a/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.spec.ts b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.spec.ts new file mode 100644 index 0000000000..6568ebf897 --- /dev/null +++ b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.spec.ts @@ -0,0 +1,95 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DynamicFormsModule } from '@forms/dynamic-forms.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TechnicalRecordService } from '@services/technical-record/technical-record.service'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { initialAppState } from '@store/index'; +import { ActivatedRoute, Router } from '@angular/router'; +import { of } from 'rxjs'; +import { GlobalErrorService } from '@core/components/global-error/global-error.service'; +import { TechRecordEditAdditionalExaminerNoteComponent } from './tech-record-edit-additional-examiner-note.component'; + +const mockTechRecordService = { + techRecord$: jest.fn(), +}; +describe('TechRecordEditAdditionalExaminerNoteComponent', () => { + let fixture: ComponentFixture; + let component: TechRecordEditAdditionalExaminerNoteComponent; + let router: Router; + let errorService: GlobalErrorService; + let route: ActivatedRoute; + let store: MockStore; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [TechRecordEditAdditionalExaminerNoteComponent], + imports: [DynamicFormsModule, FormsModule, ReactiveFormsModule, RouterTestingModule], + providers: [ + { provide: TechnicalRecordService, useValue: mockTechRecordService }, + provideMockStore({ initialState: initialAppState }), + { provide: ActivatedRoute, useValue: { params: of([{ id: 1 }]) } }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(TechRecordEditAdditionalExaminerNoteComponent); + component = fixture.componentInstance; + router = TestBed.inject(Router); + errorService = TestBed.inject(GlobalErrorService); + route = TestBed.inject(ActivatedRoute); + store = TestBed.inject(MockStore); + }); + it('should create', () => { + expect(component).toBeTruthy(); + }); + describe('ngOnInit', () => { + it('should call all initialisation functions', () => { + const examinerNoteSpy = jest.spyOn(component, 'getExaminerNote').mockReturnValue(); + const techRecordSpy = jest.spyOn(component, 'getTechRecord').mockReturnValue(); + const formSpy = jest.spyOn(component, 'setupForm').mockReturnValue(); + component.ngOnInit(); + expect(examinerNoteSpy).toHaveBeenCalled(); + expect(formSpy).toHaveBeenCalled(); + expect(techRecordSpy).toHaveBeenCalled(); + }); + }); + describe('navigateBack', () => { + it('should clear all errors', () => { + jest.spyOn(router, 'navigate').mockImplementation(); + + const clearErrorsSpy = jest.spyOn(errorService, 'clearErrors'); + + component.navigateBack(); + + expect(clearErrorsSpy).toHaveBeenCalledTimes(1); + }); + + it('should navigate back to the previous page', () => { + const navigateSpy = jest.spyOn(router, 'navigate').mockImplementation(() => Promise.resolve(true)); + + component.navigateBack(); + + expect(navigateSpy).toHaveBeenCalledWith(['../../'], { relativeTo: route }); + }); + }); + describe('handleSubmit', () => { + it('should not dispatch an action if the notes are the same', () => { + const storeSpy = jest.spyOn(store, 'dispatch'); + const navigateBackSpy = jest.spyOn(component, 'navigateBack').mockReturnValue(); + component.originalExaminerNote = 'foobar'; + component.editedExaminerNote = 'foobar'; + component.handleSubmit(); + expect(storeSpy).not.toHaveBeenCalled(); + expect(navigateBackSpy).toHaveBeenCalled(); + }); + + it('should dispatch an action if the notes are not the same', () => { + const storeSpy = jest.spyOn(store, 'dispatch'); + const navigateBackSpy = jest.spyOn(component, 'navigateBack').mockReturnValue(); + component.originalExaminerNote = 'foo'; + component.editedExaminerNote = 'bar'; + component.handleSubmit(); + expect(storeSpy).toHaveBeenCalled(); + expect(navigateBackSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.ts b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.ts new file mode 100644 index 0000000000..11e117cea6 --- /dev/null +++ b/src/app/features/tech-record/components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component.ts @@ -0,0 +1,108 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TechnicalRecordService } from '@services/technical-record/technical-record.service'; +import { TechRecordType } from '@dvsa/cvs-type-definitions/types/v3/tech-record/tech-record-vehicle-type'; +import { ReplaySubject, take, takeUntil } from 'rxjs'; +import { GlobalErrorService } from '@core/components/global-error/global-error.service'; +import { + CustomFormControl, + FormNodeEditTypes, + FormNodeTypes, + FormNodeWidth, +} from '@forms/services/dynamic-form.types'; +import { FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { State } from '@store/index'; +import { updateExistingADRAdditionalExaminerNote } from '@store/technical-records'; +import { AdditionalExaminerNotes } from '@dvsa/cvs-type-definitions/types/v3/tech-record/get/hgv/complete'; + +@Component({ + selector: 'tech-record-edit-additional-examiner-note', + templateUrl: './tech-record-edit-additional-examiner-note.component.html', + styleUrls: ['./tech-record-edit-additional-examiner-note.component.scss'], +}) +export class TechRecordEditAdditionalExaminerNoteComponent implements OnInit { + currentTechRecord!: TechRecordType<'hgv' | 'trl' | 'lgv'>; + examinerNoteIndex!: number; + editedExaminerNote: string = ''; + originalExaminerNote: string = ''; + examinerNoteObj!: AdditionalExaminerNotes; + destroy$ = new ReplaySubject(1); + form!: FormGroup; + formControl!: CustomFormControl; + + constructor( + private router: Router, + private route: ActivatedRoute, + private technicalRecordService: TechnicalRecordService, + private globalErrorService: GlobalErrorService, + private store: Store, + ) { } + + ngOnInit() { + this.getTechRecord(); + this.getExaminerNote(); + this.setupForm(); + } + + getTechRecord() { + this.technicalRecordService.techRecord$.pipe(takeUntil(this.destroy$)).subscribe((currentTechRecord) => { + this.currentTechRecord = currentTechRecord as TechRecordType<'hgv' | 'lgv' | 'trl'>; + }); + } + + getExaminerNote() { + this.route.params.pipe(take(1)).subscribe((params) => { + this.examinerNoteIndex = params['examinerNoteIndex']; + }); + const additionalExaminerNotes = this.currentTechRecord?.techRecord_adrDetails_additionalExaminerNotes; + if (additionalExaminerNotes) { + const examinerNote = additionalExaminerNotes[this.examinerNoteIndex].note; + if (examinerNote) { + this.examinerNoteObj = additionalExaminerNotes[this.examinerNoteIndex]; + this.originalExaminerNote = examinerNote; + this.editedExaminerNote = examinerNote; + } + } + } + + setupForm() { + this.formControl = new CustomFormControl({ + name: 'additionalExaminerNote', type: FormNodeTypes.CONTROL, + }, '', [Validators.required]); + this.form = new FormGroup({ + additionalExaminerNote: this.formControl, + }); + this.formControl.patchValue(this.editedExaminerNote); + } + + navigateBack() { + this.globalErrorService.clearErrors(); + void this.router.navigate(['../../'], { relativeTo: this.route }); + } + + handleSubmit(): void { + if (this.originalExaminerNote !== this.editedExaminerNote) { + this.store.dispatch( + updateExistingADRAdditionalExaminerNote({ + examinerNoteIndex: this.examinerNoteIndex, + additionalExaminerNote: this.editedExaminerNote, + }), + ); + } + this.navigateBack(); + } + + ngOnChanges(examinerNote: string) { + this.editedExaminerNote = examinerNote; + } + + get editTypes(): typeof FormNodeEditTypes { + return FormNodeEditTypes; + } + + get width(): typeof FormNodeWidth { + return FormNodeWidth; + } + +} diff --git a/src/app/features/tech-record/tech-record-routing.module.ts b/src/app/features/tech-record/tech-record-routing.module.ts index 757d0dc75b..e56d706ea7 100644 --- a/src/app/features/tech-record/tech-record-routing.module.ts +++ b/src/app/features/tech-record/tech-record-routing.module.ts @@ -10,20 +10,39 @@ import { techRecordDataResolver } from 'src/app/resolvers/tech-record-data/tech- import { techRecordValidateResolver } from 'src/app/resolvers/tech-record-validate/tech-record-validate.resolver'; import { techRecordViewResolver } from 'src/app/resolvers/tech-record-view/tech-record-view.resolver'; import { TechRecordRoutes } from '@models/routes.enum'; -import { AdrGenerateCertificateComponent } from './components/adr-generate-certificate/adr-generate-certificate.component'; -import { TechRecordAmendReasonComponent } from './components/tech-record-amend-reason/tech-record-amend-reason.component'; +import { + AdrGenerateCertificateComponent, +} from './components/adr-generate-certificate/adr-generate-certificate.component'; +import { + TechRecordAmendReasonComponent, +} from './components/tech-record-amend-reason/tech-record-amend-reason.component'; import { AmendVinComponent } from './components/tech-record-amend-vin/tech-record-amend-vin.component'; -import { AmendVrmReasonComponent } from './components/tech-record-amend-vrm-reason/tech-record-amend-vrm-reason.component'; +import { + AmendVrmReasonComponent, +} from './components/tech-record-amend-vrm-reason/tech-record-amend-vrm-reason.component'; import { AmendVrmComponent } from './components/tech-record-amend-vrm/tech-record-amend-vrm.component'; -import { TechRecordChangeStatusComponent } from './components/tech-record-change-status/tech-record-change-status.component'; +import { + TechRecordChangeStatusComponent, +} from './components/tech-record-change-status/tech-record-change-status.component'; import { ChangeVehicleTypeComponent } from './components/tech-record-change-type/tech-record-change-type.component'; -import { TechRecordChangeVisibilityComponent } from './components/tech-record-change-visibility/tech-record-change-visibility.component'; -import { GenerateLetterComponent } from './components/tech-record-generate-letter/tech-record-generate-letter.component'; +import { + TechRecordChangeVisibilityComponent, +} from './components/tech-record-change-visibility/tech-record-change-visibility.component'; +import { + GenerateLetterComponent, +} from './components/tech-record-generate-letter/tech-record-generate-letter.component'; import { GeneratePlateComponent } from './components/tech-record-generate-plate/tech-record-generate-plate.component'; -import { TechRecordSearchTyresComponent } from './components/tech-record-search-tyres/tech-record-search-tyres.component'; -import { TechRecordSummaryChangesComponent } from './components/tech-record-summary-changes/tech-record-summary-changes.component'; +import { + TechRecordSearchTyresComponent, +} from './components/tech-record-search-tyres/tech-record-search-tyres.component'; +import { + TechRecordSummaryChangesComponent, +} from './components/tech-record-summary-changes/tech-record-summary-changes.component'; import { TechRecordUnarchiveComponent } from './components/tech-record-unarchive/tech-record-unarchive-component'; import { TechRecordComponent } from './tech-record.component'; +import { + TechRecordEditAdditionalExaminerNoteComponent, +} from './components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component'; const routes: Routes = [ { @@ -151,13 +170,31 @@ const routes: Routes = [ { path: TechRecordRoutes.CORRECT_ERROR_CHANGE_SUMMARY, component: TechRecordSummaryChangesComponent, - data: { roles: Roles.TechRecordAmend }, + data: { + roles: Roles.TechRecordAmend, + isEditing: true, + }, + canActivate: [MsalGuard, RoleGuard], + }, + { + path: TechRecordRoutes.CORRECT_ERROR_EDIT_ADDITIONAL_EXAMINER_NOTE, + component: TechRecordEditAdditionalExaminerNoteComponent, + data: { + title: 'Edit Additional Examiner Note', + roles: Roles.TechRecordAmend, + isEditing: true, + reason: ReasonForEditing.CORRECTING_AN_ERROR, + }, canActivate: [MsalGuard, RoleGuard], + resolve: { techRecord: techRecordViewResolver }, }, { path: TechRecordRoutes.NOTIFIABLE_ALTERATION_NEEDED_CHANGE_SUMMARY, component: TechRecordSummaryChangesComponent, - data: { roles: Roles.TechRecordAmend }, + data: { + roles: Roles.TechRecordAmend, + isEditing: true, + }, canActivate: [MsalGuard, RoleGuard], }, { @@ -172,6 +209,18 @@ const routes: Routes = [ canActivate: [MsalGuard, RoleGuard], resolve: { techRecord: techRecordViewResolver }, }, + { + path: TechRecordRoutes.NOTIFIABLE_ALTERNATION_NEEDED_EDIT_ADDITIONAL_EXAMINER_NOTE, + component: TechRecordEditAdditionalExaminerNoteComponent, + data: { + title: 'Edit Additional Examiner Note', + roles: Roles.TechRecordAmend, + isEditing: true, + reason: ReasonForEditing.NOTIFIABLE_ALTERATION_NEEDED, + }, + canActivate: [MsalGuard, RoleGuard], + resolve: { techRecord: techRecordViewResolver }, + }, { path: TechRecordRoutes.TEST_RECORDS, data: { title: 'Test record', roles: Roles.TestResultView }, diff --git a/src/app/features/tech-record/tech-record.module.ts b/src/app/features/tech-record/tech-record.module.ts index 7ad498a655..34c34563bf 100644 --- a/src/app/features/tech-record/tech-record.module.ts +++ b/src/app/features/tech-record/tech-record.module.ts @@ -25,6 +25,9 @@ import { TechRecordComponent } from './tech-record.component'; import { AmendVrmReasonComponent } from './components/tech-record-amend-vrm-reason/tech-record-amend-vrm-reason.component'; import { TechRecordSummaryChangesComponent } from './components/tech-record-summary-changes/tech-record-summary-changes.component'; import { AdrGenerateCertificateComponent } from './components/adr-generate-certificate/adr-generate-certificate.component'; +import { + TechRecordEditAdditionalExaminerNoteComponent, +} from './components/tech-record-edit-additional-examiner-note/tech-record-edit-additional-examiner-note.component'; @NgModule({ declarations: [ @@ -43,6 +46,7 @@ import { AdrGenerateCertificateComponent } from './components/adr-generate-certi TechRecordHistoryComponent, TechRecordSearchTyresComponent, TechRecordTitleComponent, + TechRecordEditAdditionalExaminerNoteComponent, TechRouterOutletComponent, TestRecordSummaryComponent, VehicleTechnicalRecordComponent, diff --git a/src/app/forms/components/view-list-item/view-list-item.component.html b/src/app/forms/components/view-list-item/view-list-item.component.html index 254413c294..8ded37f9b4 100644 --- a/src/app/forms/components/view-list-item/view-list-item.component.html +++ b/src/app/forms/components/view-list-item/view-list-item.component.html @@ -20,35 +20,6 @@ - - - - - - - - - - - - - - - - - - -
DateCreated ByNotes
- {{ result.createdAtDate | date : 'dd/MM/yyyy' | defaultNullOrEmpty }} - {{ result.lastUpdatedBy }}{{ result.note }}
-
-
-
No additional examiner notes history available
-
-
- -
- diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history-edit.component.html b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history-edit.component.html index aa75a8a9dd..b84be43f86 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history-edit.component.html +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history-edit.component.html @@ -8,23 +8,34 @@ - - + + + - + +
DateCreated By NotesCreated ByDate
- {{ examinerNote.createdAtDate | date : 'dd/MM/yyyy' | defaultNullOrEmpty }} + {{ examinerNote.lastUpdatedBy }} - {{ examinerNote.note }} + {{ examinerNote.createdAtDate | date: 'dd/MM/yyyy HH:mm' | defaultNullOrEmpty }} + + Edit
+
diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.scss b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.scss index dc97c176aa..2df9a0b23d 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.scss +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.scss @@ -15,4 +15,10 @@ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5; -} \ No newline at end of file +} + +.button { + cursor: pointer; + text-decoration: underline; + color: blue; +} diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.spec.ts b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.spec.ts index 8f3255936e..76ff27c32c 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.spec.ts +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.spec.ts @@ -8,6 +8,11 @@ import { DynamicFormsModule } from '@forms/dynamic-forms.module'; import { mockVehicleTechnicalRecord } from '@mocks/mock-vehicle-technical-record.mock'; import { VehicleTypes } from '@models/vehicle-tech-record.model'; import { TechnicalRecordService } from '@services/technical-record/technical-record.service'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialAppState } from '@store/index'; +import { ActivatedRoute, Router } from '@angular/router'; +import { of } from 'rxjs'; +import { RouterTestingModule } from '@angular/router/testing'; const mockTechRecordService = { techRecord$: jest.fn(), @@ -15,16 +20,23 @@ const mockTechRecordService = { describe('AdrExaminerNotesHistoryEditComponent', () => { let component: AdrExaminerNotesHistoryEditComponent; let fixture: ComponentFixture; + let router: Router; + + const MOCK_HGV = mockVehicleTechnicalRecord(VehicleTypes.HGV) as TechRecordType<'hgv'>; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [AdrExaminerNotesHistoryEditComponent], - imports: [DynamicFormsModule, FormsModule, ReactiveFormsModule], + imports: [DynamicFormsModule, FormsModule, ReactiveFormsModule, RouterTestingModule], providers: [ { provide: TechnicalRecordService, useValue: mockTechRecordService }, + provideMockStore({ initialState: initialAppState }), + { provide: ActivatedRoute, useValue: { params: of([{ id: 1 }]) } }, ], }).compileComponents(); fixture = TestBed.createComponent(AdrExaminerNotesHistoryEditComponent); component = fixture.componentInstance; + router = TestBed.inject(Router); }); describe('ngOnDestroy', () => { it('should call destroy$.next and destroy$.complete', () => { @@ -48,7 +60,7 @@ describe('AdrExaminerNotesHistoryEditComponent', () => { component.currentTechRecord = mockVehicleTechnicalRecord(VehicleTypes.HGV) as TechRecordType<'hgv'>; const testNote = { note: 'testNote', - createdAtDate: new Date().toISOString().split('T')[0], + createdAtDate: new Date().toISOString(), lastUpdatedBy: 'Someone Somewhere', }; component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = [testNote]; @@ -56,4 +68,43 @@ describe('AdrExaminerNotesHistoryEditComponent', () => { expect(notes).toEqual([testNote]); }); }); + describe('getEditAdditionalExaminerNotePage', () => { + it('should navigate you to the EditAdditionalExaminerNotePage', () => { + const routerSpy = jest.spyOn(router, 'navigate'); + component.getEditAdditionalExaminerNotePage(1); + expect(routerSpy).toHaveBeenCalled(); + }); + }); + + describe('handlePaginationChange', () => { + it('should set the start and end pages', () => { + component.handlePaginationChange({ start: 0, end: 3 }); + + expect(component.pageStart).toBe(0); + expect(component.pageEnd).toBe(3); + }); + }); + + describe('currentAdrNotesPage', () => { + it('should return a sliced array of adr notes depending on the page the user is on', () => { + component.currentTechRecord = { ...MOCK_HGV }; + component.pageStart = 1; + component.pageEnd = 2; + component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = [ + { createdAtDate: 'test1', lastUpdatedBy: 'test1', note: 'test note 1' }, + { createdAtDate: 'test2', lastUpdatedBy: 'test2', note: 'test note 2' }, + ]; + expect(component.currentAdrNotesPage).toEqual([ + { createdAtDate: 'test2', lastUpdatedBy: 'test2', note: 'test note 2' }, + ]); + }); + + it('should return an empty array if the adr examiner notes is undefined', () => { + component.currentTechRecord = { ...MOCK_HGV }; + component.pageStart = 2; + component.pageEnd = 3; + component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = undefined; + expect(component.currentAdrNotesPage).toEqual([]); + }); + }); }); diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.ts b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.ts index 608210e6da..08e212cf4e 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.ts +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-edit/adr-examiner-notes-history.component-edit.ts @@ -1,4 +1,4 @@ -import { KeyValue } from '@angular/common'; +import { KeyValue, ViewportScroller } from '@angular/common'; import { AfterContentInit, Component, inject, OnDestroy, OnInit, @@ -9,6 +9,12 @@ import { BaseControlComponent } from '@forms/components/base-control/base-contro import { CustomControl, CustomFormControl } from '@forms/services/dynamic-form.types'; import { TechnicalRecordService } from '@services/technical-record/technical-record.service'; import { ReplaySubject, takeUntil } from 'rxjs'; +import { updateScrollPosition } from '@store/technical-records'; +import { TechnicalRecordServiceState } from '@store/technical-records/reducers/technical-record-service.reducer'; +import { Store } from '@ngrx/store'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ReasonForEditing } from '@models/vehicle-tech-record.model'; +import { AdditionalExaminerNotes } from '@dvsa/cvs-type-definitions/types/v3/tech-record/get/hgv/complete'; @Component({ selector: 'app-adr-examiner-notes-history', @@ -16,23 +22,29 @@ import { ReplaySubject, takeUntil } from 'rxjs'; styleUrls: ['adr-examiner-notes-history.component-edit.scss'], }) export class AdrExaminerNotesHistoryEditComponent extends BaseControlComponent implements OnInit, OnDestroy, AfterContentInit { - destroy$ = new ReplaySubject(1); - formArray = new FormArray([]); currentTechRecord?: TechRecordType<'hgv' | 'lgv' | 'trl'> = undefined; technicalRecordService = inject(TechnicalRecordService); + store = inject(Store); + viewportScroller = inject(ViewportScroller); + router = inject(Router); + route = inject(ActivatedRoute); + editingReason?: ReasonForEditing; + pageStart?: number; + pageEnd?: number; - ngOnInit() { + ngOnInit(): void { this.formArray.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((changes) => { this.control?.patchValue(changes, { emitModelToViewChange: true }); }); this.technicalRecordService.techRecord$.pipe(takeUntil(this.destroy$)).subscribe((currentTechRecord) => { this.currentTechRecord = currentTechRecord as TechRecordType<'hgv' | 'lgv' | 'trl'>; }); + this.editingReason = this.route.snapshot.data['reason']; } - override ngAfterContentInit() { + override ngAfterContentInit(): void { const injectedControl = this.injector.get(NgControl, null); if (injectedControl) { const ngControl = injectedControl.control as unknown as KeyValue; @@ -43,14 +55,30 @@ export class AdrExaminerNotesHistoryEditComponent extends BaseControlComponent i } } - getAdditionalExaminerNotes() { - const returnValue = this.currentTechRecord ? this.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes ?? [] : []; - return returnValue; + handlePaginationChange({ start, end }: { start: number; end: number }): void { + this.pageStart = start; + this.pageEnd = end; + this.cdr.detectChanges(); + } + + getAdditionalExaminerNotes(): AdditionalExaminerNotes[] { + return this.currentTechRecord?.techRecord_adrDetails_additionalExaminerNotes ?? []; + } + + get currentAdrNotesPage(): AdditionalExaminerNotes[] { + return this.currentTechRecord?.techRecord_adrDetails_additionalExaminerNotes?.slice(this.pageStart, this.pageEnd) ?? []; } - ngOnDestroy() { + getEditAdditionalExaminerNotePage(examinerNoteIndex: number) { + const route = `../${this.editingReason}/edit-additional-examiner-note/${examinerNoteIndex}`; + + this.store.dispatch(updateScrollPosition({ position: this.viewportScroller.getScrollPosition() })); + + void this.router.navigate([route], { relativeTo: this.route, state: this.currentTechRecord }); + } + + ngOnDestroy(): void { this.destroy$.next(true); this.destroy$.complete(); } - } diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.html b/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.html index 6e640b7c6e..52d754749e 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.html +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.html @@ -1,32 +1,45 @@ -
-

ADR Examiner Notes History

- - - - - - - - - - - - + +
+
DateCreated ByNotes
- {{ result.createdAtDate | date : 'dd/MM/yyyy' | defaultNullOrEmpty }} - {{ result.lastUpdatedBy }} - {{ result.note }} -
+ + + + + - - -
NotesCreated ByDate
- -
-
-
No additional examiner notes history available
-
-
-
-
+ + + + + + + + {{ result.note }} + + + {{ result.lastUpdatedBy }} + + {{ result.createdAtDate | date: 'dd/MM/yyyy HH:mm' | defaultNullOrEmpty }} + + + + + + + +
+
+
No additional examiner notes history available
+
+
+
+ +
diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.spec.ts b/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.spec.ts index 19bfc7d3d1..cac1bda6d0 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.spec.ts +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.spec.ts @@ -3,12 +3,24 @@ import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; import { CustomFormControl, FormNodeTypes } from '@forms/services/dynamic-form.types'; import { provideMockStore } from '@ngrx/store/testing'; import { State, initialAppState } from '@store/index'; +import { TechnicalRecordService } from '@services/technical-record/technical-record.service'; +import { mockVehicleTechnicalRecord } from '@mocks/mock-vehicle-technical-record.mock'; +import { VehicleTypes } from '@models/vehicle-tech-record.model'; +import { TechRecordType } from '@dvsa/cvs-type-definitions/types/v3/tech-record/tech-record-vehicle-type'; +import { of } from 'rxjs'; +import { RouterService } from '@services/router/router.service'; import { AdrExaminerNotesHistoryViewComponent } from './adr-examiner-notes-history-view.component'; describe('AdrExaminerNotesHistoryViewComponent', () => { let component: AdrExaminerNotesHistoryViewComponent; let fixture: ComponentFixture; + const MOCK_HGV = mockVehicleTechnicalRecord(VehicleTypes.HGV) as TechRecordType<'hgv'>; + const mockTechRecordService = { + techRecord$: of({ ...MOCK_HGV }), + }; + const mockRouterService = {}; + const control = new CustomFormControl({ name: 'techRecord_adrDetails_additionalExaminerNotes', type: FormNodeTypes.CONTROL, @@ -19,6 +31,8 @@ describe('AdrExaminerNotesHistoryViewComponent', () => { declarations: [AdrExaminerNotesHistoryViewComponent], providers: [ provideMockStore({ initialState: initialAppState }), + { provide: TechnicalRecordService, useValue: mockTechRecordService }, + { provide: RouterService, useValue: mockRouterService }, { provide: NG_VALUE_ACCESSOR, useExisting: AdrExaminerNotesHistoryViewComponent, multi: true }, { provide: NgControl, @@ -38,4 +52,63 @@ describe('AdrExaminerNotesHistoryViewComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + describe('ngOnInit', () => { + it('should set the currentTechRecord when ngOnInit is fired', () => { + component.currentTechRecord = undefined; + + component.ngOnInit(); + expect(component.currentTechRecord).toEqual(MOCK_HGV); + }); + }); + + describe('adrNotes', () => { + it('should return an array of the technical records adr examiner notes', () => { + component.currentTechRecord = { ...MOCK_HGV }; + component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = [ + { createdAtDate: 'test', lastUpdatedBy: 'test', note: 'test note' }, + ]; + expect(component.adrNotes).toEqual([{ createdAtDate: 'test', lastUpdatedBy: 'test', note: 'test note' }]); + }); + + it('should return an empty array if the adr examiner notes is undefined', () => { + component.currentTechRecord = { ...MOCK_HGV }; + component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = undefined; + expect(component.adrNotes).toEqual([]); + }); + }); + + describe('currentAdrNotesPage', () => { + it('should return a sliced array of adr notes depending on the page the user is on', () => { + component.currentTechRecord = { ...MOCK_HGV }; + component.pageStart = 2; + component.pageEnd = 3; + component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = [ + { createdAtDate: 'test1', lastUpdatedBy: 'test1', note: 'test note 1' }, + { createdAtDate: 'test2', lastUpdatedBy: 'test2', note: 'test note 2' }, + { createdAtDate: 'test3', lastUpdatedBy: 'test3', note: 'test note 3' }, + { createdAtDate: 'test4', lastUpdatedBy: 'test4', note: 'test note 4' }, + ]; + expect(component.currentAdrNotesPage).toEqual([ + { createdAtDate: 'test3', lastUpdatedBy: 'test3', note: 'test note 3' }, + ]); + }); + + it('should return an empty array if the adr examiner notes is undefined', () => { + component.currentTechRecord = { ...MOCK_HGV }; + component.pageStart = 1; + component.pageEnd = 2; + component.currentTechRecord.techRecord_adrDetails_additionalExaminerNotes = undefined; + expect(component.currentAdrNotesPage).toEqual([]); + }); + }); + + describe('handlePaginationChange', () => { + it('should set the start and end pages', () => { + component.handlePaginationChange({ start: 0, end: 3 }); + + expect(component.pageStart).toBe(0); + expect(component.pageEnd).toBe(3); + }); + }); }); diff --git a/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.ts b/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.ts index 78fc2c0098..4e12d6f635 100644 --- a/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.ts +++ b/src/app/forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component.ts @@ -1,6 +1,15 @@ -import { Component } from '@angular/core'; +import { + Component, inject, OnDestroy, OnInit, +} from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { BaseControlComponent } from '@forms/components/base-control/base-control.component'; +import { AdditionalExaminerNotes } from '@dvsa/cvs-type-definitions/types/v3/tech-record/get/hgv/complete'; +import { TechRecordType } from '@dvsa/cvs-type-definitions/types/v3/tech-record/tech-record-vehicle-type'; +import { TechnicalRecordService } from '@services/technical-record/technical-record.service'; +import { + map, Observable, Subject, takeUntil, +} from 'rxjs'; +import { RouterService } from '@services/router/router.service'; @Component({ selector: 'app-adr-examiner-notes-history-view', @@ -14,5 +23,40 @@ import { BaseControlComponent } from '@forms/components/base-control/base-contro }, ], }) -export class AdrExaminerNotesHistoryViewComponent extends BaseControlComponent { +export class AdrExaminerNotesHistoryViewComponent extends BaseControlComponent implements OnInit, OnDestroy { + technicalRecordService = inject(TechnicalRecordService); + routerService = inject(RouterService); + currentTechRecord?: TechRecordType<'hgv' | 'lgv' | 'trl'> | undefined; + pageStart?: number; + pageEnd?: number; + private destroy$: Subject = new Subject(); + + ngOnInit(): void { + this.technicalRecordService.techRecord$.pipe(takeUntil(this.destroy$)).subscribe((currentTechRecord) => { + this.currentTechRecord = currentTechRecord as TechRecordType<'hgv' | 'lgv' | 'trl'>; + }); + } + + get isEditing$(): Observable { + return this.routerService.getRouteDataProperty$('isEditing').pipe(map((isEditing) => !!isEditing)); + } + + handlePaginationChange({ start, end }: { start: number; end: number }): void { + this.pageStart = start; + this.pageEnd = end; + this.cdr.detectChanges(); + } + + get adrNotes(): AdditionalExaminerNotes[] { + return this.currentTechRecord?.techRecord_adrDetails_additionalExaminerNotes ?? []; + } + + get currentAdrNotesPage(): AdditionalExaminerNotes[] { + return this.currentTechRecord?.techRecord_adrDetails_additionalExaminerNotes?.slice(this.pageStart, this.pageEnd) ?? []; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.html b/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.html deleted file mode 100644 index 07d172802b..0000000000 --- a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - -
- - - Remove -
- -

- Add guidance notes -

-
diff --git a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.spec.ts b/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.spec.ts deleted file mode 100644 index 9e2a91411f..0000000000 --- a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { - FormControl, - FormGroup, - FormsModule, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule, -} from '@angular/forms'; -import { FORM_INJECTION_TOKEN } from '@forms/components/dynamic-form-field/dynamic-form-field.component'; -import { FieldErrorMessageComponent } from '@forms/components/field-error-message/field-error-message.component'; -import { SelectComponent } from '@forms/components/select/select.component'; -import { DynamicFormsModule } from '@forms/dynamic-forms.module'; -import { CustomFormControl, FormNodeTypes } from '@forms/services/dynamic-form.types'; -import { AdrGuidanceNotesComponent } from './adr-guidance-notes.component'; - -describe('AdrGuidanceNotesComponent', () => { - let component: AdrGuidanceNotesComponent; - let fixture: ComponentFixture; - - const control = new CustomFormControl({ - name: 'techRecord_adrDetails_additionalNotes_guidanceNotes', - label: 'Guidance Notes', - type: FormNodeTypes.CONTROL, - }); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AdrGuidanceNotesComponent, SelectComponent, FieldErrorMessageComponent], - imports: [DynamicFormsModule, FormsModule, ReactiveFormsModule], - providers: [ - { provide: NG_VALUE_ACCESSOR, useExisting: AdrGuidanceNotesComponent, multi: true }, - { - provide: NgControl, - useValue: { - control: { key: control.meta.name, value: control }, - }, - }, - { - provide: FORM_INJECTION_TOKEN, - useValue: new FormGroup({ - techRecord_adrDetails_additionalNotes_guidanceNotes: new FormControl(null), - }), - }, - ], - }) - .compileComponents(); - - fixture = TestBed.createComponent(AdrGuidanceNotesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - beforeEach(() => { - component.formArray.patchValue([]); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - it('should set a subscription to listen to form array changes, and patch the control value', () => { - const ngOnInitSpy = jest.spyOn(component, 'ngOnInit'); - component.ngOnInit(); - expect(ngOnInitSpy).toHaveBeenCalled(); - }); - }); - - describe('ngAfterContentInit', () => { - - it('should set the form control name', () => { - component.ngAfterContentInit(); - expect(component.name).toBeTruthy(); - }); - - it('should inject the form to which the control belongs', () => { - component.ngAfterContentInit(); - expect(component.control).toBeTruthy(); - }); - - it('should inject the control created by the dynamic form service, which is derrived from the template', () => { - component.ngAfterContentInit(); - expect(component.control).toBeTruthy(); - }); - - it('should make this control the first index of the form array', () => { - const formArraySpy = jest.spyOn(component.formArray, 'push'); - component.ngAfterContentInit(); - expect(formArraySpy).toHaveBeenCalledTimes(1); - expect(component.formArray.controls.at(0)).toBeDefined(); - }); - }); - - describe('addGuidanceNote', () => { - it('should add a copy of the first guidance control note to the form array (but with a reset value)', () => { - const methodSpy = jest.spyOn(component, 'addGuidanceNote'); - const formArraySpy = jest.spyOn(component.formArray, 'push'); - component.addGuidanceNote(); - expect(methodSpy).toHaveBeenCalled(); - expect(formArraySpy).toHaveBeenCalled(); - expect(component.formArray.controls.at(1)).toBeDefined(); - expect(component.formArray.controls.at(1)?.value).toBeFalsy(); - }); - }); - - describe('removeGuidanceNote', () => { - it('should remove the guidance note control from the form array at the index specified', () => { - const methodSpy = jest.spyOn(component, 'removeGuidanceNote'); - const formArraySpy = jest.spyOn(component.formArray, 'removeAt'); - - component.addGuidanceNote(); - component.addGuidanceNote(); - component.addGuidanceNote(); - component.addGuidanceNote(); - - component.removeGuidanceNote(3); - component.removeGuidanceNote(2); - - expect(methodSpy).toHaveBeenCalledTimes(2); - expect(formArraySpy).toHaveBeenCalledTimes(2); - expect(component.formArray.controls.at(4)).toBeUndefined(); - expect(component.formArray.controls.at(3)).toBeUndefined(); - expect(component.formArray.controls.at(2)).toBeDefined(); - expect(component.formArray.controls).toHaveLength(3); - }); - }); -}); diff --git a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.ts b/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.ts deleted file mode 100644 index 07641692cb..0000000000 --- a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - AfterContentInit, - Component, OnDestroy, OnInit, -} from '@angular/core'; -import { - FormArray, - NG_VALUE_ACCESSOR, -} from '@angular/forms'; -import { CustomFormControl } from '@forms/services/dynamic-form.types'; -import { ReplaySubject, takeUntil } from 'rxjs'; -import { CustomFormControlComponent } from '../custom-form-control/custom-form-control.component'; - -@Component({ - selector: 'app-adr-guidance-notes', - templateUrl: './adr-guidance-notes.component.html', - styleUrls: ['./adr-guidance-notes.component.scss'], - providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: AdrGuidanceNotesComponent, multi: true }], -}) -export class AdrGuidanceNotesComponent extends CustomFormControlComponent implements OnInit, AfterContentInit, OnDestroy { - destroy$ = new ReplaySubject(1); - formArray = new FormArray([]); - - ngOnInit() { - this.formArray.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((changes) => { - this.control?.patchValue(changes, { emitModelToViewChange: true }); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(true); - this.destroy$.complete(); - } - - override ngAfterContentInit() { - super.ngAfterContentInit(); - const { form, control } = this; - - if (!form) return; - if (!control) return; - - const value = form.get(this.name)?.value; - const values = Array.isArray(value) && value.length ? value : [null]; - values.forEach((guidanceNoteType: string) => { - this.formArray.push(new CustomFormControl(control.meta, guidanceNoteType)); - }); - } - - addGuidanceNote() { - const first = this.formArray.at(0); - this.formArray.push(new CustomFormControl(first.meta)); - } - - removeGuidanceNote(index: number) { - if (this.formArray.length < 2) return; - this.formArray.removeAt(index); - } - -} diff --git a/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.html b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.html new file mode 100644 index 0000000000..45022e3b8c --- /dev/null +++ b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ New Certificate required: + {{ control.value | defaultNullOrEmpty }}
+
diff --git a/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.scss b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.scss new file mode 100644 index 0000000000..20740fe733 --- /dev/null +++ b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.scss @@ -0,0 +1,11 @@ +.m-0 { + margin: 0; +} + +.break-words { + word-break: break-word; +} + +.border-b-0 { + border-bottom-width: 0; +} \ No newline at end of file diff --git a/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.spec.ts b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.spec.ts new file mode 100644 index 0000000000..88ca0a36d5 --- /dev/null +++ b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.spec.ts @@ -0,0 +1,41 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; +import { CustomFormControl, FormNodeTypes } from '@forms/services/dynamic-form.types'; +import { provideMockStore } from '@ngrx/store/testing'; +import { State, initialAppState } from '@store/index'; +import { AdrTankDetailsM145ViewComponent } from '../adr-tank-details-m145-view/adr-tank-details-m145-view.component'; +import { AdrNewCertificateRequiredViewComponent } from './adr-new-certificate-required-view.component'; +import { SharedModule } from '@shared/shared.module'; + +describe('AdrNewCertificateRequiredViewComponent', () => { + let component: AdrNewCertificateRequiredViewComponent; + let fixture: ComponentFixture; + + const control = new CustomFormControl({ + name: 'techRecord_adrDetails_m145Statement', + type: FormNodeTypes.CONTROL, + value: [], + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AdrNewCertificateRequiredViewComponent], + imports: [SharedModule], + providers: [ + provideMockStore({ initialState: initialAppState }), + { provide: NG_VALUE_ACCESSOR, useExisting: AdrTankDetailsM145ViewComponent, multi: true }, + { provide: NgControl, useValue: { control } }, + ], + }) + .compileComponents(); + + fixture = TestBed.createComponent(AdrNewCertificateRequiredViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.ts b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.ts new file mode 100644 index 0000000000..132dcd90de --- /dev/null +++ b/src/app/forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { BaseControlComponent } from '@forms/components/base-control/base-control.component'; + +@Component({ + selector: 'app-adr-new-certificate-required-view', + templateUrl: './adr-new-certificate-required-view.component.html', + styleUrl: './adr-new-certificate-required-view.component.scss', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: AdrNewCertificateRequiredViewComponent, + multi: true, + }, + ], +}) +export class AdrNewCertificateRequiredViewComponent extends BaseControlComponent {} diff --git a/src/app/forms/dynamic-forms.module.ts b/src/app/forms/dynamic-forms.module.ts index 306df05dec..a9aea0b22a 100644 --- a/src/app/forms/dynamic-forms.module.ts +++ b/src/app/forms/dynamic-forms.module.ts @@ -38,7 +38,9 @@ import { ViewCombinationComponent } from './components/view-combination/view-com import { ViewListItemComponent } from './components/view-list-item/view-list-item.component'; import { AbandonDialogComponent } from './custom-sections/abandon-dialog/abandon-dialog.component'; import { AdrExaminerNotesHistoryViewComponent } from './custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component'; -import { AdrGuidanceNotesComponent } from './custom-sections/adr-guidance-notes/adr-guidance-notes.component'; +import { + AdrNewCertificateRequiredViewComponent, +} from './custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component'; import { AdrTankDetailsInitialInspectionViewComponent, } from './custom-sections/adr-tank-details-initial-inspection-view/adr-tank-details-initial-inspection-view.component'; @@ -131,7 +133,6 @@ import { SuffixDirective } from './directives/suffix.directive'; ModifiedWeightsComponent, FieldWarningMessageComponent, AdrComponent, - AdrGuidanceNotesComponent, AdrTankDetailsSubsequentInspectionsEditComponent, AdrTankStatementUnNumberEditComponent, CustomFormControlComponent, @@ -143,6 +144,7 @@ import { SuffixDirective } from './directives/suffix.directive'; AdrCertificateHistoryComponent, AdrTankDetailsM145ViewComponent, ContingencyAdrGenerateCertComponent, + AdrNewCertificateRequiredViewComponent, ], imports: [CommonModule, FormsModule, ReactiveFormsModule, SharedModule, RouterModule], exports: [ @@ -192,6 +194,7 @@ import { SuffixDirective } from './directives/suffix.directive'; ModifiedWeightsComponent, AdrComponent, AdrCertificateHistoryComponent, + FieldWarningMessageComponent, ], }) export class DynamicFormsModule { } diff --git a/src/app/forms/models/async-validators.enum.ts b/src/app/forms/models/async-validators.enum.ts index 14985ee9ca..4232bbe6ff 100644 --- a/src/app/forms/models/async-validators.enum.ts +++ b/src/app/forms/models/async-validators.enum.ts @@ -9,4 +9,5 @@ export enum AsyncValidatorNames { RequiredIfNotResult = 'requiredIfNotResult', HideIfEqualsWithCondition = 'hideIfEqualsWithCondition', PassResultDependantOnCustomDefects = 'passResultDependantOnCustomDefects', + RequiredWhenCarryingDangerousGoods = 'requiredWhenCarryingDangerousGoods', } diff --git a/src/app/forms/services/dynamic-form.service.ts b/src/app/forms/services/dynamic-form.service.ts index 0cb554a6e7..719ca039cc 100644 --- a/src/app/forms/services/dynamic-form.service.ts +++ b/src/app/forms/services/dynamic-form.service.ts @@ -107,6 +107,7 @@ export class DynamicFormService { [AsyncValidatorNames.ResultDependantOnRequiredStandards]: () => CustomAsyncValidators.resultDependantOnRequiredStandards(this.store), [AsyncValidatorNames.UpdateTesterDetails]: () => CustomAsyncValidators.updateTesterDetails(this.store), [AsyncValidatorNames.UpdateTestStationDetails]: () => CustomAsyncValidators.updateTestStationDetails(this.store), + [AsyncValidatorNames.RequiredWhenCarryingDangerousGoods]: () => CustomAsyncValidators.requiredWhenCarryingDangerousGoods(this.store), }; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/app/forms/templates/general/adr-summary.template.ts b/src/app/forms/templates/general/adr-summary.template.ts index d54e01d5d1..3ef290f770 100644 --- a/src/app/forms/templates/general/adr-summary.template.ts +++ b/src/app/forms/templates/general/adr-summary.template.ts @@ -14,11 +14,13 @@ import { import { AdrExaminerNotesHistoryViewComponent, } from '@forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component'; -import { AdrGuidanceNotesComponent } from '@forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component'; -import { AdrTankDetailsM145ViewComponent } from '@forms/custom-sections/adr-tank-details-m145-view/adr-tank-details-m145-view.component'; +import { + AdrNewCertificateRequiredViewComponent, +} from '@forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component'; import { AdrTankDetailsInitialInspectionViewComponent, } from '@forms/custom-sections/adr-tank-details-initial-inspection-view/adr-tank-details-initial-inspection-view.component'; +import { AdrTankDetailsM145ViewComponent } from '@forms/custom-sections/adr-tank-details-m145-view/adr-tank-details-m145-view.component'; import { AdrTankDetailsSubsequentInspectionsEditComponent, } from '@forms/custom-sections/adr-tank-details-subsequent-inspections-edit/adr-tank-details-subsequent-inspections-edit.component'; @@ -171,6 +173,18 @@ export const AdrSummaryTemplate: FormNode = { }, ], }, + { + name: 'techRecord_adrDetails_vehicleDetails_usedOnInternationalJourneys', + label: 'Vehicle used on international journeys', + type: FormNodeTypes.CONTROL, + options: [ + { value: 'yes', label: 'Yes' }, + { value: 'no', label: 'No' }, + { value: 'n/a', label: 'Not applicable' }, + ], + hide: true, + groups: ['adr_details', 'dangerous_goods'], + }, { name: 'techRecord_adrDetails_vehicleDetails_approvalDate', label: 'Date processed', @@ -236,20 +250,13 @@ export const AdrSummaryTemplate: FormNode = { name: 'techRecord_adrDetails_additionalNotes_number', label: 'Guidance notes', type: FormNodeTypes.CONTROL, - editType: FormNodeEditTypes.CUSTOM, - editComponent: AdrGuidanceNotesComponent, + editType: FormNodeEditTypes.CHECKBOXGROUP, groups: ['adr_details', 'dangerous_goods'], hide: true, width: FormNodeWidth.XS, value: [], - customErrorMessage: 'Guidance notes is required with Able to carry dangerous goods', options: getOptionsFromEnum(ADRAdditionalNotesNumber), - validators: [ - { - name: ValidatorNames.IsArray, - args: { requiredIndices: [0], whenEquals: { sibling: 'techRecord_adrDetails_dangerousGoods', value: [true] } }, - }, - ], + validators: [], }, { name: 'techRecord_adrDetails_adrTypeApprovalNo', @@ -757,6 +764,31 @@ export const AdrSummaryTemplate: FormNode = { groups: ['declarations_details', 'dangerous_goods'], hide: true, }, + { + name: 'NewCertificateRequested', + label: 'New Certificate required', + type: FormNodeTypes.TITLE, + groups: ['dangerous_goods'], + hide: true, + }, + { + name: 'techRecord_adrDetails_newCertificateRequested', + label: 'Yes', + type: FormNodeTypes.CONTROL, + editType: FormNodeEditTypes.CHECKBOX, + viewType: FormNodeViewTypes.CUSTOM, + viewComponent: AdrNewCertificateRequiredViewComponent, + value: false, + groups: ['dangerous_goods'], + hide: true, + }, + { + name: 'ExaminerNotesSectionTitle', + label: 'Additional Examiner Notes History', + type: FormNodeTypes.TITLE, + groups: ['adr_details', 'dangerous_goods'], + hide: true, + }, { name: 'techRecord_adrDetails_additionalExaminerNotes_note', label: 'Additional Examiner Notes', @@ -774,7 +806,7 @@ export const AdrSummaryTemplate: FormNode = { label: 'Additional examiner notes history', value: null, type: FormNodeTypes.CONTROL, - viewType: FormNodeViewTypes.HIDDEN, + viewType: FormNodeViewTypes.CUSTOM, viewComponent: AdrExaminerNotesHistoryViewComponent, editType: FormNodeEditTypes.CUSTOM, editComponent: AdrExaminerNotesHistoryEditComponent, diff --git a/src/app/forms/templates/general/adr.template.ts b/src/app/forms/templates/general/adr.template.ts index 4cc047312e..2ea993c830 100644 --- a/src/app/forms/templates/general/adr.template.ts +++ b/src/app/forms/templates/general/adr.template.ts @@ -18,11 +18,15 @@ import { import { AdrExaminerNotesHistoryViewComponent, } from '@forms/custom-sections/adr-examiner-notes-history-view/adr-examiner-notes-history-view.component'; -import { AdrGuidanceNotesComponent } from '@forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component'; +import { + AdrNewCertificateRequiredViewComponent, +} from '@forms/custom-sections/adr-new-certificate-required-view/adr-new-certificate-required-view.component'; import { AdrTankDetailsInitialInspectionViewComponent, } from '@forms/custom-sections/adr-tank-details-initial-inspection-view/adr-tank-details-initial-inspection-view.component'; -import { AdrTankDetailsM145ViewComponent } from '@forms/custom-sections/adr-tank-details-m145-view/adr-tank-details-m145-view.component'; +import { + AdrTankDetailsM145ViewComponent, +} from '@forms/custom-sections/adr-tank-details-m145-view/adr-tank-details-m145-view.component'; import { AdrTankDetailsSubsequentInspectionsEditComponent, } from '@forms/custom-sections/adr-tank-details-subsequent-inspections-edit/adr-tank-details-subsequent-inspections-edit.component'; @@ -176,6 +180,19 @@ export const AdrTemplate: FormNode = { }, ], }, + { + name: 'techRecord_adrDetails_vehicleDetails_usedOnInternationalJourneys', + label: 'Vehicle used on international journeys', + type: FormNodeTypes.CONTROL, + editType: FormNodeEditTypes.RADIO, + options: [ + { value: 'yes', label: 'Yes' }, + { value: 'no', label: 'No' }, + { value: 'n/a', label: 'Not applicable' }, + ], + hide: true, + groups: ['adr_details', 'dangerous_goods'], + }, { name: 'techRecord_adrDetails_vehicleDetails_approvalDate', label: 'Date processed', @@ -242,19 +259,14 @@ export const AdrTemplate: FormNode = { name: 'techRecord_adrDetails_additionalNotes_number', label: 'Guidance notes', type: FormNodeTypes.CONTROL, - editType: FormNodeEditTypes.CUSTOM, - editComponent: AdrGuidanceNotesComponent, + editType: FormNodeEditTypes.CHECKBOXGROUP, groups: ['adr_details', 'dangerous_goods'], hide: true, width: FormNodeWidth.XS, value: [], - customErrorMessage: 'Guidance notes is required with Able to carry dangerous goods', options: getOptionsFromEnum(ADRAdditionalNotesNumber), validators: [ - { - name: ValidatorNames.IsArray, - args: { requiredIndices: [0], whenEquals: { sibling: 'techRecord_adrDetails_dangerousGoods', value: [true] } }, - }, + { name: ValidatorNames.RequiredIfEquals, args: { sibling: 'techRecord_adrDetails_dangerousGoods', value: [true] } }, ], }, { @@ -587,6 +599,7 @@ export const AdrTemplate: FormNode = { { name: 'techRecord_adrDetails_memosApply', label: 'Memo 7/9 (3 month extension) applied', + hint: 'Only applicable for vehicles used on national journeys', type: FormNodeTypes.CONTROL, editType: FormNodeEditTypes.CHECKBOXGROUP, groups: ['tank_details', 'dangerous_goods'], @@ -756,6 +769,31 @@ export const AdrTemplate: FormNode = { groups: ['declarations_details', 'dangerous_goods'], hide: true, }, + { + name: 'NewCertificateRequested', + label: 'New Certificate required', + type: FormNodeTypes.TITLE, + groups: ['dangerous_goods'], + hide: true, + }, + { + name: 'techRecord_adrDetails_newCertificateRequested', + label: 'Yes', + type: FormNodeTypes.CONTROL, + viewType: FormNodeViewTypes.CUSTOM, + viewComponent: AdrNewCertificateRequiredViewComponent, + editType: FormNodeEditTypes.CHECKBOX, + value: false, + groups: ['dangerous_goods'], + hide: true, + }, + { + name: 'ExaminerNotesSectionTitle', + label: 'Additional Examiner Notes History', + type: FormNodeTypes.TITLE, + groups: ['adr_details', 'dangerous_goods'], + hide: true, + }, { name: 'techRecord_adrDetails_additionalExaminerNotes_note', label: 'Additional Examiner Notes', @@ -775,7 +813,7 @@ export const AdrTemplate: FormNode = { label: 'Additional examiner notes history', value: null, type: FormNodeTypes.CONTROL, - viewType: FormNodeViewTypes.ADR_EXAMINER_NOTES, // TODO: replace with custom + viewType: FormNodeViewTypes.CUSTOM, viewComponent: AdrExaminerNotesHistoryViewComponent, editType: FormNodeEditTypes.CUSTOM, editComponent: AdrExaminerNotesHistoryEditComponent, diff --git a/src/app/forms/templates/general/hgv-trl-body.template.ts b/src/app/forms/templates/general/hgv-trl-body.template.ts index eda37032a8..e097b75124 100644 --- a/src/app/forms/templates/general/hgv-trl-body.template.ts +++ b/src/app/forms/templates/general/hgv-trl-body.template.ts @@ -1,3 +1,4 @@ +import { AsyncValidatorNames } from '@forms/models/async-validators.enum'; import { ValidatorNames } from '@forms/models/validators.enum'; import { TagType } from '@shared/components/tag/tag.component'; import { @@ -27,6 +28,7 @@ export const HgvAndTrlBodyTemplate: FormNode = { type: FormNodeTypes.CONTROL, editType: FormNodeEditTypes.TEXT, validators: [{ name: ValidatorNames.MaxLength, args: 50 }], + asyncValidators: [{ name: AsyncValidatorNames.RequiredWhenCarryingDangerousGoods }], customTags: [{ colour: TagType.PURPLE, label: TagTypeLabels.PLATES }], }, { diff --git a/src/app/forms/utils/error-message-map.ts b/src/app/forms/utils/error-message-map.ts index 9a47414b52..8318a8007f 100644 --- a/src/app/forms/utils/error-message-map.ts +++ b/src/app/forms/utils/error-message-map.ts @@ -50,4 +50,5 @@ export const ErrorMessageMap: Record = { [AsyncValidatorNames.RequiredIfNotFail]: (err: boolean, label?: string) => `${label || DEFAULT_LABEL} is required`, [AsyncValidatorNames.RequiredIfNotResult]: (err: boolean, label?: string) => `${label || DEFAULT_LABEL} is required`, [AsyncValidatorNames.RequiredIfNotResultAndSiblingEquals]: (err: boolean, label?: string) => `${label || DEFAULT_LABEL} is required`, + [AsyncValidatorNames.RequiredWhenCarryingDangerousGoods]: (err: { message: string }) => err.message, }; diff --git a/src/app/forms/validators/custom-async-validators.spec.ts b/src/app/forms/validators/custom-async-validators.spec.ts index 11f29ebf81..6c4e919959 100644 --- a/src/app/forms/validators/custom-async-validators.spec.ts +++ b/src/app/forms/validators/custom-async-validators.spec.ts @@ -1,5 +1,6 @@ import { TestBed } from '@angular/core/testing'; -import { FormGroup, ValidationErrors } from '@angular/forms'; +import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms'; +import { TechRecordType } from '@dvsa/cvs-type-definitions/types/v3/tech-record/tech-record-verb-vehicle-type'; import { operatorEnum } from '@forms/models/condition.model'; import { CustomFormControl, FormNodeTypes } from '@forms/services/dynamic-form.types'; import { createMockCustomDefect } from '@mocks/custom-defect.mock'; @@ -8,9 +9,10 @@ import { TestResultModel } from '@models/test-results/test-result.model'; import { resultOfTestEnum } from '@models/test-types/test-type.model'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { State, initialAppState } from '@store/.'; +import { editingTechRecord } from '@store/technical-records'; import { testResultInEdit } from '@store/test-records'; import { initialTestStationsState } from '@store/test-stations'; -import { Observable, firstValueFrom } from 'rxjs'; +import { Observable, firstValueFrom, lastValueFrom } from 'rxjs'; import { CustomAsyncValidators } from './custom-async-validators'; describe('resultDependantOnCustomDefects', () => { @@ -696,3 +698,121 @@ describe('hide if equals with condition', () => { expect((form.controls['bar'] as CustomFormControl).meta.hide).toBe(false); }); }); + +describe('requiredWhenCarryingDangerousGoods', () => { + let form: FormGroup; + let store: MockStore; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideMockStore({ initialState: initialAppState })], + }); + + store = TestBed.inject(MockStore); + + form = new FormGroup({ + techRecord_make: new CustomFormControl({ + name: 'techRecord_make', + type: FormNodeTypes.CONTROL, + children: [], + }, null), + techRecord_adrDetails_dangerousGoods: new CustomFormControl({ + name: 'techRecord_adrDetails_dangerousGoods', + type: FormNodeTypes.CONTROL, + children: [], + }, null), + }); + }); + it('should return null if the vehicle is not of type HGV or TRL', async () => { + // Not applicable tech record vehicle type + const carTechRecord: TechRecordType<'car', 'put'> = { + techRecord_vehicleType: 'car', + vin: 'car', + techRecord_reasonForCreation: 'test', + techRecord_statusCode: 'provisional', + }; + + store.overrideSelector(editingTechRecord, carTechRecord); + + const result = CustomAsyncValidators.requiredWhenCarryingDangerousGoods(store)(form.get('techRecord_make') as AbstractControl); + await expect(lastValueFrom(result)).resolves.toBeNull(); + }); + + it('should return null if the control is populated', async () => { + // Applicable vehicle tech record type + const hgvTechRecord: TechRecordType<'hgv', 'put'> = { + techRecord_vehicleType: 'hgv', + partialVin: '', + techRecord_bodyType_description: '', + techRecord_noOfAxles: 2, + techRecord_reasonForCreation: 'test', + techRecord_statusCode: 'provisional', + techRecord_vehicleClass_description: 'heavy goods vehicle', + primaryVrm: '', + vin: '', + + // Vehicle does carry dangerous goods + techRecord_adrDetails_dangerousGoods: true, + }; + + // ...but the control is populated + form.get('techRecord_make')?.patchValue('make'); + + store.overrideSelector(editingTechRecord, hgvTechRecord); + + const result = CustomAsyncValidators.requiredWhenCarryingDangerousGoods(store)(form.get('techRecord_make') as AbstractControl); + await expect(lastValueFrom(result)).resolves.toBeNull(); + }); + + it('should return null if the vehicle is an ADR vehicle, but does not carry dangerous goods', async () => { + // Applicable vehicle tech record type + const hgvTechRecord: TechRecordType<'hgv', 'put'> = { + techRecord_vehicleType: 'hgv', + partialVin: '', + techRecord_bodyType_description: '', + techRecord_noOfAxles: 2, + techRecord_reasonForCreation: 'test', + techRecord_statusCode: 'provisional', + techRecord_vehicleClass_description: 'heavy goods vehicle', + primaryVrm: '', + vin: '', + + // Vehicle doesn't carry dangerous goods + techRecord_adrDetails_dangerousGoods: false, + }; + + // ...and the control isn't populated + form.get('techRecord_make')?.patchValue(null); + + store.overrideSelector(editingTechRecord, hgvTechRecord); + + const result = CustomAsyncValidators.requiredWhenCarryingDangerousGoods(store)(form.get('techRecord_make') as AbstractControl); + await expect(lastValueFrom(result)).resolves.toBeNull(); + }); + + it('should return the required validation error when the vehicle is an ADR vehicle, and carries dangerous goods is selected', async () => { + // Applicable vehicle tech record type + const hgvTechRecord: TechRecordType<'hgv', 'put'> = { + techRecord_vehicleType: 'hgv', + partialVin: '', + techRecord_bodyType_description: '', + techRecord_noOfAxles: 2, + techRecord_reasonForCreation: 'test', + techRecord_statusCode: 'provisional', + techRecord_vehicleClass_description: 'heavy goods vehicle', + primaryVrm: '', + vin: '', + + // Vehicle does carry dangerous goods + techRecord_adrDetails_dangerousGoods: true, + }; + + // ...and the control isn't populated + form.get('techRecord_make')?.patchValue(null); + + store.overrideSelector(editingTechRecord, hgvTechRecord); + + const result = CustomAsyncValidators.requiredWhenCarryingDangerousGoods(store)(form.get('techRecord_make') as AbstractControl); + await expect(lastValueFrom(result)).resolves.toStrictEqual({ required: true }); + }); +}); diff --git a/src/app/forms/validators/custom-async-validators.ts b/src/app/forms/validators/custom-async-validators.ts index 1a0b1ad744..395ed33793 100644 --- a/src/app/forms/validators/custom-async-validators.ts +++ b/src/app/forms/validators/custom-async-validators.ts @@ -1,4 +1,6 @@ -import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms'; +import { + AbstractControl, AsyncValidatorFn, ValidationErrors, Validators, +} from '@angular/forms'; import { Condition, operatorEnum } from '@forms/models/condition.model'; // eslint-disable-next-line import/no-cycle import { CustomFormControl } from '@forms/services/dynamic-form.types'; @@ -9,6 +11,7 @@ import { resultOfTestEnum } from '@models/test-types/test-type.model'; import { Store, select } from '@ngrx/store'; import { State } from '@store/.'; import { selectUserByResourceKey } from '@store/reference-data'; +import { editingTechRecord } from '@store/technical-records'; import { testResultInEdit } from '@store/test-records'; import { getTestStationFromProperty } from '@store/test-stations'; import { @@ -224,6 +227,21 @@ export class CustomAsyncValidators { ); } + static requiredWhenCarryingDangerousGoods = (store: Store) => { + return (control: AbstractControl): Observable => { + return store.select(editingTechRecord).pipe(take(1), map((form) => { + if ( + form + && (form.techRecord_vehicleType === 'hgv' || form.techRecord_vehicleType === 'trl') + && (form.techRecord_adrDetails_dangerousGoods)) { + return Validators.required(control); + } + + return null; + })); + }; + }; + private static checkConditions(testResult: TestResultModel, conditions: Condition | Condition[]) { if (!Array.isArray(conditions)) { return CustomAsyncValidators.checkCondition(testResult, conditions); diff --git a/src/app/models/routes.enum.ts b/src/app/models/routes.enum.ts index f478b7c7d4..1df9e718a1 100644 --- a/src/app/models/routes.enum.ts +++ b/src/app/models/routes.enum.ts @@ -30,8 +30,10 @@ export enum TechRecordRoutes { CHANGE_VTA_VISIBILITY = 'change-vta-visibility', CORRECT_ERROR_TYRE_SEARCH = 'correcting-an-error/tyre-search/:axleNumber', CORRECT_ERROR_CHANGE_SUMMARY = 'correcting-an-error/change-summary', + CORRECT_ERROR_EDIT_ADDITIONAL_EXAMINER_NOTE = 'correcting-an-error/edit-additional-examiner-note/:examinerNoteIndex', NOTIFIABLE_ALTERATION_NEEDED_CHANGE_SUMMARY = 'notifiable-alteration-needed/change-summary', NOTIFIABLE_ALTERATION_NEEDED_TYRE_SEARCH = 'notifiable-alteration-needed/tyre-search/:axleNumber', + NOTIFIABLE_ALTERNATION_NEEDED_EDIT_ADDITIONAL_EXAMINER_NOTE = 'notifiable-alteration-needed/edit-additional-examiner-note/:examinerNoteIndex', TEST_RECORDS = 'test-records/test-result/:testResultId/:testNumber', CREATE_TEST = 'test-records/create-test', ADR_CERTIFICATE = 'adr-certificate', diff --git a/src/app/shared/components/collapsible-text/collapsible-text.component.html b/src/app/shared/components/collapsible-text/collapsible-text.component.html new file mode 100644 index 0000000000..f5371e2a2c --- /dev/null +++ b/src/app/shared/components/collapsible-text/collapsible-text.component.html @@ -0,0 +1,26 @@ + +
{{ text | slice: 0 : maxChars }}...
+ + +
+ +
+ {{ text }} +
+ + +
diff --git a/src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.scss b/src/app/shared/components/collapsible-text/collapsible-text.component.scss similarity index 100% rename from src/app/forms/custom-sections/adr-guidance-notes/adr-guidance-notes.component.scss rename to src/app/shared/components/collapsible-text/collapsible-text.component.scss diff --git a/src/app/shared/components/collapsible-text/collapsible-text.component.spec.ts b/src/app/shared/components/collapsible-text/collapsible-text.component.spec.ts new file mode 100644 index 0000000000..1dd3750ca8 --- /dev/null +++ b/src/app/shared/components/collapsible-text/collapsible-text.component.spec.ts @@ -0,0 +1,36 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CollapsibleTextComponent } from './collapsible-text.component'; + +describe('CollapsibleTextComponent', () => { + let component: CollapsibleTextComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CollapsibleTextComponent], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollapsibleTextComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should open when open method is called', () => { + component.isCollapsed = true; + component.open(); + expect(component.isCollapsed).toBe(false); + }); + + it('should close when close method is called', () => { + component.isCollapsed = false; + component.close(); + expect(component.isCollapsed).toBe(true); + }); +}); diff --git a/src/app/shared/components/collapsible-text/collapsible-text.component.ts b/src/app/shared/components/collapsible-text/collapsible-text.component.ts new file mode 100644 index 0000000000..e39ff48585 --- /dev/null +++ b/src/app/shared/components/collapsible-text/collapsible-text.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'collapsible-text', + templateUrl: './collapsible-text.component.html', + styleUrls: ['./collapsible-text.component.scss'], +}) +export class CollapsibleTextComponent { + + @Input() text: string = ''; + @Input() maxChars: number = 0; + @Input() isCollapsed = true; + + open() { + this.isCollapsed = false; + } + + close() { + this.isCollapsed = true; + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 94f3bf1dd5..c812d56293 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -4,6 +4,7 @@ import { RouterModule } from '@angular/router'; import { DocumentRetrievalService } from '@api/document-retrieval'; import { RoleRequiredDirective } from '@directives/app-role-required.directive'; import { FeatureToggleDirective } from '@directives/feature-toggle.directive'; +import { CollapsibleTextComponent } from '@shared/components/collapsible-text/collapsible-text.component'; import { AccordionControlComponent } from './components/accordion-control/accordion-control.component'; import { AccordionComponent } from './components/accordion/accordion.component'; import { BannerComponent } from './components/banner/banner.component'; @@ -54,6 +55,7 @@ import { TyreAxleLoadPipe } from './pipes/tyre-axle-load/tyre-axle-load.pipe'; TyreAxleLoadPipe, GetControlLabelPipe, FormatVehicleTypePipe, + CollapsibleTextComponent, ], imports: [CommonModule, RouterModule], exports: [ @@ -80,6 +82,7 @@ import { TyreAxleLoadPipe } from './pipes/tyre-axle-load/tyre-axle-load.pipe'; TyreAxleLoadPipe, GetControlLabelPipe, FormatVehicleTypePipe, + CollapsibleTextComponent, ], providers: [DocumentRetrievalService], diff --git a/src/app/store/technical-records/actions/technical-record-service.actions.ts b/src/app/store/technical-records/actions/technical-record-service.actions.ts index 21ce912342..3e70e03b7b 100644 --- a/src/app/store/technical-records/actions/technical-record-service.actions.ts +++ b/src/app/store/technical-records/actions/technical-record-service.actions.ts @@ -98,7 +98,12 @@ export const clearScrollPosition = createAction(`${prefix} clearScrollPosition`) export const clearADRDetailsBeforeUpdate = createAction(`${prefix} clearADRDetailsBeforeUpdate`); -export const updateADRAdditionalExaminerNotes = createAction(`${prefix} handleADRExaminerNoteChanges`, props<{ username: string }>()); +export const updateADRAdditionalExaminerNotes = createAction(`${prefix} updateADRAdditionalExaminerNotes`, props<{ username: string }>()); + +export const updateExistingADRAdditionalExaminerNote = createAction( + `${prefix} updateExistingADRAdditionalExaminerNote`, + props<{ additionalExaminerNote: string, examinerNoteIndex: number }>(), +); export const generateADRCertificate = createAction(`${prefix} generateADRCertificate`, props<{ systemNumber: string, createdTimestamp: string, certificateType: string diff --git a/src/app/store/technical-records/reducers/technical-record-service.reducer.spec.ts b/src/app/store/technical-records/reducers/technical-record-service.reducer.spec.ts index 71076d2966..9bb48cd36b 100644 --- a/src/app/store/technical-records/reducers/technical-record-service.reducer.spec.ts +++ b/src/app/store/technical-records/reducers/technical-record-service.reducer.spec.ts @@ -33,7 +33,7 @@ import { updateBody, updateBrakeForces, updateEditingTechRecord, - updateEditingTechRecordCancel, + updateEditingTechRecordCancel, updateExistingADRAdditionalExaminerNote, updateScrollPosition, updateTechRecord, updateTechRecordFailure, @@ -559,10 +559,19 @@ describe('Vehicle Technical Record Reducer', () => { }); describe('handleADRExaminerNoteChanges', () => { - it('should', () => { + beforeEach(() => { + const mockedDate = new Date(2024, 5, 20); + jest.useFakeTimers(); + jest.setSystemTime(mockedDate); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should handle any changes made to the adr examiner notes', () => { const testNote = { note: 'testNote', - createdAtDate: new Date().toISOString().split('T')[0], + createdAtDate: new Date().toISOString(), lastUpdatedBy: 'someone', }; const state: TechnicalRecordServiceState = { @@ -586,4 +595,29 @@ describe('Vehicle Technical Record Reducer', () => { .toContainEqual(testNote); }); }); + describe('handleUpdateExistingADRExaminerNote', () => { + it('should', () => { + const state: TechnicalRecordServiceState = { + ...initialState, + editingTechRecord: { + systemNumber: 'foo', + createdTimestamp: 'bar', + vin: 'testVin', + techRecord_adrDetails_additionalExaminerNotes: [ + { + note: 'foo', + createdAtDate: 'bar', + lastUpdatedBy: 'foo', + }, + ], + } as unknown as TechRecordType<'put'>, + loading: true, + }; + const newNote = 'foobar'; + const action = updateExistingADRAdditionalExaminerNote({ additionalExaminerNote: newNote, examinerNoteIndex: 0 }); + const newState = vehicleTechRecordReducer(state, action); + const editingTechRecord = newState.editingTechRecord as unknown as (NonVerbTechRecordType<'hgv' | 'lgv' | 'trl'>); + expect(editingTechRecord.techRecord_adrDetails_additionalExaminerNotes![0].note).toEqual(newNote); + }); + }); }); diff --git a/src/app/store/technical-records/reducers/technical-record-service.reducer.ts b/src/app/store/technical-records/reducers/technical-record-service.reducer.ts index 4639b484da..320ce60951 100644 --- a/src/app/store/technical-records/reducers/technical-record-service.reducer.ts +++ b/src/app/store/technical-records/reducers/technical-record-service.reducer.ts @@ -61,7 +61,7 @@ import { updateBody, updateBrakeForces, updateEditingTechRecord, - updateEditingTechRecordCancel, + updateEditingTechRecordCancel, updateExistingADRAdditionalExaminerNote, updateScrollPosition, updateTechRecord, updateTechRecordFailure, @@ -153,6 +153,8 @@ export const vehicleTechRecordReducer = createReducer( on(updateADRAdditionalExaminerNotes, (state, action) => handleADRExaminerNoteChanges(state, action.username)), + on(updateExistingADRAdditionalExaminerNote, (state, action) => handleUpdateExistingADRExaminerNote(state, action)), + on(addAxle, (state) => handleAddAxle(state)), on(removeAxle, (state, action) => handleRemoveAxle(state, action)), @@ -425,6 +427,7 @@ function handleClearADRDetails(state: TechnicalRecordServiceState) { editingTechRecord: { ...editingTechRecord, techRecord_adrDetails_vehicleDetails_type: null, + techRecord_adrDetails_vehicleDetails_usedOnInternationalJourneys: null, techRecord_adrDetails_vehicleDetails_approvalDate: null, techRecord_adrDetails_permittedDangerousGoods: null, ...nulledCompatibilityGroupJ, @@ -448,6 +451,7 @@ function handleClearADRDetails(state: TechnicalRecordServiceState) { techRecord_adrDetails_additionalNotes_number: null, techRecord_adrDetails_adrTypeApprovalNo: null, techRecord_adrDetails_adrCertificateNotes: null, + techRecord_adrDetails_newCertificateRequested: null, ...nulledTankDetails, }, }; @@ -536,7 +540,7 @@ function handleADRExaminerNoteChanges(state: TechnicalRecordServiceState, userna const additionalExaminerNotes = { note: additionalNoteTechRecord.techRecord_adrDetails_additionalExaminerNotes_note, lastUpdatedBy: username, - createdAtDate: new Date().toISOString().split('T')[0], + createdAtDate: new Date().toISOString(), }; if (additionalNoteTechRecord.techRecord_adrDetails_additionalExaminerNotes === null || additionalNoteTechRecord.techRecord_adrDetails_additionalExaminerNotes === undefined) { @@ -547,3 +551,17 @@ function handleADRExaminerNoteChanges(state: TechnicalRecordServiceState, userna } return { ...state, editingTechRecord: additionalNoteTechRecord as unknown as (TechRecordType<'put'>) }; } + +function handleUpdateExistingADRExaminerNote( + state: TechnicalRecordServiceState, + action: { additionalExaminerNote: string, examinerNoteIndex: number }, +) { + const { editingTechRecord } = state; + const editedTechRecord = editingTechRecord as unknown as + (NonVerbTechRecordType<'hgv' | 'lgv' | 'trl'>); + if (editedTechRecord) { + const examinerNotes = editedTechRecord.techRecord_adrDetails_additionalExaminerNotes; + examinerNotes![action.examinerNoteIndex].note = action.additionalExaminerNote; + } + return { ...state, editingTechRecord: editingTechRecord as unknown as (TechRecordType<'put'>) }; +} diff --git a/src/assets/featureToggle.int.json b/src/assets/featureToggle.int.json index 8c5f462fcb..14c9ac4699 100644 --- a/src/assets/featureToggle.int.json +++ b/src/assets/featureToggle.int.json @@ -2,6 +2,5 @@ "testToggle": false, "adrToggle": true, "adrCertToggle": true, - "requiredStandards": false + "requiredStandards": true } - \ No newline at end of file diff --git a/src/assets/featureToggle.preprod.json b/src/assets/featureToggle.preprod.json index 8c5f462fcb..14c9ac4699 100644 --- a/src/assets/featureToggle.preprod.json +++ b/src/assets/featureToggle.preprod.json @@ -2,6 +2,5 @@ "testToggle": false, "adrToggle": true, "adrCertToggle": true, - "requiredStandards": false + "requiredStandards": true } - \ No newline at end of file diff --git a/src/assets/featureToggle.prod.json b/src/assets/featureToggle.prod.json index 9fd2e25cdb..df58ab6e4b 100644 --- a/src/assets/featureToggle.prod.json +++ b/src/assets/featureToggle.prod.json @@ -2,5 +2,5 @@ "testToggle": false, "adrToggle": true, "adrCertToggle": true, - "requiredStandards": false + "requiredStandards": true }