From d38c4d4f8e57f162b777f68640da76fe624e7b80 Mon Sep 17 00:00:00 2001
From: sebo <sebastian.bergandy@external.mgm-cp.com>
Date: Tue, 1 Apr 2025 10:56:38 +0200
Subject: [PATCH] wip albert sebo validation

---
 .../src/lib/keycloak-formservice.ts           | 21 +++++++--
 .../lib/user-form/user-form.component.html    | 34 +++++++-------
 .../src/lib/user-form/user.formservice.ts     | 44 ++++++-------------
 .../formcontrol-editor.abstract.component.ts  | 10 ++++-
 .../src/lib/button/button.component.ts        |  4 +-
 5 files changed, 60 insertions(+), 53 deletions(-)

diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts
index e1856df17c..2e628b17e0 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts
@@ -21,7 +21,13 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { createEmptyStateResource, InvalidParam, isLoaded, setInvalidParamValidationError, StateResource, } from '@alfa-client/tech-shared';
+import {
+  createEmptyStateResource,
+  InvalidParam,
+  isLoaded,
+  setInvalidParamValidationError,
+  StateResource,
+} from '@alfa-client/tech-shared';
 import { inject, Injectable } from '@angular/core';
 import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms';
 import { ActivatedRoute, UrlSegment } from '@angular/router';
@@ -72,16 +78,24 @@ export abstract class KeycloakFormService<T> {
   }
 
   public submit(): Observable<StateResource<T>> {
+    console.info('value before update and validity: %o', this.form.value);
+    this._showValidationErrorForAllInvalidControls(this.form);
+    console.info('value after update and validity: %o', this.form.value);
+    setTimeout(() => {
+      console.info('form after timeout valid = %s, value = %o', this.form.valid, this.form.value);
+    }, 0);
     if (this.form.invalid) {
+      console.info('form INVALID');
       return this._processInvalidForm();
     }
+    console.info('form VALID');
     return this._doSubmit().pipe(
       catchError((keycloakError: KeycloakHttpErrorResponse) => this._processResponseValidationErrors(keycloakError)),
     );
   }
 
   _processInvalidForm(): Observable<StateResource<T>> {
-    this._showValidationErrorForAllInvalidControls(this.form);
+    // this._showValidationErrorForAllInvalidControls(this.form);
     return of(createEmptyStateResource<T>());
   }
 
