Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/wip 1139 title progress #1313

Merged
merged 15 commits into from
Sep 26, 2022
33 changes: 26 additions & 7 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
<div mat-dialog-title style="display: flex">
<h1 i18n="Edit the progress of one or multiple tasks">Edit Progress</h1>
<mat-form-field class="title-field">
<input
[formControl]="title"
matInput
type="text"
i18n-placeholder="Title Progress|Edit the progress of one or multiple tasks"
placeholder="Title"
/>
<mat-error *ngIf="title.hasError('required')" i18n>
This field is required
</mat-error>
</mat-form-field>
TheSlimvReal marked this conversation as resolved.
Show resolved Hide resolved
</div>

<mat-dialog-content class="dialog-content">
<div *ngFor="let control of forms.controls; let index=index" class="entry-wrapper mat-elevation-z1" [formGroup]="$any(control)">
<div *ngFor="let control of parts.controls; let index=index" class="entry-wrapper mat-elevation-z1"
[formGroup]="$any(control)">
<mat-form-field class="header-field">
<input
formControlName="label"
Expand All @@ -12,6 +24,9 @@ <h1 i18n="Edit the progress of one or multiple tasks">Edit Progress</h1>
i18n-placeholder="Process Label|The label of a process or task"
placeholder="Task"
/>
<mat-error *ngIf="control.hasError('required','label')" i18n>
This field is required
</mat-error>
</mat-form-field>

<button
Expand Down Expand Up @@ -90,12 +105,13 @@ <h1 i18n="Edit the progress of one or multiple tasks">Edit Progress</h1>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button
mat-stroked-button
[mat-dialog-close]="forms.value"
[disabled]="!forms.valid"
>
<span [matTooltip]="tooltipOnSave" i18n>Save</span>
</button>
<button
mat-stroked-button
[mat-dialog-close]="outputData.value"
[disabled]="outputData.invalid"
>
<span matTooltip="Fix the errors to save the form" [matTooltipDisabled]="outputData.valid"
i18n-matTooltip="Shown when there are errors that prevent saving" i18n>Save</span>
</button>
<button mat-stroked-button mat-dialog-close i18n>Cancel</button>
</mat-dialog-actions>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<EditProgressDashboardComponent>;

const mockDialogData: EditProgressDashboardComponentData = {
title: "qwe",
parts: [
{
label: "foo",
Expand Down Expand Up @@ -47,8 +46,8 @@ describe("EditProgressDashboardComponent", () => {
}).compileComponents();
});

function getGroup(index: number): TypedForm<ProgressDashboardPart> {
return component.forms.at(index);
function getGroup(index: number): FormGroup {
return component.parts.at(index) as FormGroup;
}

beforeEach(() => {
Expand All @@ -62,29 +61,40 @@ 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],
]);
});

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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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[];
}

Expand All @@ -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),
Expand All @@ -50,7 +66,7 @@ export class EditProgressDashboardComponent {
}

currentLessThanTarget(
control: TypedForm<ProgressDashboardPart>
control: AngularForm<ProgressDashboardPart>
): ValidationErrors | null {
const current = control.get("currentValue");
const target = control.get("targetValue");
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
});
Expand Down