Skip to content

Commit

Permalink
feat/cb2-14457 - ADR DFS Refactor (#1640)
Browse files Browse the repository at this point in the history
* chore(cb2-0000): basic form

* chore(cb2-0000): changes

* chore(cb2-0000): add view and summary mode

* chore(cb2-0000): add example directive

* chore(cb2-0000): add dates and directives

* chore(cb2-0000): adr section changes

* chore(cb2-0000): fix issues with global error service

* chore(cb2-0000): fix validation for ADR section

* chore(cb2-0000): fix tc3 validator

* chore(cb2-0000): general ADR section fixes

* chore(cb2-0000): allow editing fields on different pages

* chore(cb2-0000): replicate behaviour around explosives

* chore(cb2-0000): fix unit tests

* feat(cb2-14457): migrate styles and directives over

* feat(cb2-14457): attempt at using new styles and directive

* feat(cb2-14457): attempt at using new styles and directive

* feat(cb2-14457): add automation ids

* feat(cb2-14457): amend review screen

* feat(cb2-14457): convert inputs to gds format

* feat(cb2-14457): add body declaration

* feat(cb2-14457): add ids to input labels

* feat(cb2-14457): migrate last of inputs to gds

* feat(cb2-14457): more radios

* feat(cb2-14457): final radio

* feat(cb2-14457): make dates less verbose

* feat(cb2-14457): checkbox styling and small fixes

* feat(cb2-14457): checkbox styling

* feat(cb2-14457): directives rework

* feat(cb2-14457): selects refactored

* feat(cb2-14457): fix date errors

* feat(cb2-14457): text area refactor

* feat(cb2-14457): introduce styling for dates

* feat(cb2-14457): label cleanup

* feat(cb2-14457): fix date styling and unit tests

* Feat/cb2 14457 (#1642)

* chore(cb2-0000): basic form

* chore(cb2-0000): changes

* chore(cb2-0000): add view and summary mode

* chore(cb2-0000): add example directive

* chore(cb2-0000): add dates and directives

* chore(cb2-0000): adr section changes

* chore(cb2-0000): fix issues with global error service

* chore(cb2-0000): fix validation for ADR section

* chore(cb2-0000): fix tc3 validator

* chore(cb2-0000): general ADR section fixes

* chore(cb2-0000): allow editing fields on different pages

* chore(cb2-0000): replicate behaviour around explosives

* chore(cb2-0000): fix unit tests

* feat(cb2-14457): migrate styles and directives over

* feat(cb2-14457): attempt at using new styles and directive

* feat(cb2-14457): attempt at using new styles and directive

* feat(cb2-14457): add automation ids

* feat(cb2-14457): amend review screen

* feat(cb2-14457): convert inputs to gds format

* feat(cb2-14457): add body declaration

* feat(cb2-14457): add ids to input labels

* feat(cb2-14457): migrate last of inputs to gds

* feat(cb2-14457): more radios

* feat(cb2-15038): allow current year +1 for year of manufacture (#1639)

* feat(cb2-14457): final radio

* feat(cb2-14457): make dates less verbose

* feat(cb2-14457): checkbox styling and small fixes

* feat(cb2-14457): checkbox styling

* feat(cb2-14457): directives rework

* feat(cb2-14457): selects refactored

* feat(cb2-14457): fix date errors

* feat(cb2-14457): text area refactor

* feat(cb2-14457): introduce styling for dates

* feat(cb2-14457): label cleanup

* feat(cb2-14457): fix date styling and unit tests

---------

Co-authored-by: Thomas Crawley <thomas.crawley@dvsa.gov.uk>

* feat(cb2-14457): don't override element id

* feat(cb2-14457): remove id and name

* feat(cb2-14457): remove id and name (#1643)

Co-authored-by: pbardy2000 <146740183+pbardy2000@users.noreply.github.com>

* feat(cb2-14457): add checkbox to ids

* Feat/cb2 14457 (#1644)

* feat(cb2-14457): remove id and name

* feat(cb2-14457): add checkbox to ids

---------

Co-authored-by: pbardy2000 <146740183+pbardy2000@users.noreply.github.com>

* feat(cb2-14457): character count id fix

* Feat/cb2 14457 - text area id's (#1645)

* feat(cb2-14457): remove id and name

* feat(cb2-14457): add checkbox to ids

* feat(cb2-14457): character count id fix

---------

Co-authored-by: pbardy2000 <146740183+pbardy2000@users.noreply.github.com>

* feat(cb2-14457): character count corrections

* feat(cb2-14457): add additional details

* feat(cb2-14457): resolve validator message mismatch

* feat/cb2-14457 (#1646)

* feat(cb2-14457): remove id and name

* feat(cb2-14457): add checkbox to ids

* feat(cb2-14457): character count id fix

* feat(cb2-14457): character count corrections

* feat(cb2-14457): add additional details

* feat(cb2-14457): resolve validator message mismatch

---------

Co-authored-by: pbardy2000 <146740183+pbardy2000@users.noreply.github.com>

* feat(cb2-14457): amend additional examiner notes validator

* Feat/cb2 14457 (#1647)

* feat(cb2-14457): remove id and name

* feat(cb2-14457): add checkbox to ids

* feat(cb2-14457): character count id fix

* feat(cb2-14457): character count corrections

* feat(cb2-14457): add additional details

* feat(cb2-14457): resolve validator message mismatch

* feat(cb2-14457): amend additional examiner notes validator

---------

Co-authored-by: pbardy2000 <146740183+pbardy2000@users.noreply.github.com>

* feat(cb2-14457): more additional examiner notes amendments

* feat(cb2-14457): add ids to inline error messages

* feat(cb2-14457): change to using element id, use correct id in label fors

* feat(cb2-14457): fix labels

* feat(cb2-14457): fix battery list applicable validation

* feat(cb2-14457): test

* feat(cb2-14457): fix error messages for automation pack

* feat(cb2-14457): fix ADR specific issues

* feat(cb2-14457): correct max length validation

* feat(cb2-14457): correct more messages

* feat(cb2-14457): fix batch plating

* feat(cb2-14457): change wording

* feat(cb2-14457): fix spelling

* feat(cb2-14457): change date validation

* feat(cb2-14457): year must be four digits

* feat(cb2-14457): fix discrepancies with develop

* feat(cb2-14457): add missing details

* feat(cb2-14457): only show brake endurance with issue section

* feat(cb2-14457): show brake endurance with issuer

* feat(cb2-14457): improvments

* feat(cb2-14457): improvments again

* feat(cb2-14457): change conditions for displaying ADR details message

* feat(cb2-14457): turn flags on

---------

Co-authored-by: pbardy2000 <146740183+pbardy2000@users.noreply.github.com>
Co-authored-by: Tom Evans <thomas.evans@dvsa.gov.uk>
  • Loading branch information
3 people authored Nov 25, 2024
1 parent cc22891 commit 2634cf0
Show file tree
Hide file tree
Showing 56 changed files with 4,260 additions and 226 deletions.
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@angular/router": "^18.2.7",
"@azure/msal-angular": "^3.0.24",
"@azure/msal-browser": "^3.24.0",
"@dvsa/cvs-type-definitions": "^7.6.1",
"@dvsa/cvs-type-definitions": "^7.6.2",
"@ngrx/effects": "^18.0.2",
"@ngrx/entity": "^18.0.2",
"@ngrx/operators": "^18.1.0",
Expand All @@ -53,7 +53,7 @@
"accessible-autocomplete": "^2.0.4",
"angular-google-tag-manager": "^1.10.0",
"deep-object-diff": "^1.1.9",
"govuk-frontend": "^4.7.0",
"govuk-frontend": "^4.8.0",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"lodash.clonedeep": "^4.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h2 class="govuk-error-summary__title" id="error-summary-title">There is a probl
<div class="govuk-error-summary__body">
<ul class="govuk-list govuk-error-summary__list">
<li *ngFor="let error of errorMessages">
<a [routerLink]="" queryParamsHandling="preserve" [fragment]="error.anchorLink" (click)="goto(error)">{{ error.error }}</a>
<a id="{{ error.anchorLink }}-global-error" href="javascript:void(0)" queryParamsHandling="preserve" (click)="goto(error)">{{ error.error }}</a>
</li>
</ul>
</div>
Expand Down
53 changes: 53 additions & 0 deletions src/app/core/components/global-error/global-error.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors } from '@angular/forms';
import { Store, select } from '@ngrx/store';
import { State } from '@store/.';
import { globalErrorState } from '@store/global-error/global-error-service.reducer';
Expand Down Expand Up @@ -56,4 +57,56 @@ export class GlobalErrorService {
}
});
}

extractErrors = (control: AbstractControl) => {
const errors: ValidationErrors = {};

if (control instanceof FormControl) {
Object.entries(control.errors || {}).forEach(([key, error]) => {
errors[key] = error;
});

return errors;
}

if (control instanceof FormGroup || control instanceof FormArray) {
Object.values(control.controls).forEach((control) => {
if (control instanceof FormGroup || control instanceof FormArray) {
this.extractErrors(control);
} else if (control.invalid && control.errors) {
Object.entries(control.errors).forEach(([key, error]) => {
errors[key] = error;
});
}
});
}

return errors;
};

extractGlobalErrors = (form: FormGroup | FormArray) => {
const errors: GlobalError[] = [];

// For each control in the form, determine its validity and collect form errors
Object.entries(form.controls).forEach(([key, control]) => {
control.updateValueAndValidity();

// For nested form groups/arrays, collect the top level errors, then recusively collect errors from their children
if (control instanceof FormGroup || control instanceof FormArray) {
if (control.invalid && control.errors) {
Object.values(control.errors).forEach((error) => {
errors.push({ error, anchorLink: key });
});
}

errors.push(...this.extractGlobalErrors(control));
} else if (control.invalid && control.errors) {
Object.values(control.errors).forEach((error) => {
errors.push({ error, anchorLink: key });
});
}
});

return errors;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ describe('NumberOnlyDirective', () => {

beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [NumberOnlyDirective, TestComponent],
imports: [NumberOnlyDirective],
declarations: [TestComponent],
}).createComponent(TestComponent);
fixture.detectChanges();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Directive, ElementRef, HostListener } from '@angular/core';

@Directive({
selector: '[appNumberOnly]',
standalone: true,
})
export class NumberOnlyDirective {
inputElement: HTMLInputElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ describe('FocusNextDirective', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TestComponent, DateFocusNextDirective],
imports: [DateFocusNextDirective],
declarations: [TestComponent],
}).compileComponents();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
selector: '[appFocusNext]',
standalone: true,
})
export class DateFocusNextDirective {
@Input() displayTime = false;
Expand Down
41 changes: 41 additions & 0 deletions src/app/directives/govuk-checkbox/govuk-checkbox.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Directive, ElementRef, OnDestroy, OnInit, inject, input } from '@angular/core';
import { ControlContainer } from '@angular/forms';
import { FormNodeWidth } from '@services/dynamic-forms/dynamic-form.types';
import { ReplaySubject, takeUntil } from 'rxjs';