@@ -98,7 +112,8 @@ export abstract class KeycloakFormService<T> {
   }
 
   _showValidationErrorForAllInvalidControls(control: AbstractControl): void {
-    if (control.invalid) control.updateValueAndValidity();
+    // if (control.invalid)
+    control.updateValueAndValidity();
     if (control instanceof FormGroup || control instanceof FormArray) {
       Object.values(control.controls).forEach((control) => this._showValidationErrorForAllInvalidControls(control));
     }
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html
index 132dc6ba2c..6b840fbfea 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html
@@ -23,20 +23,22 @@
     unter der Lizenz sind dem Lizenztext zu entnehmen.
 
 -->
-<ods-spinner [stateResource]="userStateResource$ | async">
-  <div class="max-w-[960px]" data-test-id="user-content">
-    <admin-user-form-headline [isPatch]="isPatch" />
-    <admin-user-form-data [formGroupParent]="formService.form" [isPatch]="isPatch" [userName]="userName" />
-    <admin-user-form-roles [formGroupParent]="formService.form" />
-    <admin-user-form-organisations-einheit-list
-      [formGroupParent]="formService.form"
-      [formGroupOrganisationsEinheiten]="formService.getOrganisationsEinheitenGroup()"
-    />
-    <div class="mb-6 flex justify-between">
-      <admin-user-form-save-button />
-      @if (isPatch) {
-        <admin-delete-open-dialog-button data-test-id="delete-button-container" />
-      }
+<form [formGroup]="formService.form">
+  <ods-spinner [stateResource]="userStateResource$ | async">
+    <div class="max-w-[960px]" data-test-id="user-content">
+      <admin-user-form-headline [isPatch]="isPatch" />
+      <admin-user-form-data [formGroupParent]="formService.form" [isPatch]="isPatch" [userName]="userName" />
+      <admin-user-form-roles [formGroupParent]="formService.form" />
+      <admin-user-form-organisations-einheit-list
+        [formGroupParent]="formService.form"
+        [formGroupOrganisationsEinheiten]="formService.getOrganisationsEinheitenGroup()"
+      />
+      <div class="mb-6 flex justify-between">
+        <admin-user-form-save-button />
+        @if (isPatch) {
+          <admin-delete-open-dialog-button data-test-id="delete-button-container" />
+        }
+      </div>
     </div>
-  </div>
-</ods-spinner>
+  </ods-spinner>
+</form>
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts
index b0cb7867c9..9b7cea47cd 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts
@@ -32,19 +32,10 @@ import {
   PatchConfig,
 } from '@admin/keycloak-shared';
 import { NavigationService } from '@alfa-client/navigation-shared';
-import {
-  checkBoxGroupsEmptyValidator,
-  EMPTY_STRING,
-  fieldEmptyValidator,
-  fieldInvalidValidator,
-  fieldLengthValidator,
-  isLoaded,
-  mapToResource,
-  StateResource,
-} from '@alfa-client/tech-shared';
+import { EMPTY_STRING, isLoaded, mapToResource, StateResource } from '@alfa-client/tech-shared';
 import { SnackBarService } from '@alfa-client/ui';
 import { Injectable, OnDestroy } from '@angular/core';
-import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
+import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
 import { UrlSegment } from '@angular/router';
 import { filter, Observable, Subscription, tap } from 'rxjs';
 
@@ -100,15 +91,13 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
   }
 
   _initForm(): UntypedFormGroup {
-    return this.formBuilder.group({
-      [UserFormService.FIRST_NAME]: new FormControl(EMPTY_STRING, fieldEmptyValidator(UserFormService.FIRST_NAME)),
-      [UserFormService.LAST_NAME]: new FormControl(EMPTY_STRING, fieldEmptyValidator(UserFormService.LAST_NAME)),
-      [UserFormService.USERNAME]: new FormControl(EMPTY_STRING, [fieldLengthValidator(UserFormService.USERNAME, 3, 255)]),
-      [UserFormService.EMAIL]: new FormControl(EMPTY_STRING, [
-        fieldInvalidValidator(UserFormService.EMAIL, /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/),
-      ]),
-      [UserFormService.CLIENT_ROLES]: this.formBuilder.group(
-        {
+    return this.formBuilder.group(
+      {
+        [UserFormService.FIRST_NAME]: new FormControl(EMPTY_STRING, Validators.required),
+        [UserFormService.LAST_NAME]: new FormControl(EMPTY_STRING),
+        [UserFormService.USERNAME]: new FormControl(EMPTY_STRING),
+        [UserFormService.EMAIL]: new FormControl(EMPTY_STRING),
+        [UserFormService.CLIENT_ROLES]: this.formBuilder.group({
           [UserFormService.ADMINISTRATION_GROUP]: this.formBuilder.group({
             [UserFormService.ADMIN]: new FormControl(false),
             [UserFormService.DATENBEAUFTRAGUNG]: new FormControl(false),
@@ -118,16 +107,11 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
             [UserFormService.USER]: new FormControl(false),
             [UserFormService.POSTSTELLE]: new FormControl(false),
           }),
-        },
-        {
-          validators: checkBoxGroupsEmptyValidator(UserFormService.CLIENT_ROLES, [
-            UserFormService.ADMINISTRATION_GROUP,
-            UserFormService.ALFA_GROUP,
-          ]),
-        },
-      ),
-      [UserFormService.GROUPS]: this.formBuilder.group({}),
-    });
+        }),
+        [UserFormService.GROUPS]: this.formBuilder.group({}),
+      },
+      { updateOn: 'submit' },
+    );
   }
 
   _initOrganisationsEinheiten(): Observable<AdminOrganisationsEinheit[]> {
diff --git a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts
index 89c0cd5fb1..775483fcac 100644
--- a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts
@@ -45,13 +45,20 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue
 
     this.changesSubscr = this.fieldControl.valueChanges.subscribe((val) => {
       this.onChange(val);
-      this.setErrors();
+      // if (this.control?.control && this.control.control.getRawValue() !== this.fieldControl.value) {
+      //   this.control.control.setValue(this.fieldControl.value, { emitEvent: false });
+      // }
+      this.fieldControl.setErrors(null);
+      this.invalidParams = [];
     });
   }
 
   ngOnInit(): void {
     if (!this.statusSubscr && this.control)
       this.statusSubscr = this.control.statusChanges.subscribe(() => {
+        this.onChange(this.fieldControl.value);
+        this.fieldControl.updateValueAndValidity({ emitEvent: false });
+        this.control.control.updateValueAndValidity({ emitEvent: false });
         this.setErrors();
       });
   }
@@ -62,7 +69,6 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue
 
   writeValue(text: string): void {
     this.fieldControl.setValue(text);
-    this.setErrors();
   }
 
   registerOnChange(fn: (text: string | Date) => {}): void {
diff --git a/alfa-client/libs/design-system/src/lib/button/button.component.ts b/alfa-client/libs/design-system/src/lib/button/button.component.ts
index 3f88a32abc..78bc43acf3 100644
--- a/alfa-client/libs/design-system/src/lib/button/button.component.ts
+++ b/alfa-client/libs/design-system/src/lib/button/button.component.ts
@@ -104,8 +104,8 @@ export type ButtonVariants = VariantProps<typeof buttonVariants>;
   selector: 'ods-button',
   standalone: true,
   imports: [CommonModule, SpinnerIconComponent],
-  template: `<button
-    type="button"
+  template: ` <button
+    type="submit"
     [ngClass]="buttonVariants({ size, variant, disabled: isDisabled, destructive })"
     [attr.aria-disabled]="isDisabled"
     [attr.aria-label]="text"
-- 
GitLab