diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts index d57cf619353a41d05d4e9d866ca098331d4345b1..0aa7983369385f6507e8892f4caa5b44fa7ba7a4 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts @@ -5,6 +5,9 @@ export function patchForm(valueToPatch: any, formGroup: FormGroup): void { formGroup.patchValue(valueToPatch); patchNonStringValues(valueToPatch, formGroup); + + console.log(formGroup); + console.log(valueToPatch); } function patchNonStringValues(valueToPatch: any, formGroup: FormGroup): void { 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 6cd713c43da4b1821c5661d3bdc56620e3a37eff..2949096da22278cb2e4e16b2bd1746f4915ea171 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 @@ -31,7 +31,7 @@ import { import { inject, Injectable } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute, UrlSegment } from '@angular/router'; -import { catchError, first, Observable, of, tap } from 'rxjs'; +import { catchError, delay, first, Observable, of, startWith, tap } from 'rxjs'; import { ValidationMessageCode } from '../../../../tech-shared/src/lib/validation/tech.validation.messages'; import * as FormUtil from './form.util'; import { ErrorRepresentation, KeycloakErrorMessage, KeycloakFieldName, KeycloakHttpErrorResponse } from './keycloak-error.model'; @@ -78,17 +78,9 @@ 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)), ); @@ -96,7 +88,7 @@ export abstract class KeycloakFormService<T> { _processInvalidForm(): Observable<StateResource<T>> { this._showValidationErrorForAllInvalidControls(this.form); - return of(createEmptyStateResource<T>()); + return of(createEmptyStateResource<T>()).pipe(delay(200), startWith(createEmptyStateResource<T>(true))); } _processResponseValidationErrors(keycloakError: KeycloakHttpErrorResponse): Observable<StateResource<T>> { diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html index d3d39a71ceb69e639c38e1ac0219f27eb1c5e6f5..f89c785b925bd4d3edaa70a4689b7817284d352d 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html @@ -35,7 +35,7 @@ <div [formGroupName]="UserFormService.ALFA_GROUP" class="flex flex-1 flex-col gap-2"> <h3 class="text-md block font-medium text-text">Alfa</h3> <div class="flex items-center gap-2"> - <ods-checkbox-editor [formControlName]="UserFormService.LOESCHEN" label="Löschen" inputId="delete" /> + <ods-checkbox-editor [formControlName]="UserFormService.LOESCHEN" label="Löschen" inputId="delete" (inputChange)="disableUncheckedAlfaCheckboxes(UserFormService.LOESCHEN,$event)"/> <button data-test-id="loschen-role-info-button" tooltip='Diese Rolle hat dieselben Rechte wie die Rolle "User". Zusätzlich kann "Löschen" Löschanträge aus Alfa bestätigen. ' @@ -44,7 +44,7 @@ </button> </div> <div class="flex items-center gap-2"> - <ods-checkbox-editor [formControlName]="UserFormService.USER" label="User" inputId="user" /> + <ods-checkbox-editor [formControlName]="UserFormService.USER" label="User" inputId="user" (inputChange)="disableUncheckedAlfaCheckboxes(UserFormService.USER,$event)"/> <button data-test-id="user-role-info-button" tooltip="Diese Rolle kann alle Vorgänge sehen und bearbeiten, wenn diese seiner Organisationseinheit zugewiesen sind." @@ -53,7 +53,7 @@ </button> </div> <div class="flex items-center gap-2"> - <ods-checkbox-editor [formControlName]="UserFormService.POSTSTELLE" label="Poststelle" inputId="post_office" /> + <ods-checkbox-editor [formControlName]="UserFormService.POSTSTELLE" label="Poststelle" inputId="post_office" (inputChange)="disableUncheckedAlfaCheckboxes(UserFormService.POSTSTELLE,$event)"/> <button data-test-id="poststelle-role-info-button" tooltip="Diese Rolle kann alle neu eingegangenen Vorgänge sehen."> <ods-info-icon /> </button> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts index 7a8c901dab035b8a8364b9ce60c1453a7374711c..27480606f95f5765d6f5ab299370e05d465a47f9 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts @@ -1,6 +1,6 @@ import { generateValidationErrorId, InvalidParam } from '@alfa-client/tech-shared'; import { AsyncPipe } from '@angular/common'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, inject, Input, OnInit } from '@angular/core'; import { AbstractControl, FormControlStatus, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { CheckboxEditorComponent, ValidationErrorComponent } from '@ods/component'; import { InfoIconComponent, TooltipDirective } from '@ods/system'; @@ -22,6 +22,8 @@ import { UserFormService } from '../user.formservice'; templateUrl: './user-form-roles.component.html', }) export class UserFormRolesComponent implements OnInit { + public readonly formService = inject(UserFormService); + @Input() formGroupParent: UntypedFormGroup; public invalidParams$: Observable<InvalidParam[]> = of([]); @@ -37,4 +39,12 @@ export class UserFormRolesComponent implements OnInit { tap((invalidParams: InvalidParam[]) => (this.isValid = isEmpty(invalidParams))), ); } + + disableUncheckedAlfaCheckboxes(formControlName: string, value: boolean) { + if (value) { + this.formService.disableOtherCheckboxes(formControlName); + } else { + this.formService.enableAllAlfaCheckboxes(); + } + } } diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.ts index 941904eb8d84b9c72b9483cabcfdb354026fd48e..e1d18c6e2c177e51d947691dec5a39285aebf660 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.ts @@ -1,7 +1,7 @@ import { User } from '@admin-client/user-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { AsyncPipe } from '@angular/common'; -import { Component, inject } from '@angular/core'; +import { Component, inject, Input } from '@angular/core'; import { ButtonWithSpinnerComponent } from '@ods/component'; import { Observable } from 'rxjs'; import { UserFormService } from '../user.formservice'; @@ -15,9 +15,9 @@ import { UserFormService } from '../user.formservice'; export class UserFormSaveButtonComponent { public readonly formService = inject(UserFormService); - public submitStateResource$: Observable<StateResource<User>>; + @Input() submitStateResource$: Observable<StateResource<User>>; public submit(): void { - this.submitStateResource$ = this.formService.submit(); + this.formService.form.get(UserFormService.ALFA_GROUP).updateValueAndValidity(); } } 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 0d9c504aa0471c0081a51cec152f88a187dd92c2..367fabce15ad13e02f931cd6ce7bd04c77e94ee9 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,7 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<form [formGroup]="formService.form" (ngSubmit)="onSubmit()"> +<form [formGroup]="formService.form" (ngSubmit)="submit()"> <ods-spinner [stateResource]="userStateResource$ | async"> <div class="max-w-[960px]" data-test-id="user-content"> <admin-user-form-headline [isPatch]="isPatch" /> @@ -34,7 +34,7 @@ [formGroupOrganisationsEinheiten]="formService.getOrganisationsEinheitenGroup()" /> <div class="mb-6 flex justify-between"> - <admin-user-form-save-button /> + <admin-user-form-save-button [submitStateResource$]="submitStateResource$"/> @if (isPatch) { <admin-delete-open-dialog-button data-test-id="delete-button-container" /> } diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts index eee2cb862553ef56050bb09eadb91f73a216ad50..9874532168270ed3ae8558d24949fe15c9032131 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts @@ -62,6 +62,7 @@ export class UserFormComponent implements OnInit { public userStateResource$: Observable<StateResource<User>>; public isPatch: boolean; public userName: string; + public submitStateResource$: Observable<StateResource<User>>; ngOnInit(): void { this.userStateResource$ = this.formService.get().pipe( @@ -72,7 +73,7 @@ export class UserFormComponent implements OnInit { ); } - onSubmit(): void { - this.formService.submit(); + public submit(): void { + this.submitStateResource$ = this.formService.submit(); } } 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 d97dee1e2b586f2a1bf01ed02059bcc31f687afd..717247d0280dc2b80517c52db066d4495f5d8da7 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 @@ -164,6 +164,8 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest const alfaGroup: UntypedFormGroup = this.getRoleGroup(UserFormService.ALFA_GROUP); this._handleAlfaGroupChange(alfaGroup); this._alfaGroupChanges = alfaGroup.valueChanges.subscribe(() => { + console.log('Checkboxes in ALFA_GROUP changed'); + alfaGroup.parent.setErrors(null, { emitEvent: false }); this._handleAlfaGroupChange(alfaGroup); }); } @@ -187,6 +189,21 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest } } + disableOtherCheckboxes(formControlName: string) { + const alfaGroup: UntypedFormGroup = this.getRoleGroup(UserFormService.ALFA_GROUP); + for (const key of Object.keys(alfaGroup.controls)) { + const control: AbstractControl = alfaGroup.controls[key]; + if (formControlName !== key) { + if (!control.value) control.disable({ emitEvent: false }); + } + } + } + + enableAllAlfaCheckboxes() { + const alfaGroup: UntypedFormGroup = this.getRoleGroup(UserFormService.ALFA_GROUP); + this._enableAllCheckboxes(alfaGroup); + } + _enableAllCheckboxes(group: UntypedFormGroup): void { for (const control of Object.values<AbstractControl>(group.controls)) { control.enable({ emitEvent: false }); diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html index 1cc8b07470d8086854cee7b310342c8697b4eed5..cf21692584c4ef6d986a494166f22ae5cb894328 100644 --- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html @@ -30,6 +30,7 @@ [disabled]="control.disabled" [hasError]="hasError" [ariaDescribedBy]="validationErrorId" + (inputChange)="inputChange.emit($event)" > <ods-validation-error error diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts index bbb9f693fc54a9760f0bb33b006848b12aba6554..0a098c993e31075992c2716acf82a6bd5a507f17 100644 --- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared'; -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { CheckboxComponent } from '@ods/system'; import { FormControlEditorAbstractComponent } from '../formcontrol-editor.abstract.component'; @@ -38,6 +38,8 @@ export class CheckboxEditorComponent extends FormControlEditorAbstractComponent @Input() inputId: string; @Input() label: string; + @Output() inputChange = new EventEmitter<boolean>(); + public readonly validationErrorId: string = generateValidationErrorId(); get hasError(): boolean { 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 1338796de9248221609af0d806b510ae1806da1b..c83b8b16d3646d1afaa99ef4034b4ac7a2a41eb6 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 @@ -23,7 +23,7 @@ */ import { InvalidParam } from '@alfa-client/tech-shared'; import { Component, OnDestroy, OnInit, Optional, Self } from '@angular/core'; -import { ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms'; +import { AbstractControl, ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms'; import { Subscription } from 'rxjs'; @Component({ @@ -94,6 +94,17 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue removeErrors(): void { this.fieldControl.setErrors(null); this._updateInvalidParams(); + this.clearAllParentErrors(this.control.control); + } + + clearAllParentErrors(control: AbstractControl | null): void { + if (!control) return; + + const parent = control.parent; + if (parent) { + parent.setErrors(null); + this.clearAllParentErrors(parent); + } } _updateInvalidParams(): void { diff --git a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts index 3b3750bd3174bfaeecf2821d4dc29d9e5c6a4d4e..767a1e9c069b27174d9a05a1ab566cde0f1aa06e 100644 --- a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts @@ -23,7 +23,7 @@ */ import { ConvertForDataTestPipe, EMPTY_STRING } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; @Component({ @@ -46,6 +46,7 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms'; [attr.disabled]="disabled ? true : null" [attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor'" [attr.aria-describedby]="ariaDescribedBy" + (change)="inputChangeHandler($event)" /> <label class="leading-5 text-text" [attr.for]="inputId">{{ label }}</label> <svg @@ -71,4 +72,10 @@ export class CheckboxComponent { @Input() disabled: boolean = false; @Input() hasError: boolean = false; @Input() ariaDescribedBy: string = EMPTY_STRING; + + @Output() inputChange = new EventEmitter<boolean | undefined>(); + + inputChangeHandler(event: Event) { + this.inputChange.emit((event.target as HTMLInputElement).checked); + } }