diff --git a/src/app/carbon-estimator-form/carbon-estimator-form.component.html b/src/app/carbon-estimator-form/carbon-estimator-form.component.html index 416abb0..9d1d37e 100644 --- a/src/app/carbon-estimator-form/carbon-estimator-form.component.html +++ b/src/app/carbon-estimator-form/carbon-estimator-form.component.html @@ -1,5 +1,9 @@
+ @if (showErrorSummary) { + + } +
@@ -14,9 +18,9 @@ required [attr.aria-describedby]="(headCount | invalidated) ? 'headCountError' : null" /> @if (headCount | invalidated) { -
+
error -

The number of employees must be greater than 0.

+

{{ errorConfig.headCount.errorMessage }}

}
@@ -81,9 +85,9 @@ required [attr.aria-describedby]="(numberOfServers | invalidated) ? 'numberOfServersError' : null" /> @if (numberOfServers | invalidated) { -
+
error -

The number of servers must be greater than or equal to 0.

+

{{ errorConfig.numberOfServers.errorMessage }}

}
@@ -210,12 +214,12 @@ required [attr.aria-describedby]="(monthlyActiveUsers | invalidated) ? 'monthlyActiveUsersError' : null" /> @if (monthlyActiveUsers | invalidated) { -
+
error -

- Monthly active users must be greater than 0. To specify no external users, use the - checkbox above. -

+

{{ errorConfig.monthlyActiveUsers.errorMessage }}

}
@@ -258,35 +262,8 @@
- +
- @if (estimatorForm | invalidated) { -
- error -

- Unable to calculate emissions because the following fields are invalid: - @if (headCount?.invalid) { - number of employees  - } - @if (numberOfServers?.invalid) { - number of servers  - } - @if (monthlyActiveUsers?.invalid) { - monthly active users - } -

-
- }
= { - WORLD: 'Globally', - 'NORTH AMERICA': 'in North America', - EUROPE: 'in Europe', - GBR: 'in the UK', - ASIA: 'in Asia', - AFRICA: 'in Africa', - OCEANIA: 'in Oceania', - 'LATIN AMERICA AND CARIBBEAN': 'in Latin America or the Caribbean', -}; +import { ErrorSummaryComponent } from '../error-summary/error-summary.component'; @Component({ selector: 'carbon-estimator-form', @@ -34,6 +32,7 @@ const locationDescriptions: Record = { ExpansionPanelComponent, FormatCostRangePipe, InvalidatedPipe, + ErrorSummaryComponent, ], }) export class CarbonEstimatorFormComponent implements OnInit { @@ -42,6 +41,8 @@ export class CarbonEstimatorFormComponent implements OnInit { @Output() public formSubmit: EventEmitter = new EventEmitter(); @Output() public formReset: EventEmitter = new EventEmitter(); + @ViewChild(ErrorSummaryComponent) errorSummary?: ErrorSummaryComponent; + public estimatorForm!: FormGroup; public formContext = formContext; @@ -69,6 +70,10 @@ export class CarbonEstimatorFormComponent implements OnInit { public questionPanelConfig = questionPanelConfig; + public errorConfig = errorConfig; + public showErrorSummary = false; + public validationErrors: ValidationError[] = []; + constructor( private formBuilder: FormBuilder, private changeDetector: ChangeDetectorRef, @@ -157,9 +162,14 @@ export class CarbonEstimatorFormComponent implements OnInit { } public handleSubmit() { - if (!this.estimatorForm.valid) { + if (this.estimatorForm.invalid) { + this.validationErrors = this.getValidationErrors(); + this.showErrorSummary = true; + this.changeDetector.detectChanges(); + this.errorSummary?.summary.nativeElement.focus(); return; } + this.showErrorSummary = false; const formValue = this.estimatorForm.getRawValue(); if (formValue.onPremise.serverLocation === 'unknown') { formValue.onPremise.serverLocation = 'WORLD'; @@ -197,4 +207,19 @@ export class CarbonEstimatorFormComponent implements OnInit { ); } } + + private getValidationErrors() { + const validationErrors: ValidationError[] = []; + if (this.headCount?.invalid) { + validationErrors.push(this.errorConfig.headCount); + } + if (this.numberOfServers?.invalid) { + validationErrors.push(this.errorConfig.numberOfServers); + } + if (this.monthlyActiveUsers?.invalid) { + validationErrors.push(this.errorConfig.monthlyActiveUsers); + } + + return validationErrors; + } } diff --git a/src/app/carbon-estimator-form/carbon-estimator-form.constants.ts b/src/app/carbon-estimator-form/carbon-estimator-form.constants.ts index c314d2a..3669403 100644 --- a/src/app/carbon-estimator-form/carbon-estimator-form.constants.ts +++ b/src/app/carbon-estimator-form/carbon-estimator-form.constants.ts @@ -1,5 +1,6 @@ import { ExpansionPanelConfig } from '../expansion-panel/expansion-panel.constants'; import { CostRange, EstimatorValues } from '../types/carbon-estimator'; +import { WorldLocation } from '../types/carbon-estimator'; export const costRanges: CostRange[] = [ { min: 0, max: 1000 }, @@ -97,3 +98,34 @@ export const questionPanelConfig: ExpansionPanelConfig = { buttonStyles: 'material-icons-outlined tce-text-base hover:tce-bg-slate-200 hover:tce-rounded', contentContainerStyles: 'tce-px-3 tce-py-2 tce-bg-slate-100 tce-border tce-border-slate-400 tce-rounded tce-text-sm', }; + +export const locationDescriptions: Record = { + WORLD: 'Globally', + 'NORTH AMERICA': 'in North America', + EUROPE: 'in Europe', + GBR: 'in the UK', + ASIA: 'in Asia', + AFRICA: 'in Africa', + OCEANIA: 'in Oceania', + 'LATIN AMERICA AND CARIBBEAN': 'in Latin America or the Caribbean', +}; + +export type ValidationError = { + inputId: string; + errorMessage: string; +}; + +export const errorConfig = { + headCount: { + inputId: 'headCount', + errorMessage: 'The number of employees must be greater than 0', + }, + numberOfServers: { + inputId: 'numberOfServers', + errorMessage: 'The number of servers must be greater than or equal to 0', + }, + monthlyActiveUsers: { + inputId: 'monthlyActiveUsers', + errorMessage: 'The number of monthly active users must be greater than 0', + }, +}; diff --git a/src/app/error-summary/error-summary.component.html b/src/app/error-summary/error-summary.component.html new file mode 100644 index 0000000..bc60abe --- /dev/null +++ b/src/app/error-summary/error-summary.component.html @@ -0,0 +1,10 @@ +
+

There is a problem

+
+ @for (error of validationErrors(); track $index) { + {{ error.errorMessage }} + } +
+
diff --git a/src/app/error-summary/error-summary.component.spec.ts b/src/app/error-summary/error-summary.component.spec.ts new file mode 100644 index 0000000..79de3b3 --- /dev/null +++ b/src/app/error-summary/error-summary.component.spec.ts @@ -0,0 +1,42 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ErrorSummaryComponent } from './error-summary.component'; +import { ValidationError } from '../carbon-estimator-form/carbon-estimator-form.constants'; + +describe('ErrorSummaryComponent', () => { + let component: ErrorSummaryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ErrorSummaryComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ErrorSummaryComponent); + component = fixture.componentInstance; + + const validationErrors: ValidationError[] = [ + { + inputId: 'input1', + errorMessage: 'Input 1 must be greater than 0', + }, + { + inputId: 'input2', + errorMessage: 'Input 2 must be greater than 0', + }, + ]; + + fixture.componentRef.setInput('validationErrors', validationErrors); + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display validation error messages', () => { + expect(fixture.nativeElement.textContent).toContain('Input 1 must be greater than 0'); + expect(fixture.nativeElement.textContent).toContain('Input 2 must be greater than 0'); + }); +}); diff --git a/src/app/error-summary/error-summary.component.ts b/src/app/error-summary/error-summary.component.ts new file mode 100644 index 0000000..ab4436e --- /dev/null +++ b/src/app/error-summary/error-summary.component.ts @@ -0,0 +1,13 @@ +import { Component, ElementRef, input, ViewChild } from '@angular/core'; +import { ValidationError } from '../carbon-estimator-form/carbon-estimator-form.constants'; + +@Component({ + selector: 'error-summary', + standalone: true, + imports: [], + templateUrl: './error-summary.component.html', +}) +export class ErrorSummaryComponent { + validationErrors = input.required(); + @ViewChild('errorSummary') summary!: ElementRef; +} diff --git a/src/app/tech-carbon-estimator/tech-carbon-estimator.component.html b/src/app/tech-carbon-estimator/tech-carbon-estimator.component.html index 36861c9..f494824 100644 --- a/src/app/tech-carbon-estimator/tech-carbon-estimator.component.html +++ b/src/app/tech-carbon-estimator/tech-carbon-estimator.component.html @@ -9,7 +9,7 @@

Technology Carbon Estimator

aria-live="polite" (closeEvent)="closeAssumptionsAndLimitation($event)"> } @else { -
+