diff --git a/package-lock.json b/package-lock.json index 5fbd990f6b..6af25c3b9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "@babel/core": "^7.18.13", "@compodoc/compodoc": "^1.1.19", "@cypress/schematic": "~2.0.3", + "@oasisdigital/angular-typed-forms-helpers": "^1.3.2", "@schematics/angular": "^14.2.0", "@storybook/addon-actions": "^6.5.10", "@storybook/addon-essentials": "^6.5.10", @@ -4732,6 +4733,15 @@ "tao": "index.js" } }, + "node_modules/@oasisdigital/angular-typed-forms-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@oasisdigital/angular-typed-forms-helpers/-/angular-typed-forms-helpers-1.3.2.tgz", + "integrity": "sha512-dLATZYh+mspxdYz9UQRXfsctSMlHNjmxUxOoHXYrdxXm7kgSXEbv/gpogtSuwQg26WQM0lNRtixoLvqO7LNwWg==", + "dev": true, + "dependencies": { + "@angular/forms": "^14.0.1" + } + }, "node_modules/@parcel/watcher": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", @@ -36217,6 +36227,15 @@ "nx": "14.5.10" } }, + "@oasisdigital/angular-typed-forms-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@oasisdigital/angular-typed-forms-helpers/-/angular-typed-forms-helpers-1.3.2.tgz", + "integrity": "sha512-dLATZYh+mspxdYz9UQRXfsctSMlHNjmxUxOoHXYrdxXm7kgSXEbv/gpogtSuwQg26WQM0lNRtixoLvqO7LNwWg==", + "dev": true, + "requires": { + "@angular/forms": "^14.0.1" + } + }, "@parcel/watcher": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", @@ -36641,7 +36660,7 @@ "ts-loader": "^8.0.14", "tsconfig-paths-webpack-plugin": "^3.3.0", "util-deprecate": "^1.0.2", - "webpack": ">=4.0.0 <6.0.0" + "webpack": "5.74.0" }, "dependencies": { "@types/react": { @@ -36852,7 +36871,7 @@ "ts-dedent": "^2.0.0", "url-loader": "^4.1.1", "util-deprecate": "^1.0.2", - "webpack": "4", + "webpack": "5.74.0", "webpack-dev-middleware": "^3.7.3", "webpack-filter-warnings-plugin": "^1.2.1", "webpack-hot-middleware": "^2.25.1", @@ -37587,7 +37606,7 @@ "terser-webpack-plugin": "^5.0.3", "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2", - "webpack": "^5.9.0", + "webpack": "5.74.0", "webpack-dev-middleware": "^4.1.0", "webpack-hot-middleware": "^2.25.1", "webpack-virtual-modules": "^0.4.1" @@ -38007,7 +38026,7 @@ "telejson": "^6.0.8", "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2", - "webpack": "4" + "webpack": "5.74.0" }, "dependencies": { "@babel/helper-define-polyfill-provider": { @@ -38267,7 +38286,7 @@ "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2", "watchpack": "^2.2.0", - "webpack": "4", + "webpack": "5.74.0", "ws": "^8.2.3", "x-default-browser": "^0.4.0" }, @@ -38452,7 +38471,7 @@ "ts-dedent": "^2.0.0", "url-loader": "^4.1.1", "util-deprecate": "^1.0.2", - "webpack": "4", + "webpack": "5.74.0", "webpack-dev-middleware": "^3.7.3", "webpack-virtual-modules": "^0.2.2" }, @@ -39199,7 +39218,7 @@ "terser-webpack-plugin": "^5.0.3", "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2", - "webpack": "^5.9.0", + "webpack": "5.74.0", "webpack-dev-middleware": "^4.1.0", "webpack-virtual-modules": "^0.4.1" }, diff --git a/package.json b/package.json index 2585e111e6..57a8b30654 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@babel/core": "^7.18.13", "@compodoc/compodoc": "^1.1.19", "@cypress/schematic": "~2.0.3", + "@oasisdigital/angular-typed-forms-helpers": "^1.3.2", "@schematics/angular": "^14.2.0", "@storybook/addon-actions": "^6.5.10", "@storybook/addon-essentials": "^6.5.10", diff --git a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.html b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.html index 28006ef5e1..77947a66cb 100644 --- a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.html +++ b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.html @@ -1,9 +1,21 @@
-

Edit Progress