@Directive({
selector: '[govukCheckbox]',
})
export class GovukCheckboxDirective implements OnInit, OnDestroy {
elementRef = inject<ElementRef<HTMLInputElement>>(ElementRef);
controlContainer = inject(ControlContainer);

controlName = input<string>('', { alias: 'formControlName' });
width = input<FormNodeWidth>();

destroy$ = new ReplaySubject<boolean>(1);

ngOnInit(): void {
const controlName = this.controlName();
const control = this.controlContainer.control?.get(controlName);
if (control) {
this.elementRef.nativeElement.setAttribute('aria-labelledby', `${controlName}-label`);
control.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((statusChange) => {
if (statusChange === 'INVALID' && control.touched) {
this.elementRef.nativeElement.classList.add('govuk-checkbox--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', `${controlName}-error`);
}

if (statusChange === 'VALID') {
this.elementRef.nativeElement.classList.remove('govuk-checkbox--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', '');
}
});
}
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
52 changes: 52 additions & 0 deletions src/app/directives/govuk-date-input/govuk-date-input.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Directive, ElementRef, OnDestroy, OnInit, inject, input } from '@angular/core';
import { ControlContainer } from '@angular/forms';
import { NumberOnlyDirective } from '@directives/app-number-only/app-number-only.directive';
import { DateFocusNextDirective } from '@directives/date-focus-next/date-focus-next.directive';
import { ReplaySubject, takeUntil } from 'rxjs';

@Directive({
selector: '[govukDateInput]',
standalone: true,
hostDirectives: [DateFocusNextDirective, NumberOnlyDirective],
})
export class GovukDateInputDirective implements OnInit, OnDestroy {
elementRef = inject<ElementRef<HTMLInputElement>>(ElementRef);
controlContainer = inject(ControlContainer);

govukDateInput = input.required<string>();
controlName = input.required<string>({ alias: 'formControlName' });

destroy$ = new ReplaySubject<boolean>(1);

ngOnInit(): void {
const parent = this.govukDateInput();
const controlName = this.controlName();
const id = `${parent}-${controlName}`;

const control = this.controlContainer.control?.get(controlName);
if (control) {
this.elementRef.nativeElement.setAttribute('id', id);
this.elementRef.nativeElement.setAttribute('name', id);
this.elementRef.nativeElement.setAttribute('type', 'number');
this.elementRef.nativeElement.setAttribute('inputmode', 'numeric');
this.elementRef.nativeElement.classList.add('govuk-input', 'govuk-date-input__input');

control.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((statusChange) => {
if (statusChange === 'INVALID' && control.touched) {
this.elementRef.nativeElement.classList.add('govuk-input--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', `${controlName}-error`);
}

if (statusChange === 'VALID') {
this.elementRef.nativeElement.classList.remove('govuk-input--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', '');
}
});
}
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
43 changes: 43 additions & 0 deletions src/app/directives/govuk-input/govuk-input.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Directive, ElementRef, OnDestroy, OnInit, inject, input } from '@angular/core';
import { ControlContainer } from '@angular/forms';
import { ReplaySubject, takeUntil } from 'rxjs';

@Directive({
selector: '[govukInput]',
})
export class GovukInputDirective implements OnInit, OnDestroy {
elementRef = inject<ElementRef<HTMLInputElement>>(ElementRef);
controlContainer = inject(ControlContainer);

controlName = input.required<string>({ alias: 'formControlName' });

destroy$ = new ReplaySubject<boolean>(1);

ngOnInit(): void {
const controlName = this.controlName();
const control = this.controlContainer.control?.get(controlName);
if (control) {
this.elementRef.nativeElement.setAttribute('id', controlName);
this.elementRef.nativeElement.setAttribute('name', controlName);
this.elementRef.nativeElement.setAttribute('aria-labelledby', `${controlName}-label`);
this.elementRef.nativeElement.classList.add('govuk-input');

control.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((statusChange) => {
if (statusChange === 'INVALID' && control.touched) {
this.elementRef.nativeElement.classList.add('govuk-input--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', `${controlName}-error`);
}

if (statusChange === 'VALID') {
this.elementRef.nativeElement.classList.remove('govuk-input--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', '');
}
});
}
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
43 changes: 43 additions & 0 deletions src/app/directives/govuk-radio/govuk-radio.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Directive, ElementRef, OnDestroy, OnInit, inject, input } from '@angular/core';
import { ControlContainer } from '@angular/forms';
import { FormNodeWidth } from '@services/dynamic-forms/dynamic-form.types';
import { ReplaySubject, takeUntil } from 'rxjs';

@Directive({
selector: '[govukRadio]',
})
export class GovukRadioDirective implements OnInit, OnDestroy {
elementRef = inject<ElementRef<HTMLInputElement>>(ElementRef);
controlContainer = inject(ControlContainer);

controlName = input.required<string>({ alias: 'formControlName' });
width = input<FormNodeWidth>();

destroy$ = new ReplaySubject<boolean>(1);

ngOnInit(): void {
this.elementRef.nativeElement.classList.add('govuk-radios__input');

const controlName = this.controlName();
const control = this.controlContainer.control?.get(controlName);
if (control) {
this.elementRef.nativeElement.setAttribute('aria-labelledby', `${controlName}-label`);
control.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((statusChange) => {
if (statusChange === 'INVALID' && control.touched) {
this.elementRef.nativeElement.classList.add('govuk-radio--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', `${controlName}-error`);
}

if (statusChange === 'VALID') {
this.elementRef.nativeElement.classList.remove('govuk-radio--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', '');
}
});
}
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
43 changes: 43 additions & 0 deletions src/app/directives/govuk-select/govuk-select.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Directive, ElementRef, inject, input } from '@angular/core';
import { ControlContainer } from '@angular/forms';
import { ReplaySubject, takeUntil } from 'rxjs';

@Directive({
selector: '[govukSelect]',
})
export class GovukSelectDirective {
elementRef = inject<ElementRef<HTMLSelectElement>>(ElementRef);
controlContainer = inject(ControlContainer);

controlName = input.required<string>({ alias: 'formControlName' });

destroy$ = new ReplaySubject<boolean>(1);

ngOnInit(): void {
const controlName = this.controlName();
const control = this.controlContainer.control?.get(controlName);
if (control) {
this.elementRef.nativeElement.setAttribute('id', controlName);
this.elementRef.nativeElement.setAttribute('name', controlName);
this.elementRef.nativeElement.setAttribute('aria-labelledby', `${controlName}-label`);
this.elementRef.nativeElement.classList.add('govuk-select');

control.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((statusChange) => {
if (statusChange === 'INVALID' && control.touched) {
this.elementRef.nativeElement.classList.add('govuk-select--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', `${controlName}-error`);
}

if (statusChange === 'VALID') {
this.elementRef.nativeElement.classList.remove('govuk-select--error');
this.elementRef.nativeElement.setAttribute('aria-describedby', '');
}
});
}
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
Loading

0 comments on commit 2634cf0

Please sign in to comment.