src/app/features/dashboard-widgets/progress-dashboard-widget/edit-progress-dashboard/edit-progress-dashboard.component.ts

Implements

OnInit

Metadata

Relationships

Used by

No results matching.

Depends on

Index

Properties
Methods

Methods

addPart
addPart()
Returns : void
createPartForm
createPartForm(part: ProgressDashboardPart)
Parameters :
Name Type Optional
part ProgressDashboardPart No
Returns : any
currentLessThanTarget
currentLessThanTarget(control: TypedFormGroup<ProgressDashboardPart>)
Parameters :
Name Type Optional
control TypedFormGroup<ProgressDashboardPart> No
Returns : ValidationErrors | null
removePart
removePart(index: number)
Parameters :
Name Type Optional
index number No
Returns : void

Properties

Readonly currentErrorStateMatcher
Type : ErrorStateMatcher
Default value : { isErrorState: (control: FormControl | null) => !control?.parent?.valid, }

This marks the control as invalid when the whole form has an error

outputData
Type : FormGroup
parts
Type : FormArray
title
Type : FormControl
import {
  Component,
  inject,
  OnInit,
  ChangeDetectionStrategy,
} from "@angular/core";
import { MAT_DIALOG_DATA, MatDialogModule } from "@angular/material/dialog";
import {
  ProgressDashboardConfig,
  ProgressDashboardPart,
} from "../progress-dashboard/progress-dashboard-config";
import {
  FormArray,
  FormBuilder,
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  ValidationErrors,
  Validators,
} from "@angular/forms";
import { ErrorStateMatcher } from "@angular/material/core";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { DialogCloseComponent } from "../../../../core/common-components/dialog-close/dialog-close.component";
import { MatButtonModule } from "@angular/material/button";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { MatTooltipModule } from "@angular/material/tooltip";
import { TypedFormGroup } from "#src/app/core/common-components/entity-form/entity-form";

export interface EditProgressDashboardComponentData {
  title: string;
  parts: ProgressDashboardPart[];
}

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-edit-progress-dashboard",
  templateUrl: "./edit-progress-dashboard.component.html",
  styleUrls: ["./edit-progress-dashboard.component.scss"],
  imports: [
    MatDialogModule,
    MatFormFieldModule,
    ReactiveFormsModule,
    MatInputModule,
    DialogCloseComponent,
    MatButtonModule,
    FontAwesomeModule,
    MatTooltipModule,
  ],
})
export class EditProgressDashboardComponent implements OnInit {
  private data = inject<ProgressDashboardConfig>(MAT_DIALOG_DATA);
  private fb = inject(FormBuilder);

  /**
   * This marks the control as invalid when the whole form has an error
   */
  readonly currentErrorStateMatcher: ErrorStateMatcher = {
    isErrorState: (control: FormControl | null) => !control?.parent?.valid,
  };

  title: FormControl;
  parts: FormArray;
  outputData: FormGroup;

  ngOnInit(): void {
    this.title = new FormControl(this.data.title, [Validators.required]);
    this.parts = this.fb.array(
      this.data.parts.map((part) => this.createPartForm(part)),
    );
    this.outputData = new FormGroup({
      title: this.title,
      parts: this.parts,
    });
  }

  createPartForm(part: ProgressDashboardPart) {
    return this.fb.group(
      {
        label: this.fb.control(part.label, [Validators.required]),
        currentValue: this.fb.control(part.currentValue, [
          Validators.required,
          Validators.min(0),
        ]),
        targetValue: this.fb.control(part.targetValue, [
          Validators.required,
          Validators.min(0),
        ]),
      },
      {
        validators: [this.currentLessThanTarget],
      },
    );
  }

  currentLessThanTarget(
    control: TypedFormGroup<ProgressDashboardPart>,
  ): ValidationErrors | null {
    const current = control.get("currentValue");
    const target = control.get("targetValue");
    if (current.value > target.value) {
      return {
        currentGtTarget: true,
      };
    } else {
      return null;
    }
  }