+ + + + This field is required + +
-
+
Edit Progress i18n-placeholder="Process Label|The label of a process or task" placeholder="Task" /> + + This field is required +
- + diff --git a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.scss b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.scss index d348085cf6..b80d92adcf 100644 --- a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.scss +++ b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.scss @@ -18,6 +18,14 @@ margin-bottom: 12px; } +.title-field { + input{ + font-size: 32px; + } + width: 100%; + min-width: 0; +} + .header-field { grid-area: header; min-width: 0; diff --git a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.spec.ts b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.spec.ts index 70c0ef40c5..97ab2d2f71 100644 --- a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.spec.ts +++ b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.spec.ts @@ -5,15 +5,14 @@ import { EditProgressDashboardComponentData, } from "./edit-progress-dashboard.component"; import { MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { ReactiveFormsModule, FormBuilder } from "@angular/forms"; -import { TypedForm } from "../../../core/entity-components/entity-form/entity-form.service"; -import { ProgressDashboardPart } from "../progress-dashboard/progress-dashboard-config"; +import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; describe("EditProgressDashboardComponent", () => { let component: EditProgressDashboardComponent; let fixture: ComponentFixture; const mockDialogData: EditProgressDashboardComponentData = { + title: "qwe", parts: [ { label: "foo", @@ -47,8 +46,8 @@ describe("EditProgressDashboardComponent", () => { }).compileComponents(); }); - function getGroup(index: number): TypedForm { - return component.forms.at(index); + function getGroup(index: number): FormGroup { + return component.parts.at(index) as FormGroup; } beforeEach(() => { @@ -62,19 +61,30 @@ describe("EditProgressDashboardComponent", () => { }); it("should contain the initial state from the data", () => { - expect(component.forms).toHaveValue(mockDialogData.parts); - expect(component.forms).toBeValidForm(); + expect(component.parts).toHaveValue(mockDialogData.parts); + expect(component.parts).toBeValidForm(); + + expect(component.title).toHaveValue(mockDialogData.title); + expect(component.title).toBeValidForm(); + }); + + it("should mark form as invalid when title is empty", () => { + component.title.setValue(""); + expect(component.title).toHaveValue(""); + expect(component.title).toContainFormError("required"); + + expect(component.title).not.toBeValidForm(); }); it("should append a new part", () => { component.addPart(); - expect(component.forms).toHaveSize(4); + expect(component.parts).toHaveSize(4); }); it("should delete a part", () => { component.removePart(1); - expect(component.forms).toHaveSize(2); - expect(component.forms).toHaveValue([ + expect(component.parts).toHaveSize(2); + expect(component.parts).toHaveValue([ mockDialogData.parts[0], mockDialogData.parts[2], ]); @@ -82,9 +92,9 @@ describe("EditProgressDashboardComponent", () => { it("should mark the form as invalid when current or target is not present", () => { const firstForm = getGroup(0); - firstForm.get("currentValue").setValue(undefined); + firstForm.get("currentValue").setValue(""); expect(firstForm.get("currentValue")).toContainFormError("required"); - firstForm.get("targetValue").setValue(undefined); + firstForm.get("targetValue").setValue(""); expect(firstForm.get("targetValue")).toContainFormError("required"); expect(firstForm).not.toBeValidForm(); diff --git a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.ts b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.ts index 15bb0528a4..eb56f9ed63 100644 --- a/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.ts +++ b/src/app/features/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.ts @@ -1,18 +1,21 @@ import { Component, Inject } from "@angular/core"; import { MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { ProgressDashboardPart } from "../progress-dashboard/progress-dashboard-config"; import { - UntypedFormControl, - FormGroupDirective, - NgForm, + ProgressDashboardPart, + ProgressDashboardConfig, +} from "../progress-dashboard/progress-dashboard-config"; +import { + FormBuilder, + FormControl, + FormGroup, ValidationErrors, Validators, - FormBuilder, } from "@angular/forms"; import { ErrorStateMatcher } from "@angular/material/core"; -import { TypedForm } from "../../../core/entity-components/entity-form/entity-form.service"; +import { AngularForm } from "@oasisdigital/angular-typed-forms-helpers"; export interface EditProgressDashboardComponentData { + title: string; parts: ProgressDashboardPart[]; } @@ -22,18 +25,31 @@ export interface EditProgressDashboardComponentData { styleUrls: ["./edit-progress-dashboard.component.scss"], }) export class EditProgressDashboardComponent { - forms = this.fb.array(this.data.parts.map((part) => this.formGroup(part))); - currentErrorStateMatcher = new FormCurrentErrorStateMatcher(); + /** + * This marks the control as invalid when the whole form has an error + */ + readonly currentErrorStateMatcher: ErrorStateMatcher = { + isErrorState: (control: FormControl | null) => !control?.parent?.valid, + }; + + title = new FormControl(this.data.title, [Validators.required]); + parts = this.fb.array( + this.data.parts.map((part) => this.createPartForm(part)) + ); + outputData = new FormGroup({ + title: this.title, + parts: this.parts, + }); constructor( - @Inject(MAT_DIALOG_DATA) public data: EditProgressDashboardComponentData, + @Inject(MAT_DIALOG_DATA) private data: ProgressDashboardConfig, private fb: FormBuilder ) {} - formGroup(part: ProgressDashboardPart) { + createPartForm(part: ProgressDashboardPart) { return this.fb.group( { - label: this.fb.control(part.label), + label: this.fb.control(part.label, [Validators.required]), currentValue: this.fb.control(part.currentValue, [ Validators.required, Validators.min(0), @@ -50,7 +66,7 @@ export class EditProgressDashboardComponent { } currentLessThanTarget( - control: TypedForm + control: AngularForm ): ValidationErrors | null { const current = control.get("currentValue"); const target = control.get("targetValue"); @@ -69,25 +85,10 @@ export class EditProgressDashboardComponent { currentValue: 1, targetValue: 10, }; - this.forms.push(this.formGroup(newPart)); - } - - get tooltipOnSave(): string { - return this.forms.valid - ? "" - : $localize`:Shown when there are errors that prevent saving:Fix the errors to save the form`; + this.parts.push(this.createPartForm(newPart)); } removePart(index: number) { - this.forms.removeAt(index); - } -} - -class FormCurrentErrorStateMatcher implements ErrorStateMatcher { - isErrorState( - control: UntypedFormControl | null, - form: FormGroupDirective | NgForm | null - ): boolean { - return !control?.parent?.valid; + this.parts.removeAt(index); } } diff --git a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts index 3a4aba1253..73daeb41db 100644 --- a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts +++ b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts @@ -71,7 +71,7 @@ export class ProgressDashboardComponent .afterClosed() .subscribe(async (next) => { if (next) { - this.data.parts = next; + Object.assign(this.data, next); await this.save(); } });