  addPart() {
    const newPart: ProgressDashboardPart = {
      label: $localize`:Part of a whole:Part`,
      currentValue: 1,
      targetValue: 10,
    };
    this.parts.push(this.createPartForm(newPart));
  }

  removePart(index: number) {
    this.parts.removeAt(index);
  }
}
<div mat-dialog-title style="display: flex">
  <mat-form-field class="title-field margin-top-small">
    <input
      [formControl]="title"
      matInput
      type="text"
      i18n-placeholder="Edit the progress of one or multiple tasks"
      placeholder="Title"
    />
    @if (title.hasError("required")) {
      <mat-error i18n> This field is required </mat-error>
    }
  </mat-form-field>

  <app-dialog-close mat-dialog-close></app-dialog-close>
</div>

<mat-dialog-content class="dialog-content">
  @for (control of parts.controls; track $index; let index = $index) {
    <div class="entry-wrapper mat-elevation-z1" [formGroup]="$any(control)">
      <mat-form-field class="header-field">
        <input
          formControlName="label"
          matInput
          type="text"
          i18n-placeholder="The label of a process or task"
          placeholder="Task"
        />
        @if (control.hasError("required", "label")) {
          <mat-error i18n> This field is required </mat-error>
        }
      </mat-form-field>
      <button mat-icon-button color="accent" (click)="removePart(index)">
        <fa-icon
          class="button-icon"
          i18n-aria-label
          aria-label="remove element"
          icon="trash"
        ></fa-icon>
      </button>
      <mat-form-field class="current-field">
        <input
          formControlName="currentValue"
          matInput
          type="number"
          i18n-placeholder="
            Current process|The Current value of a process or task
          "
          placeholder="Current"
          [errorStateMatcher]="currentErrorStateMatcher"
        />
        @if (control.hasError("required", "currentValue")) {
          <mat-error i18n> This field is required </mat-error>
        }
        @if (control.hasError("min", "currentValue")) {
          <mat-error i18n> Must be greater than 0 </mat-error>
        }
        @if (control.hasError("currentGtTarget")) {
          <mat-error
            i18n="
              The number entered in this form is less than another field but
              should not be
            "
          >
            Must not be greater than target
          </mat-error>
        }
      </mat-form-field>
      <mat-form-field class="target-field">
        <input
          formControlName="targetValue"
          matInput
          type="number"
          i18n-placeholder="Target process|The target amount of a process"
          placeholder="Target"
        />
        @if (control.hasError("required", "targetValue")) {
          <mat-error
            i18n="A field entered in a form is required and wasn't provided"
          >
            This field is required
          </mat-error>
        }
        @if (control.hasError("min", "targetValue")) {
          <mat-error i18n="A field entered in a form must be greater than 0">
            Must be greater than 0
          </mat-error>
        }
      </mat-form-field>
    </div>
  }

  <div>
    <button mat-button color="accent" (click)="addPart()">
      <fa-icon
        class="button-icon margin-sides-small"
        aria-hidden="true"
        icon="plus-circle"
      ></fa-icon>
      <span i18n="Add a task to the progress dashboard list">
        Add New Task
      </span>
    </button>
  </div>
</mat-dialog-content>
<mat-dialog-actions>
  <button
    mat-raised-button
    color="accent"
    [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>

./edit-progress-dashboard.component.scss

@use "variables/sizes";

.entry-wrapper {
  display: grid;
  grid-template-columns: repeat(2, 1fr) 64px;
  grid-template-rows: repeat(2, 1fr);
  grid-gap: sizes.$small;
  grid-template-areas:
    "header header trash"
    "current target trash";
  padding: 12px;
}

.entry-wrapper:not(:last-child) {
  margin-bottom: sizes.$large;
}

.entry-wrapper:last-child {
  margin-bottom: sizes.$regular;
}

.title-field {
  width: 90%;
  min-width: 0;

  input {
    font-size: 32px;
  }
}

.header-field {
  grid-area: header;
  min-width: 0;
}

.current-field {
  grid-area: current;
  min-width: 0;
}

.target-field {
  grid-area: target;
  min-width: 0;
}

.dialog-content {
  @media (max-height: 800px) {
    height: 40vh;
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""