Merge pull request #83 in TEA/mina-sidor-fa-web from feature/TV-396 to develop
Squashed commit of the following: commit f5029b04d2117df86eaf6692c88bdc692059d8d6 Merge: 3d776b145f2fb5Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Sat Sep 11 06:17:07 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 3d776b10824be6b54e186104a5bcd351e5b2fb42 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 23:26:47 2021 +0200 TV-396 fixed some tests and so on commit bd57fce383ba409ae8de1869c242b5a8f51071d2 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 23:20:26 2021 +0200 TV-396 made some adjustments to the validation logic after feedback in PR commit 942cddb263d0965e772f7f34305e85737da76df4 Merge: 174dfe9ceee702Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 14:10:23 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 174dfe924f2eac979992275ddd55ed0758144efb Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 14:05:39 2021 +0200 TV-396 fixed issue with general info after restructuring.. commit 7e0d4bdf9e76e0fb58fe30358c3e729cce1f9260 Merge: da02f6c5b00453Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 12:00:29 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit da02f6cc7f4a9405ad1d8167ef18729b18973d61 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 10:43:42 2021 +0200 TV-396 added aria-expanded attribute commit 48eb24ca6e354b44ae4d4b62ce2ffa496743d0b5 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 09:46:39 2021 +0200 TV-396 moved some logic into seperate template commit 0ef787d6c3700677ae793c486930b07748365412 Merge: 6dfd7b05f81d6fAuthor: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 17:34:40 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 6dfd7b00caa45d335f3fe8619b92c282038ac5cb Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 17:33:54 2021 +0200 TV-396 used digi internal link instead of basic a-tag commit 46c17011b7f6b1f628b14ccf020c06cdc95627c8 Merge: 4338e151938b94Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 15:07:16 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 4338e153f7e4ebdf8ab65a64a6194dbd4d9fa9c7 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 14:56:26 2021 +0200 TV-396 added error summary an validation handling for the edit form etc. commit ebb2e76993b99756d5a641ab8ca7d137be8a982f Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Wed Sep 8 22:58:44 2021 +0200 TV-396 making sure utforande verksamheter and addresses are populated as they should be when editing an employee. commit 01f4c9bf7ad8fc4ad44b0e8945182492b864d0cf Merge: 1c2aa92b06436aAuthor: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Wed Sep 8 22:07:25 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 1c2aa92f21aac57036ed05d5ebeab0f0e6a45c2c Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Wed Sep 8 22:07:00 2021 +0200 Merge branch 'develop-remote' into bugs/TV-520 # Conflicts: # apps/mina-sidor-fa/src/app/pages/administration/pages/employee-card/employee-card.component.html
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
<digi-typography>
|
||||
<digi-typography #editEmployeeFormContainer>
|
||||
<form
|
||||
class="edit-employee-form"
|
||||
*ngIf="editEmployeeFormGroup"
|
||||
[formGroup]="editEmployeeFormGroup"
|
||||
(ngSubmit)="onFormSubmitted()"
|
||||
>
|
||||
<msfa-error-list
|
||||
[hidden]="!displayErrorSummary"
|
||||
[headingText]="'Åtgärda följande fel för att spara dina ändringar:'"
|
||||
[validationErrorLinks]="getValidationErrorLinks()"
|
||||
></msfa-error-list>
|
||||
<digi-ng-form-input
|
||||
afId="edit-employee-form-email"
|
||||
[afId]="emailElementId"
|
||||
afLabel="E-post adress"
|
||||
afType="email"
|
||||
[formControlName]="emailFormControlName"
|
||||
@@ -20,7 +25,6 @@
|
||||
<h2>Behörigheter</h2>
|
||||
<p>Här kan du ändra personalkontots behörigheter.</p>
|
||||
</div>
|
||||
|
||||
<div class="edit-employee-form__block">
|
||||
<h3>Tjänster</h3>
|
||||
<p>Välj de tjänster du vill ge personalen tillgång till.</p>
|
||||
@@ -32,6 +36,7 @@
|
||||
[afSelectItems]="selectableTjansterFormItems"
|
||||
[afDisableValidStyle]="true"
|
||||
[afInvalid]="tjansterFormControl.invalid && tjansterFormControl.touched"
|
||||
[afId]="tjansterElementId"
|
||||
(afOnChange)="toggleTjanst()"
|
||||
></digi-ng-form-select>
|
||||
<digi-form-validation-message
|
||||
@@ -48,22 +53,30 @@
|
||||
<p *ngIf="!availableUtforandeVerksamheter || availableUtforandeVerksamheter.length === 0">
|
||||
<strong>Du måste välja en eller flera tjänster för att kunna välja utförande verksamheter.</strong>
|
||||
</p>
|
||||
<digi-ng-form-checkbox
|
||||
class="edit-employee-form__choose-all-utforande-verksamheter"
|
||||
[formControl]="toggleAllUtforandeVerksamhetFormControl"
|
||||
[afLabel]="'Välj alla utförande verksamheter och alla utförande adresser'"
|
||||
(afOnChange)="toggleAllUtforandeVerksamheter($event)"
|
||||
></digi-ng-form-checkbox>
|
||||
<ng-container *ngIf="!isLoadingUtforandeVerksamheter else loadingUtforandeVerksamheterTemplate">
|
||||
<digi-ng-form-checkbox
|
||||
class="edit-employee-form__choose-all-utforande-verksamheter"
|
||||
[formControl]="selectAllUtforandeVerksamheterFormControl"
|
||||
[afLabel]="'Välj alla utförande verksamheter och alla utförande adresser'"
|
||||
></digi-ng-form-checkbox>
|
||||
|
||||
<msfa-tree-nodes-selector
|
||||
*ngIf="!toggleAllUtforandeVerksamhetFormControl.value"
|
||||
[headingText]="'Välj utförande verksamheter och adresser'"
|
||||
[formControlName]="utforandeVerksamhetFormControlName"
|
||||
[isInvalid]="utforandeVerksamhetFormControl?.invalid"
|
||||
[showValidation]="utforandeVerksamhetFormControl?.touched"
|
||||
[validationMessages]="utforandeVerksamhetFormControl.errors?.required ? ['Välj minst en utförande verksamhet'] : []"
|
||||
(selectedTreeNodesChanged)="updateToggleAllUtforandeVerksamheter()"
|
||||
></msfa-tree-nodes-selector>
|
||||
<msfa-tree-nodes-selector
|
||||
*ngIf="!selectAllUtforandeVerksamheterFormControl.value"
|
||||
[buttonElementId]="utforandeVerksamhetElementId"
|
||||
[headingText]="'Välj utförande verksamheter och adresser'"
|
||||
[formControlName]="utforandeVerksamheterFormControlName"
|
||||
[isInvalid]="editEmployeeFormGroup.invalid && editEmployeeFormGroup.errors?.noUtforandeVerksamhetSelected"
|
||||
[showValidation]="utforandeVerksamheterFormControl?.touched"
|
||||
[validationMessages]="editEmployeeFormGroup.errors?.noUtforandeVerksamhetSelected ? [utforandeVerksamhetRequiredMessage] : []"
|
||||
(selectedTreeNodesChanged)="updateToggleAllUtforandeVerksamheter()"
|
||||
></msfa-tree-nodes-selector>
|
||||
</ng-container>
|
||||
<ng-template #loadingUtforandeVerksamheterTemplate>
|
||||
<digi-ng-loader-spinner
|
||||
*ngIf="isLoadingUtforandeVerksamheter"
|
||||
[afLabel]="'Läser in utförande verksamheter för vald/a tjänster...'"
|
||||
></digi-ng-loader-spinner>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="edit-employee-form__block" *ngIf="rolesFormGroup && availableRoles" [formGroup]="rolesFormGroup">
|
||||
|
||||
@@ -3,12 +3,14 @@ import { FormSelectItem } from '@af/digi-ng/_form/form-select';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
|
||||
import { RoleEnum } from '@msfa-enums/role.enum';
|
||||
@@ -19,13 +21,12 @@ import { Role } from '@msfa-models/role.model';
|
||||
import { Tjanst } from '@msfa-models/tjanst.model';
|
||||
import { UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model';
|
||||
import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service';
|
||||
import {
|
||||
TreeNode,
|
||||
TreeNodesSelectorService,
|
||||
} from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service';
|
||||
import { ValidationErrorLink } from '@msfa-shared/components/error-list/error-list.component';
|
||||
import { TreeNodesSelectorService } from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service';
|
||||
import { EmailValidator } from '@msfa-utils/validators/email.validator';
|
||||
import { EmployeeValidator } from '@msfa-utils/validators/employee.validator';
|
||||
import { RequiredValidator } from '@msfa-utils/validators/required.validator';
|
||||
import { TreeNodeValidator } from '@msfa-utils/validators/tree-node.validator';
|
||||
import { UUID } from 'angular2-uuid';
|
||||
import { EmployeeFormService } from '../services/employee-form.service';
|
||||
|
||||
@Component({
|
||||
@@ -35,9 +36,12 @@ import { EmployeeFormService } from '../services/employee-form.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
@ViewChild('editEmployeeFormContainer') editEmployeeFormContainer: ElementRef;
|
||||
|
||||
@Input() employee: Employee;
|
||||
@Input() availableRoles: Role[];
|
||||
@Input() availableTjanster: Tjanst[];
|
||||
@Input() isLoadingUtforandeVerksamheter = false;
|
||||
@Input() availableUtforandeVerksamheter: UtforandeVerksamhet[];
|
||||
@Input() errorWhileUpdating: CustomError;
|
||||
|
||||
@@ -51,14 +55,21 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
|
||||
readonly emailFormControlName = 'email';
|
||||
readonly tjansterFormControlName = 'tjanster';
|
||||
readonly utforandeVerksamhetFormControlName = 'utforandeVerksamheter';
|
||||
readonly toggleAllUtforandeVerksamhetFormControlName = 'allaUtforandeVerksamheter';
|
||||
readonly utforandeVerksamheterFormControlName = 'utforandeVerksamheter';
|
||||
readonly selectAllUtforandeVerksamheterFormControlName = 'allaUtforandeVerksamheter';
|
||||
readonly utforandeVerksamhetRequiredMessage = 'Välj minst en utförande verksamhet';
|
||||
readonly formUuid = UUID.UUID();
|
||||
readonly emailElementId = `email-control-${this.formUuid}`;
|
||||
readonly tjansterElementId = `tjanster-control-${this.formUuid}`;
|
||||
readonly utforandeVerksamhetElementId = `utforande-verksamhet-control-${this.formUuid}`;
|
||||
readonly firstValidationErrorLinkId = `validation-error-link-${this.formUuid}`;
|
||||
|
||||
editEmployeeFormGroup: FormGroup | null = null;
|
||||
displayEditWithoutRolesDialog = false;
|
||||
displayPristineWarning = false;
|
||||
|
||||
displayRolesDialog = false;
|
||||
displayErrorSummary = false;
|
||||
|
||||
selectableTjansterFormItems: Array<FormSelectItem> | null = null;
|
||||
|
||||
@@ -88,9 +99,11 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
this.editEmployeeFormGroup.patchValue(
|
||||
Object.fromEntries([
|
||||
[
|
||||
this.utforandeVerksamhetFormControlName,
|
||||
this.utforandeVerksamheterFormControlName,
|
||||
this.utforandeVerksamheterService.getTreeNodeDataFromUtforandeVerksamheter(
|
||||
this.availableUtforandeVerksamheter
|
||||
this.availableUtforandeVerksamheter,
|
||||
this.employee?.utforandeVerksamheter,
|
||||
this.employee?.allaUtforandeVerksamheter
|
||||
),
|
||||
],
|
||||
])
|
||||
@@ -112,23 +125,23 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
return this.editEmployeeFormGroup.get('roles');
|
||||
}
|
||||
|
||||
get utforandeVerksamhetFormControl(): AbstractControl | undefined {
|
||||
return this.editEmployeeFormGroup?.get(this.utforandeVerksamhetFormControlName);
|
||||
get utforandeVerksamheterFormControl(): AbstractControl | undefined {
|
||||
return this.editEmployeeFormGroup?.get(this.utforandeVerksamheterFormControlName);
|
||||
}
|
||||
|
||||
get toggleAllUtforandeVerksamhetFormControl(): AbstractControl | undefined {
|
||||
return this.editEmployeeFormGroup?.get(this.toggleAllUtforandeVerksamhetFormControlName);
|
||||
get selectAllUtforandeVerksamheterFormControl(): AbstractControl | undefined {
|
||||
return this.editEmployeeFormGroup?.get(this.selectAllUtforandeVerksamheterFormControlName);
|
||||
}
|
||||
|
||||
private updateUtforandeVerksamhetStatus(): void {
|
||||
if (this.availableUtforandeVerksamheter && this.availableUtforandeVerksamheter.length > 0) {
|
||||
this.utforandeVerksamhetFormControl.enable();
|
||||
this.toggleAllUtforandeVerksamhetFormControl.enable();
|
||||
this.utforandeVerksamheterFormControl.enable();
|
||||
this.selectAllUtforandeVerksamheterFormControl.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
this.utforandeVerksamhetFormControl.disable();
|
||||
this.toggleAllUtforandeVerksamhetFormControl.disable();
|
||||
this.utforandeVerksamheterFormControl.disable();
|
||||
this.selectAllUtforandeVerksamheterFormControl.disable();
|
||||
}
|
||||
|
||||
private updateSelectableTjansterFormItems(): void {
|
||||
@@ -143,22 +156,32 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
? this.availableTjanster.find(tjanst => tjanst.code === currentTjanst.tjansteKod).tjanstId
|
||||
: null;
|
||||
|
||||
this.editEmployeeFormGroup = new FormGroup({
|
||||
email: new FormControl(this.employee.email, [RequiredValidator('E-postadress'), EmailValidator()]),
|
||||
tjanster: new FormControl(tjanstId, [RequiredValidator('Tjänst')]),
|
||||
roles: this.employeeFormService.getRolesFormGroup(this.availableRoles, this.employee.roles),
|
||||
utforandeVerksamheter: new FormControl(
|
||||
this.utforandeVerksamheterService.getTreeNodeDataFromUtforandeVerksamheter(this.availableUtforandeVerksamheter),
|
||||
[
|
||||
TreeNodeValidator.IsValidTreeNode(
|
||||
this.utforandeVerksamheterService.hasSelectedUtforandeVerksamhet,
|
||||
'required',
|
||||
this.toggleAllUtforandeVerksamhetFormControl
|
||||
this.editEmployeeFormGroup = new FormGroup(
|
||||
{
|
||||
email: new FormControl(this.employee.email, [
|
||||
RequiredValidator('E-postadress'),
|
||||
EmailValidator('e-postadress'),
|
||||
]),
|
||||
tjanster: new FormControl(tjanstId, [RequiredValidator('Tjänst')]),
|
||||
roles: this.employeeFormService.getRolesFormGroup(this.availableRoles, this.employee.roles),
|
||||
utforandeVerksamheter: new FormControl(
|
||||
this.utforandeVerksamheterService.getTreeNodeDataFromUtforandeVerksamheter(
|
||||
this.availableUtforandeVerksamheter,
|
||||
this.employee?.utforandeVerksamheter,
|
||||
this.employee?.allaUtforandeVerksamheter
|
||||
),
|
||||
]
|
||||
),
|
||||
allaUtforandeVerksamheter: new FormControl(this.employee.allaUtforandeVerksamheter),
|
||||
});
|
||||
[]
|
||||
),
|
||||
allaUtforandeVerksamheter: new FormControl(this.employee.allaUtforandeVerksamheter),
|
||||
},
|
||||
{
|
||||
validators: EmployeeValidator.HasSelectedAtLeastOneUtforandeVerksamhet(
|
||||
this.utforandeVerksamheterFormControlName,
|
||||
this.selectAllUtforandeVerksamheterFormControlName,
|
||||
this.utforandeVerksamheterService.hasSelectedUtforandeVerksamhet
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
this.updateUtforandeVerksamhetStatus();
|
||||
}
|
||||
@@ -172,22 +195,27 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
onFormSubmitted(saveWithoutRoles = false): void {
|
||||
if (!this.editEmployeeFormGroup) {
|
||||
if (!this.editEmployeeFormGroup || this.isLoadingUtforandeVerksamheter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.displayPristineWarning = this.editEmployeeFormGroup.pristine;
|
||||
|
||||
this.editEmployeeFormGroup.markAllAsTouched();
|
||||
|
||||
if (this.editEmployeeFormGroup.invalid) {
|
||||
if (this.editEmployeeFormGroup.invalid || this.editEmployeeFormGroup.pristine) {
|
||||
this.displayErrorSummary = true;
|
||||
setTimeout(() => {
|
||||
this.focusLinkElement('.error-list__validation-error-link a');
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editEmployeeFormGroup.pristine) {
|
||||
this.displayPristineWarning = true;
|
||||
return;
|
||||
}
|
||||
this.displayErrorSummary = false;
|
||||
|
||||
const roles = this.employeeFormService.getRolesFromFormGroup(this.rolesFormGroup, this.availableRoles);
|
||||
|
||||
if (!roles.length && !saveWithoutRoles) {
|
||||
this.displayEditWithoutRolesDialog = true;
|
||||
return;
|
||||
@@ -204,21 +232,67 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
RoleEnum.MSFA_Standard,
|
||||
]),
|
||||
],
|
||||
adressIds: this.toggleAllUtforandeVerksamhetFormControl.value
|
||||
adressIds: this.selectAllUtforandeVerksamheterFormControl.value
|
||||
? []
|
||||
: this.utforandeVerksamheterService.getSelectedAdressIdsFromTreeNode(
|
||||
this.utforandeVerksamhetFormControl?.value
|
||||
this.utforandeVerksamheterFormControl?.value
|
||||
),
|
||||
allaUtforandeVerksamheter: !!this.toggleAllUtforandeVerksamhetFormControl.value,
|
||||
allaUtforandeVerksamheter: !!this.selectAllUtforandeVerksamheterFormControl.value,
|
||||
});
|
||||
}
|
||||
|
||||
toggleTjanst(): void {
|
||||
if (this.tjansterFormControl.value) {
|
||||
this.tjansterSelected.emit(
|
||||
this.employeeFormService.getSelectedTjanster(this.availableTjanster, +this.tjansterFormControl.value)
|
||||
);
|
||||
getValidationErrorLinks(): ValidationErrorLink[] {
|
||||
let validationErrorLinks: ValidationErrorLink[] = [];
|
||||
|
||||
if (!this.editEmployeeFormGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.emailFormControl?.errors) {
|
||||
validationErrorLinks = validationErrorLinks.concat({
|
||||
elementId: this.emailElementId,
|
||||
text: this.emailFormControl?.errors?.message as string,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.tjansterFormControl?.errors) {
|
||||
validationErrorLinks = validationErrorLinks.concat({
|
||||
elementId: this.tjansterElementId,
|
||||
text: this.tjansterFormControl?.errors?.message as string,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.editEmployeeFormGroup.errors?.noUtforandeVerksamhetSelected) {
|
||||
validationErrorLinks = validationErrorLinks.concat({
|
||||
elementId: this.utforandeVerksamhetElementId,
|
||||
text: this.utforandeVerksamhetRequiredMessage,
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrorLinks;
|
||||
}
|
||||
|
||||
focusLinkElement(selector: string): void {
|
||||
let errorListElement: HTMLElement = null;
|
||||
let linkElement: HTMLLinkElement = null;
|
||||
|
||||
if (!this.editEmployeeFormContainer || !this.editEmployeeFormContainer.nativeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorListElement = this.editEmployeeFormContainer.nativeElement as HTMLElement;
|
||||
linkElement = errorListElement?.querySelector(selector);
|
||||
linkElement?.focus();
|
||||
}
|
||||
|
||||
toggleTjanst(): void {
|
||||
if (!this.tjansterFormControl.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tjansterSelected.emit(
|
||||
this.employeeFormService.getSelectedTjanster(this.availableTjanster, +this.tjansterFormControl.value)
|
||||
);
|
||||
}
|
||||
|
||||
openRolesDialog(): void {
|
||||
@@ -229,23 +303,13 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges {
|
||||
this.displayRolesDialog = false;
|
||||
}
|
||||
|
||||
toggleAllUtforandeVerksamheter(selectAll: boolean): void {
|
||||
let treeNode: TreeNode = this.utforandeVerksamhetFormControl.value as TreeNode;
|
||||
|
||||
treeNode = selectAll
|
||||
? this.treeNodesSelectorService.getTreeNodeWithAllNodesSelected(treeNode)
|
||||
: this.treeNodesSelectorService.getTreeNodeWithNoNodesSelected(treeNode);
|
||||
|
||||
this.editEmployeeFormGroup.patchValue(Object.fromEntries([[this.utforandeVerksamhetFormControlName, treeNode]]));
|
||||
}
|
||||
|
||||
updateToggleAllUtforandeVerksamheter(): void {
|
||||
const hasSelectedAllLeafNodes = this.treeNodesSelectorService.hasSelectedAllLeafNodes(
|
||||
this.utforandeVerksamhetFormControl.value
|
||||
this.utforandeVerksamheterFormControl.value
|
||||
);
|
||||
|
||||
this.editEmployeeFormGroup.patchValue(
|
||||
Object.fromEntries([[this.toggleAllUtforandeVerksamhetFormControlName, hasSelectedAllLeafNodes]])
|
||||
Object.fromEntries([[this.selectAllUtforandeVerksamheterFormControlName, hasSelectedAllLeafNodes]])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<msfa-layout>
|
||||
<digi-typography>
|
||||
<section class="employee-form" *ngIf="employee$ | async as employee">
|
||||
<section class="employee-form">
|
||||
<header class="employee-form__header">
|
||||
<h1>Redigera personalkonto</h1>
|
||||
<msfa-employee-delete [returnToEmployeeList]="true"></msfa-employee-delete>
|
||||
@@ -13,41 +13,46 @@
|
||||
Ta bort konto
|
||||
</digi-button> -->
|
||||
</header>
|
||||
|
||||
<div class="employee-form__block">
|
||||
<h2>Personuppgifter</h2>
|
||||
<dl>
|
||||
<dt>Förnamn</dt>
|
||||
<dd>{{employee.firstName}}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Efternamn</dt>
|
||||
<dd>{{employee.lastName}}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Personnummer</dt>
|
||||
<dd>
|
||||
<msfa-hide-text
|
||||
symbols="********-****"
|
||||
[changingText]="employee.ssn"
|
||||
ariaLabelType="personnummer"
|
||||
></msfa-hide-text>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="employee-form__block">
|
||||
<msfa-edit-employee-form
|
||||
*ngIf="employee && (tjanster$ | async)"
|
||||
[employee]="employee"
|
||||
[availableRoles]="availableRoles"
|
||||
[availableTjanster]="tjanster$ | async"
|
||||
[availableUtforandeVerksamheter]="availableUtforandeVerksamheter$ | async"
|
||||
[errorWhileUpdating]="errorWhileUpdating$ | async"
|
||||
(tjansterSelected)="setupAvailableUtforandeVerksamheter($event)"
|
||||
(formSubmitted)="updateEmployee($event)"
|
||||
(closeError)="closeError()"
|
||||
></msfa-edit-employee-form>
|
||||
</div>
|
||||
<ng-container *ngIf="employee$ | async as employee; else isLoadingEmployeeTemplate">
|
||||
<div class="employee-form__block">
|
||||
<h2>Personuppgifter</h2>
|
||||
<dl>
|
||||
<dt>Förnamn</dt>
|
||||
<dd>{{employee.firstName}}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Efternamn</dt>
|
||||
<dd>{{employee.lastName}}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Personnummer</dt>
|
||||
<dd>
|
||||
<msfa-hide-text
|
||||
symbols="********-****"
|
||||
[changingText]="employee.ssn"
|
||||
ariaLabelType="personnummer"
|
||||
></msfa-hide-text>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="employee-form__block">
|
||||
<msfa-edit-employee-form
|
||||
*ngIf="employee && (tjanster$ | async)"
|
||||
[employee]="employee"
|
||||
[availableRoles]="availableRoles"
|
||||
[availableTjanster]="tjanster$ | async"
|
||||
[isLoadingUtforandeVerksamheter]="isLoadingUtforandeVerksamheter$ | async"
|
||||
[availableUtforandeVerksamheter]="availableUtforandeVerksamheter$ | async"
|
||||
[errorWhileUpdating]="errorWhileUpdating$ | async"
|
||||
(tjansterSelected)="setupAvailableUtforandeVerksamheter($event)"
|
||||
(formSubmitted)="updateEmployee($event)"
|
||||
(closeError)="closeError()"
|
||||
></msfa-edit-employee-form>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #isLoadingEmployeeTemplate>
|
||||
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personalkonto..."></digi-ng-skeleton-base>
|
||||
</ng-template>
|
||||
</section>
|
||||
</digi-typography>
|
||||
</msfa-layout>
|
||||
|
||||
@@ -10,8 +10,8 @@ import { EmployeeService } from '@msfa-services/api/employee.service';
|
||||
import { TjanstService } from '@msfa-services/api/tjanst.service';
|
||||
import { RoleService } from '@msfa-services/role.service';
|
||||
import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { filter, switchMap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { catchError, mapTo, startWith, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-employee-form',
|
||||
@@ -23,16 +23,18 @@ export class EmployeeFormComponent implements OnInit {
|
||||
private _employeeId$ = new BehaviorSubject<string>(this.activatedRoute.snapshot.params['employeeId']);
|
||||
private _selectedTjanstIds$ = new BehaviorSubject<number[]>(null);
|
||||
private _errorWhileUpdating$ = new BehaviorSubject<CustomError>(null);
|
||||
errorWhileUpdating$: Observable<CustomError> = this._errorWhileUpdating$.asObservable();
|
||||
employee$ = this.employeeService.employee$;
|
||||
tjanster$: Observable<Tjanst[]> = this.tjanstService.tjanster$;
|
||||
|
||||
availableUtforandeVerksamheter$: Observable<UtforandeVerksamhet[]> = this._selectedTjanstIds$.pipe(
|
||||
filter(selectedTjanstIds => !!selectedTjanstIds?.length),
|
||||
switchMap(selectedTjanstIds => this.utforandeVerksamheterService.fetchUtforandeVerksamheter$(selectedTjanstIds))
|
||||
);
|
||||
|
||||
errorWhileUpdating$: Observable<CustomError> = this._errorWhileUpdating$.asObservable();
|
||||
employee$ = this.employeeService.employee$;
|
||||
tjanster$: Observable<Tjanst[]> = this.tjanstService.tjanster$;
|
||||
availableRoles: Role[] = this.roleService.allRoles;
|
||||
|
||||
isLoadingUtforandeVerksamheter$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private employeeService: EmployeeService,
|
||||
private roleService: RoleService,
|
||||
@@ -68,6 +70,11 @@ export class EmployeeFormComponent implements OnInit {
|
||||
|
||||
setupAvailableUtforandeVerksamheter(selectedTjanster: Tjanst[]): void {
|
||||
this._selectedTjanstIds$.next(selectedTjanster.map(tjanst => tjanst.tjanstId));
|
||||
this.isLoadingUtforandeVerksamheter$ = this.availableUtforandeVerksamheter$.pipe(
|
||||
mapTo(false),
|
||||
catchError(() => of(false)),
|
||||
startWith(true)
|
||||
);
|
||||
}
|
||||
setEmployeeToDelete(employee: Employee): void {
|
||||
this.employeeService.setEmployeeToDelete(employee);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
|
||||
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
|
||||
import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
|
||||
import { DigiNgPopoverModule } from '@af/digi-ng/_popover/popover';
|
||||
import { DigiNgLoaderSpinnerModule } from '@af/digi-ng/_loader/loader-spinner';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
@@ -18,6 +19,8 @@ import { LocalDatePipeModule } from '@msfa-shared/pipes/local-date/local-date.mo
|
||||
import { EmployeeDeleteModule } from '../../components/employee-delete/employee-delete.module';
|
||||
import { EditEmployeeFormComponent } from './edit-employee-form/edit-employee-form.component';
|
||||
import { EmployeeFormComponent } from './employee-form.component';
|
||||
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
|
||||
import { ErrorListModule } from '@msfa-shared/components/error-list/error-list.module';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
@@ -34,11 +37,14 @@ import { EmployeeFormComponent } from './employee-form.component';
|
||||
DigiNgPopoverModule,
|
||||
DigiNgFormCheckboxModule,
|
||||
DigiNgButtonModule,
|
||||
DigiNgLoaderSpinnerModule,
|
||||
DigiNgSkeletonBaseModule,
|
||||
LayoutModule,
|
||||
EmployeeDeleteModule,
|
||||
DigiNgDialogModule,
|
||||
HideTextModule,
|
||||
TreeNodesSelectorModule,
|
||||
ErrorListModule,
|
||||
RolesDialogModule,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<div class="error-list-wrapper" [hidden]="!validationErrorLinks || validationErrorLinks.length === 0">
|
||||
<digi-notification-alert
|
||||
class="error-list"
|
||||
af-variation="danger"
|
||||
[attr.af-heading]="headingText"
|
||||
[af-heading-level]="headingLevel"
|
||||
[afCloseable]="false"
|
||||
>
|
||||
<ul class="error-list__validation-error-links">
|
||||
<li *ngFor="let validationErrorLink of validationErrorLinks;">
|
||||
<digi-ng-link-internal
|
||||
msfaAnchorLink
|
||||
class="error-list__validation-error-link"
|
||||
[afHref]="'#' + validationErrorLink.elementId"
|
||||
[afText]="validationErrorLink.text"
|
||||
></digi-ng-link-internal>
|
||||
</li>
|
||||
</ul>
|
||||
</digi-notification-alert>
|
||||
</div>
|
||||
<!-- <digi-form-error-list
|
||||
class="edit-employee-form__validation-error-summary"
|
||||
af-heading="Åtgärda följande fel för att spara dina ändringar:"
|
||||
*ngIf="validationErrorLinks && validationErrorLinks.length !== 0"
|
||||
>
|
||||
Behöver hantera ankarlänkar kopplat till den här komponenten om det ska fungera att använda den...
|
||||
<a
|
||||
[attr.href]="'#' + validationErrorLink.elementId"
|
||||
[attr.id]="first ? firstValidationErrorLinkId : 'validation-error-link-' + index"
|
||||
*ngFor="let validationErrorLink of validationErrorLinks; let first = first; let index = index"
|
||||
>
|
||||
{{ validationErrorLink.text }}
|
||||
</a>
|
||||
</digi-form-error-list> -->
|
||||
@@ -0,0 +1,15 @@
|
||||
@import 'mixins/list';
|
||||
@import 'variables/gutters';
|
||||
|
||||
.error-list {
|
||||
display: block;
|
||||
margin: 1.5rem 0;
|
||||
|
||||
&__validation-error-links {
|
||||
@include msfa__reset-list;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $digi--layout--gutter;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ErrorListComponent } from './error-list.component';
|
||||
|
||||
describe('ErrorListComponent', () => {
|
||||
let component: ErrorListComponent;
|
||||
let fixture: ComponentFixture<ErrorListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ErrorListComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ErrorListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { TypographyDynamicHeadingLevel } from '@af/digi-ng/_typography/typography-dynamic-heading';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
|
||||
export interface ValidationErrorLink {
|
||||
elementId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-error-list',
|
||||
templateUrl: './error-list.component.html',
|
||||
styleUrls: ['./error-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ErrorListComponent {
|
||||
@Input() validationErrorLinks: ValidationErrorLink[] = [];
|
||||
@Input() headingText: string;
|
||||
@Input() headingLevel: TypographyDynamicHeadingLevel = TypographyDynamicHeadingLevel.H3;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { AnchorLinkModule } from '@msfa-directives/anchor-link.module';
|
||||
import { ErrorListComponent } from './error-list.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [ErrorListComponent],
|
||||
imports: [CommonModule, AnchorLinkModule, DigiNgLinkInternalModule],
|
||||
exports: [ErrorListComponent],
|
||||
})
|
||||
export class ErrorListModule {}
|
||||
@@ -1,88 +1,115 @@
|
||||
<div class="expanded-tree-node" *ngIf="treeNodeModel">
|
||||
<digi-ng-typography-dynamic-heading
|
||||
class="expanded-tree-node__heading"
|
||||
[afText]="getTreeNodeHeadingText(treeNodeModel)"
|
||||
[afLevel]="headingLevel"
|
||||
></digi-ng-typography-dynamic-heading>
|
||||
<p class="expanded-tree-node__info" *ngIf="treeNodeModel.showGeneralInfoAboutGrandChildren">
|
||||
{{treeNodeModel.grandChildrenInfo}}
|
||||
</p>
|
||||
<div
|
||||
*ngIf="treeNodeModel.children && !treeNodeModel.showGeneralInfoAboutGrandChildren"
|
||||
class="expanded-tree-node__filter"
|
||||
>
|
||||
<digi-form-input-search
|
||||
[attr.af-variation]="FormInputSearchVariation.S"
|
||||
[attr.af-button-text]="getFilterButtonAriaLabelText(treeNodeModel)"
|
||||
[attr.af-button-type]="ButtonType.BUTTON"
|
||||
[attr.af-label]="' '"
|
||||
[attr.af-aria-labelledby]="getFilterDescriptionId(treeNodeModel)"
|
||||
(afOnFocusOutside)="onFocusOutsideFilter($event)"
|
||||
(afOnChange)="onFilterTextChanged($event, treeNodeModel)"
|
||||
(afOnKeyup)="onFilterTextChanged($event, treeNodeModel)"
|
||||
></digi-form-input-search>
|
||||
<div class="msfa__a11y-sr-only" [attr.id]="getFilterDescriptionId(treeNodeModel)">
|
||||
Filtrera valbara alternativ där deras namn måste innehålla den angivna texten.
|
||||
</div>
|
||||
<div
|
||||
class="expanded-tree-node"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node--is-root-node' : treeNodeModel?.isRoot
|
||||
}"
|
||||
>
|
||||
<div class="expanded-tree-node__content">
|
||||
<digi-ng-typography-dynamic-heading
|
||||
class="expanded-tree-node__heading"
|
||||
[afText]="headingText"
|
||||
[afLevel]="headingLevel"
|
||||
></digi-ng-typography-dynamic-heading>
|
||||
<ng-container *ngIf="showGeneralInfo else treeNodeChildrenTemplate">
|
||||
<p class="expanded-tree-node__info">{{generalInfo}}</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="hasChildLeafNodes(treeNodeModel)"
|
||||
class="expanded-tree-node__node-checkbox-presentation expanded-tree-node__node-checkbox-presentation--toggle-all"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node__node-checkbox-presentation--focus': treeNodeModel.toggleAllHasFocus,
|
||||
'expanded-tree-node__node-checkbox-presentation--checked': allChildLeafNodesAreSelected(treeNodeModel)
|
||||
}"
|
||||
[attr.id]="getPresentationToggleAllId(treeNodeModel)"
|
||||
(click)="nodePresentationToggleAllClicked(treeNodeModel)"
|
||||
>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__box"></span>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__text">{{treeNodeModel.toggleAllChildrenLabel}}</span>
|
||||
</div>
|
||||
<ng-container *ngIf="visibleChildren">
|
||||
<ul *ngIf="!treeNodeModel.showGeneralInfoAboutGrandChildren" class="expanded-tree-node__nodes">
|
||||
<li
|
||||
*ngFor="let childNode of visibleChildren"
|
||||
[attr.id]="getPresentationItemId(childNode)"
|
||||
[ngClass]="{'expanded-tree-node__node--leaf' : isLeafNode(childNode)}"
|
||||
class="expanded-tree-node__node"
|
||||
>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="childNode.children ? nodeExpansionPresentationTemplate : nodeCheckboxPresentationTemplate"
|
||||
[ngTemplateOutletContext]="{node: childNode, parentNode: treeNodeModel}"
|
||||
></ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #nodeCheckboxPresentationTemplate let-node="node">
|
||||
<div
|
||||
class="expanded-tree-node__node-checkbox-presentation"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node__node-checkbox-presentation--focus': node.hasFocus,
|
||||
'expanded-tree-node__node-checkbox-presentation--checked': node.isSelected
|
||||
}"
|
||||
(click)="nodePresentationItemClicked(node)"
|
||||
>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__box"></span>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__text">{{node.label}}</span>
|
||||
</div>
|
||||
<ng-template #treeNodeChildrenTemplate>
|
||||
<ng-container *ngIf="treeNodeModel">
|
||||
<div *ngIf="treeNodeModel.children" class="expanded-tree-node__filter">
|
||||
<digi-form-input-search
|
||||
[attr.af-variation]="FormInputSearchVariation.S"
|
||||
[attr.af-button-text]="getFilterButtonAriaLabelText(treeNodeModel)"
|
||||
[attr.af-button-type]="ButtonType.BUTTON"
|
||||
[attr.af-label]="' '"
|
||||
[attr.af-aria-labelledby]="'filter-description-'+treeNodeModel.uuid"
|
||||
[attr.af-value]="treeNodeModel.filterText"
|
||||
(afOnFocusOutside)="onFocusOutsideFilter($event)"
|
||||
(afOnChange)="onFilterTextChanged($event, treeNodeModel)"
|
||||
(afOnKeyup)="onFilterTextChanged($event, treeNodeModel)"
|
||||
></digi-form-input-search>
|
||||
<div class="msfa__a11y-sr-only" [attr.id]="'filter-description-'+treeNodeModel.uuid">
|
||||
Filtrera valbara alternativ där deras namn måste innehålla den angivna texten.
|
||||
</div>
|
||||
</div>
|
||||
<digi-form-checkbox
|
||||
*ngIf="hasChildLeafNodes(treeNodeModel)"
|
||||
class="expanded-tree-node__child-node-checkbox expanded-tree-node__child-node-checkbox--toggle-all"
|
||||
[attr.af-label]="treeNodeModel.toggleAllChildrenLabel"
|
||||
[attr.af-aria-labelledby]="'toggle-all-description-'+treeNodeModel.uuid"
|
||||
[attr.af-checked]="allChildLeafNodesAreSelected(treeNodeModel)"
|
||||
(afOnChange)="onToggleAllChildLeafNodes(treeNodeModel)"
|
||||
></digi-form-checkbox>
|
||||
<div class="msfa__a11y-sr-only" [attr.af-id]="'toggle-all-description-'+treeNodeModel.uuid">
|
||||
{{getAriaLabelForToggleAllButton(treeNodeModel)}}
|
||||
</div>
|
||||
<ul *ngIf="visibleChildren" class="expanded-tree-node__nodes">
|
||||
<li *ngFor="let childNode of visibleChildren" class="expanded-tree-node__node">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="childNode.children ? nodeExpansionTemplate : nodeCheckboxTemplate"
|
||||
[ngTemplateOutletContext]="{node: childNode, parentNode: treeNodeModel, isExpanded: isExpandedNode(treeNodeModel, childNode)}"
|
||||
>
|
||||
</ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
<ng-container
|
||||
*ngIf="showGeneralInfoTemplate(treeNodeModel)"
|
||||
[ngTemplateOutlet]="generalInfoPanelTemplate"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #nodeExpansionPresentationTemplate let-node="node" let-parentNode="parentNode">
|
||||
<div
|
||||
class="expanded-tree-node__node-expansion-presentation"
|
||||
<ng-template #nodeCheckboxTemplate let-node="node">
|
||||
<digi-form-checkbox
|
||||
class="expanded-tree-node__child-node-checkbox"
|
||||
[attr.af-id]="'item-'+node.uuid"
|
||||
[attr.af-label]="node.label"
|
||||
[attr.af-checked]="node.isSelected"
|
||||
[attr.af-value]="node.value"
|
||||
(afOnChange)="onToggleSelected(node, $event)"
|
||||
></digi-form-checkbox>
|
||||
</ng-template>
|
||||
<ng-template #nodeExpansionTemplate let-node="node" let-parentNode="parentNode" let-isExpanded="isExpanded">
|
||||
<button
|
||||
type="button"
|
||||
class="expanded-tree-node__child-node-expansion-btn"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node__node-expansion-presentation--focus': node.hasFocus,
|
||||
'expanded-tree-node__node-expansion-presentation--active' : isExpandedNode(parentNode, node)
|
||||
}"
|
||||
(click)="nodePresentationItemClicked(node)"
|
||||
'expanded-tree-node__child-node-expansion-btn--active' : isExpanded
|
||||
}"
|
||||
[attr.aria-expanded]="isExpanded"
|
||||
[attr.aria-label]="getAriaLabelTextForExpansionButton(parentNode, node)"
|
||||
[attr.aria-controls]="'expansion-panel-'+node.uuid"
|
||||
(click)="onSetExpandedChild(parentNode, node)"
|
||||
>
|
||||
<span class="expanded-tree-node__node-expansion-presentation__text">{{node.label}}</span>
|
||||
<span class="expanded-tree-node__child-node-expansion-btn__text">{{node.label}}</span>
|
||||
<span
|
||||
class="expanded-tree-node__node-expansion-presentation__has-selection-dot"
|
||||
class="expanded-tree-node__child-node-expansion-btn__has-selection-dot"
|
||||
*ngIf="hasSelectedDescendant(node)"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<digi-icon-arrow-right class="expanded-tree-node__node-expansion-presentation__icon"></digi-icon-arrow-right>
|
||||
<digi-icon-arrow-right class="expanded-tree-node__child-node-expansion-btn__icon"></digi-icon-arrow-right>
|
||||
</button>
|
||||
<div
|
||||
[attr.id]="'expansion-panel-'+node.uuid"
|
||||
class="expanded-tree-node__child-node-expansion-panel"
|
||||
[ngClass]="{'expanded-tree-node__child-node-expansion-panel--active': isExpanded}"
|
||||
>
|
||||
<msfa-expanded-tree-node
|
||||
*ngIf="isExpanded"
|
||||
[treeNodeModel]="node"
|
||||
[headingText]="node.label"
|
||||
[visibleChildren]="childNodeVisibleChildren"
|
||||
(filterVisibleChildrenRequested)="updateChildNodeVisibleChildren($event)"
|
||||
(selectedNodesChanged)="updateExpandedChildPanel($event)"
|
||||
></msfa-expanded-tree-node>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #generalInfoPanelTemplate>
|
||||
<div class="expanded-tree-node__child-node-expansion-panel expanded-tree-node__child-node-expansion-panel--active">
|
||||
<msfa-expanded-tree-node
|
||||
[headingText]="treeNodeModel.grandChildrenItemType"
|
||||
[showGeneralInfo]="true"
|
||||
[generalInfo]="treeNodeModel.grandChildrenInfo"
|
||||
></msfa-expanded-tree-node>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,24 @@
|
||||
@import 'variables/gutters';
|
||||
|
||||
.expanded-tree-node {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-top: $digi--layout--gutter--m;
|
||||
|
||||
&--is-root-node {
|
||||
position: static;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__filter {
|
||||
padding: 0 0.9375rem;
|
||||
@@ -60,53 +73,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__node-checkbox-presentation {
|
||||
font-size: var(--digi--typography--font-size--s);
|
||||
font-weight: 400;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 1.875rem;
|
||||
cursor: pointer;
|
||||
line-height: normal;
|
||||
|
||||
&__box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
border-radius: 0.1875rem;
|
||||
background-color: var(--digi--ui--color--background);
|
||||
border: 1px solid var(--digi--ui--color--background--dark);
|
||||
}
|
||||
|
||||
&--toggle-all {
|
||||
margin: 1.25rem $digi--layout--gutter;
|
||||
}
|
||||
|
||||
&--checked &__box {
|
||||
background-color: var(--digi--ui--color--success);
|
||||
border-color: var(--digi--ui--color--success);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0.375rem;
|
||||
top: 0.0625rem;
|
||||
width: 0.375rem;
|
||||
height: 0.6875rem;
|
||||
border: solid #fff;
|
||||
border-width: 0 0.125rem 0.125rem 0;
|
||||
transform: rotate(35deg);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
&--focus &__box {
|
||||
box-shadow: var(--digi--ui--outline--focus--m);
|
||||
}
|
||||
}
|
||||
|
||||
&__node-expansion-presentation {
|
||||
&__child-node-expansion-btn {
|
||||
display: inline-flex;
|
||||
padding: 0.3125rem 0.9375rem;
|
||||
justify-content: space-between;
|
||||
@@ -122,7 +89,7 @@
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&--focus {
|
||||
&:focus {
|
||||
background-color: var(--digi--ui--color--background--secondary);
|
||||
}
|
||||
|
||||
@@ -131,7 +98,7 @@
|
||||
color: var(--digi--ui--color--background);
|
||||
|
||||
&:hover,
|
||||
&--focus {
|
||||
&:focus {
|
||||
background-color: lighten($digi--ui--color--primary, 10%);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +106,6 @@
|
||||
&__text {
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
max-width: rem(450);
|
||||
margin-right: $digi--layout--gutter--s;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
@@ -163,4 +129,33 @@
|
||||
max-height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&__child-node-checkbox {
|
||||
display: block;
|
||||
margin: 0.3125rem $digi--layout--gutter;
|
||||
|
||||
&--toggle-all {
|
||||
margin: 1.25rem $digi--layout--gutter;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.digi-form-checkbox__label {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__child-node-expansion-panel {
|
||||
&--active {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
border-left: 1px solid var(--digi--typography--color--text--disabled);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { TypographyDynamicHeadingLevel } from '@af/digi-ng/_typography/typography-dynamic-heading';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
|
||||
import { ButtonType } from '../../../../../pages/avrop/enums/button-type.enum';
|
||||
@@ -26,42 +25,44 @@ import {
|
||||
styleUrls: ['./expanded-tree-node.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements OnInit, OnChanges {
|
||||
@Input() treeNodeModel: TreeNodeModel | null = null;
|
||||
export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements OnChanges {
|
||||
@Input() treeNodeModel: TreeNodeModel;
|
||||
@Input() headingLevel: TypographyDynamicHeadingLevel = TypographyDynamicHeadingLevel.H3;
|
||||
@Input() latestParentActionKey: string;
|
||||
@Output() filterTreeNodeRequested = new EventEmitter<FilterTreeNodeData>();
|
||||
@Output() clickAndFocusElementRequested = new EventEmitter<string>();
|
||||
@Input() headingText: string;
|
||||
@Input() showGeneralInfo: boolean;
|
||||
@Input() generalInfo: string;
|
||||
@Input() visibleChildren: Array<TreeNodeModel>;
|
||||
@Output() filterVisibleChildrenRequested = new EventEmitter<FilterTreeNodeData>();
|
||||
@Output() selectedNodesChanged = new EventEmitter<TreeNodeModel>();
|
||||
|
||||
readonly ButtonType = ButtonType;
|
||||
readonly FormInputSearchVariation = FormInputSearchVariation;
|
||||
|
||||
visibleChildren: Array<TreeNodeModel> | null = null;
|
||||
childNodeVisibleChildren: Array<TreeNodeModel>;
|
||||
|
||||
private readonly filterTreeNodeDebouncer: Subject<FilterTreeNodeData> = new Subject<FilterTreeNodeData>();
|
||||
private readonly filterTreeNodeDebouncer: Subject<string> = new Subject<string>();
|
||||
|
||||
constructor(private treeNodesSelectorService: TreeNodesSelectorService) {
|
||||
super();
|
||||
|
||||
super.unsubscribeOnDestroy(
|
||||
this.filterTreeNodeDebouncer.pipe(debounceTime(200)).subscribe(filterTreeNodeData => {
|
||||
this.filterTreeNodeRequested.emit(filterTreeNodeData);
|
||||
this.filterTreeNodeDebouncer.pipe(debounceTime(200)).subscribe(text => {
|
||||
this.filterVisibleChildrenRequested.emit({ text, treeNode: this.treeNodeModel });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refreshComponentData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.latestParentActionKey || changes.treeNodeModel) {
|
||||
this.refreshComponentData();
|
||||
}
|
||||
}
|
||||
let expandedChildNode: TreeNodeModel | null = null;
|
||||
|
||||
private refreshComponentData(): void {
|
||||
this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(this.treeNodeModel);
|
||||
if (changes.visibleChildren) {
|
||||
expandedChildNode = this.visibleChildren?.find(childNode => this.isExpandedNode(this.treeNodeModel, childNode));
|
||||
|
||||
this.updateChildNodeVisibleChildren({
|
||||
text: expandedChildNode?.filterText,
|
||||
treeNode: expandedChildNode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onFilterTextChanged(event: CustomEvent<{ target: { value: string } }>, treeNode: TreeNodeModel): void {
|
||||
@@ -69,10 +70,7 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O
|
||||
return;
|
||||
}
|
||||
|
||||
this.filterTreeNodeDebouncer.next({
|
||||
text: event.detail.target.value,
|
||||
treeNode,
|
||||
});
|
||||
this.filterTreeNodeDebouncer.next(event.detail.target.value);
|
||||
}
|
||||
|
||||
onFocusOutsideFilter(event: Event): void {
|
||||
@@ -80,42 +78,68 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
nodePresentationToggleAllClicked(treeNode: TreeNodeModel): void {
|
||||
if (!treeNode) {
|
||||
onToggleSelected(treeNode: TreeNodeModel, event: CustomEvent<{ target: { checked: boolean } }>): void {
|
||||
if (!treeNode || !event?.detail?.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clickAndFocusElementRequested.emit(`#${this.treeNodesSelectorService.getToggleAllButtonId(treeNode)}`);
|
||||
treeNode.isSelected = event?.detail?.target?.checked;
|
||||
|
||||
this.selectedNodesChanged.emit(this.treeNodeModel);
|
||||
}
|
||||
|
||||
nodePresentationItemClicked(treeNode: TreeNodeModel): void {
|
||||
if (!treeNode) {
|
||||
onToggleAllChildLeafNodes(treeNode: TreeNodeModel): void {
|
||||
let allChildLeafNodesAreSelected = false;
|
||||
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clickAndFocusElementRequested.emit(`#${this.treeNodesSelectorService.getItemButtonId(treeNode)}`);
|
||||
allChildLeafNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode);
|
||||
|
||||
treeNode.children = treeNode.children.map(child => {
|
||||
return { ...child, isSelected: !allChildLeafNodesAreSelected };
|
||||
});
|
||||
|
||||
this.selectedNodesChanged.emit(this.treeNodeModel);
|
||||
}
|
||||
|
||||
getPresentationItemId(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getPresentationItemId(treeNode);
|
||||
onSetExpandedChild(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): void {
|
||||
if (!parentTreeNode || !treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
parentTreeNode.expandedChildUuid = parentTreeNode.expandedChildUuid === treeNode.uuid ? null : treeNode.uuid;
|
||||
|
||||
this.updateChildNodeVisibleChildren({ text: treeNode.filterText, treeNode });
|
||||
}
|
||||
|
||||
getPresentationToggleAllId(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getPresentationToggleAllId(treeNode);
|
||||
updateExpandedChildPanel(treeNode: TreeNodeModel): void {
|
||||
if (!this.childNodeVisibleChildren) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.childNodeVisibleChildren = this.treeNodesSelectorService.getVisibleChildren(treeNode);
|
||||
}
|
||||
|
||||
getFilterDescriptionId(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getFilterDescriptionId(treeNode);
|
||||
updateChildNodeVisibleChildren(filterTreeNodeData: FilterTreeNodeData): void {
|
||||
if (!filterTreeNodeData || !filterTreeNodeData.treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren(
|
||||
filterTreeNodeData
|
||||
);
|
||||
|
||||
filterTreeNodeData.treeNode.filterText = filterTreeNodeData.text;
|
||||
|
||||
this.childNodeVisibleChildren = this.treeNodesSelectorService.getVisibleChildren(filterTreeNodeData.treeNode);
|
||||
}
|
||||
|
||||
getFilterButtonAriaLabelText(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getFilterButtonAriaLabelText(treeNode);
|
||||
}
|
||||
|
||||
getTreeNodeHeadingText(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getTreeNodeHeadingText(treeNode);
|
||||
}
|
||||
|
||||
hasChildLeafNodes(node: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.hasChildLeafNodes(node);
|
||||
}
|
||||
@@ -135,4 +159,16 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O
|
||||
hasSelectedDescendant(treeNode: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.hasSelectedLeafNodeDescendant(treeNode);
|
||||
}
|
||||
|
||||
getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getAriaLabelTextForExpansionButton(parentTreeNode, treeNode);
|
||||
}
|
||||
|
||||
getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getAriaLabelForToggleAllButton(treeNode);
|
||||
}
|
||||
|
||||
showGeneralInfoTemplate(treeNode: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.showGeneralInfoTemplate(treeNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<digi-util-detect-focus-outside (afOnFocusOutside)="closePanel()">
|
||||
<digi-util-keydown-handler (afOnEsc)="closePanel()">
|
||||
<section class="tree-nodes-selector-panel" #treeNodesSelectorPanel>
|
||||
<section class="tree-nodes-selector-panel">
|
||||
<header>
|
||||
<h2 class="tree-nodes-selector-panel__heading">{{headingText}}</h2>
|
||||
<p class="msfa__a11y-sr-only">
|
||||
@@ -18,21 +18,13 @@
|
||||
</button>
|
||||
</header>
|
||||
<div class="tree-nodes-selector-panel__content" *ngIf="pendingRootNode">
|
||||
<ul class="tree-nodes-selector-panel__expanded-nodes" aria-hidden="true" *ngIf="expandedTreeNodes">
|
||||
<li class="tree-nodes-selector-panel__expanded-node" *ngFor="let node of expandedTreeNodes">
|
||||
<msfa-expanded-tree-node
|
||||
[treeNodeModel]="node"
|
||||
[latestParentActionKey]="latestParentActionKey"
|
||||
(filterTreeNodeRequested)="filterTreeNode($event)"
|
||||
(clickAndFocusElementRequested)="clickAndFocusElementByQuerySelector($event)"
|
||||
></msfa-expanded-tree-node>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="msfa__a11y-sr-only">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{node: pendingRootNode}"
|
||||
></ng-container>
|
||||
<div class="tree-nodes-selector-panel__tree">
|
||||
<msfa-expanded-tree-node
|
||||
[treeNodeModel]="pendingRootNode"
|
||||
[visibleChildren]="visibleChildren"
|
||||
(selectedNodesChanged)="updateExpandedChildPanel($event)"
|
||||
(filterVisibleChildrenRequested)="updateVisibleChildren($event)"
|
||||
></msfa-expanded-tree-node>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
@@ -45,64 +37,6 @@
|
||||
{{confirmationButtonText}}
|
||||
</digi-button>
|
||||
</footer>
|
||||
<ng-template #nodeTemplate let-node="node">
|
||||
<div class="tree-nodes-selector-panel__level">
|
||||
<h3 class="tree-nodes-selector-panel__heading">{{node.label}}</h3>
|
||||
<digi-form-checkbox
|
||||
*ngIf="hasChildLeafNodes(node)"
|
||||
[attr.af-id]="getToggleAllButtonId(node)"
|
||||
[attr.af-label]="node.selectAllChildrenLabel"
|
||||
[attr.af-aria-labelledby]="'toggle-all-description-'+node.uuid"
|
||||
[attr.af-checked]="allChildLeafNodesAreSelected(node)"
|
||||
(afOnChange)="toggleAllChildLeafNodes(node)"
|
||||
(afOnFocus)="setFocusOnToggleAll(node, true)"
|
||||
(afOnBlur)="setFocusOnToggleAll(node, false)"
|
||||
></digi-form-checkbox>
|
||||
<div [attr.af-id]="'toggle-all-description-'+node.uuid">{{getAriaLabelForToggleAllButton(node)}}</div>
|
||||
<ul *ngIf="getVisibleChildren(node) as visibleChildren" class="tree-nodes-selector-panel__nodes">
|
||||
<li
|
||||
*ngFor="let childNode of visibleChildren"
|
||||
class="tree-nodes-selector-panel__node"
|
||||
[ngClass]="{'tree-nodes-selector-panel__node--expanded': isExpandedNode(node, childNode)}"
|
||||
>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="childNode.children ? nodeExpansionTemplate : nodeCheckboxTemplate"
|
||||
[ngTemplateOutletContext]="{node: childNode, parentNode: node}"
|
||||
>
|
||||
</ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #nodeCheckboxTemplate let-node="node">
|
||||
<digi-form-checkbox
|
||||
[attr.af-id]="'item-'+node.uuid"
|
||||
[attr.af-label]="node.label"
|
||||
[attr.af-checked]="node.isSelected"
|
||||
[attr.af-value]="node.value"
|
||||
(afOnChange)="onToggleSelected(node, $event)"
|
||||
(afOnFocus)="setFocusOnNodeItem(node, true)"
|
||||
(afOnBlur)="setFocusOnNodeItem(node, false)"
|
||||
></digi-form-checkbox>
|
||||
</ng-template>
|
||||
<ng-template #nodeExpansionTemplate let-node="node" let-parentNode="parentNode">
|
||||
<button
|
||||
type="button"
|
||||
[attr.id]="getItemButtonId(node)"
|
||||
[attr.aria-label]="getAriaLabelTextForExpansionButton(parentNode, node)"
|
||||
(click)="setExpandedChild(parentNode, node)"
|
||||
(focus)="setFocusOnNodeItem(node, true)"
|
||||
(blur)="setFocusOnNodeItem(node, false)"
|
||||
>
|
||||
{{node.label}}
|
||||
</button>
|
||||
<ng-container
|
||||
*ngIf="isExpandedNode(parentNode, node)"
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{node: node}"
|
||||
>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</section>
|
||||
</digi-util-keydown-handler>
|
||||
</digi-util-detect-focus-outside>
|
||||
|
||||
@@ -41,28 +41,15 @@
|
||||
font-size: var(--digi--typography--font-size--xs);
|
||||
}
|
||||
|
||||
&__expanded-nodes {
|
||||
@include msfa__reset-list;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
&__content {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&__expanded-node {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 50%;
|
||||
&__tree {
|
||||
max-width: 50%;
|
||||
overflow: auto;
|
||||
padding-top: $digi--layout--gutter--m;
|
||||
padding-bottom: $digi--layout--gutter--l;
|
||||
border-left: 1px solid var(--digi--typography--color--text--disabled);
|
||||
|
||||
&:first-child {
|
||||
border-left-width: 0;
|
||||
}
|
||||
height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import { ButtonSize } from '@af/digi-ng/_button/button';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import {
|
||||
FilterTreeNodeData,
|
||||
TreeNodeModel,
|
||||
@@ -28,13 +19,10 @@ export class TreeNodesSelectorPanelComponent implements OnInit {
|
||||
@Output() selectedChangesConfirmed = new EventEmitter<TreeNodeModel>();
|
||||
@Output() closePanelRequested = new EventEmitter<void>();
|
||||
|
||||
@ViewChild('treeNodesSelectorPanel') treeNodesSelector: ElementRef;
|
||||
|
||||
readonly ButtonSize = ButtonSize;
|
||||
|
||||
pendingRootNode: TreeNodeModel | null = null;
|
||||
expandedTreeNodes: Array<TreeNodeModel> | null = null;
|
||||
latestParentActionKey: string = null;
|
||||
visibleChildren: Array<TreeNodeModel> | null = null;
|
||||
|
||||
constructor(private treeNodesSelectorService: TreeNodesSelectorService) {}
|
||||
|
||||
@@ -44,123 +32,18 @@ export class TreeNodesSelectorPanelComponent implements OnInit {
|
||||
|
||||
private setupPendingRootNode(): void {
|
||||
this.pendingRootNode = this.treeNodesSelectorService.getClonedTreeNode(this.rootNode);
|
||||
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
|
||||
}
|
||||
|
||||
onToggleSelected(treeNode: TreeNodeModel, event: CustomEvent<{ target: { checked: boolean } }>): void {
|
||||
if (!treeNode || !event?.detail?.target) {
|
||||
if (!this.pendingRootNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
treeNode.isSelected = event?.detail?.target?.checked;
|
||||
|
||||
// eslint-disable-next-line
|
||||
this.latestParentActionKey = `${treeNode.uuid}-selected-${treeNode.isSelected}`;
|
||||
}
|
||||
|
||||
setExpandedChild(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): void {
|
||||
if (!parentTreeNode || !treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
parentTreeNode.expandedChildUuid = parentTreeNode.expandedChildUuid === treeNode.uuid ? null : treeNode.uuid;
|
||||
|
||||
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
|
||||
|
||||
this.latestParentActionKey = `${parentTreeNode.uuid}-expanded-child-${parentTreeNode.expandedChildUuid}`;
|
||||
}
|
||||
|
||||
setFocusOnToggleAll(treeNode: TreeNodeModel, hasFocus: boolean): void {
|
||||
let presentationToggleAllElement: HTMLElement = null;
|
||||
let expansionColumnElement: HTMLElement = null;
|
||||
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
treeNode.toggleAllHasFocus = hasFocus;
|
||||
|
||||
if (!hasFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
presentationToggleAllElement = this.treeNodesSelectorService.getPresentationToggleAllElement(
|
||||
this.treeNodesSelector,
|
||||
treeNode
|
||||
);
|
||||
|
||||
expansionColumnElement = this.treeNodesSelectorService.getExpansionColumnElement(presentationToggleAllElement);
|
||||
|
||||
this.scrollElementInContainer(expansionColumnElement);
|
||||
}
|
||||
|
||||
private scrollElementInContainer(element: HTMLElement): void {
|
||||
let elementRect: DOMRect = null;
|
||||
let parentElementRect: DOMRect = null;
|
||||
|
||||
if (!element || !element.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.treeNodesSelectorService.isFullyVisibleElement(element, element.parentElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
elementRect = element.getBoundingClientRect();
|
||||
|
||||
parentElementRect = element.parentElement.getBoundingClientRect();
|
||||
|
||||
element.parentElement.scrollTop = elementRect.top + element.parentElement.scrollTop - parentElementRect.top;
|
||||
element.parentElement.scrollLeft = elementRect.left + element.parentElement.scrollLeft - parentElementRect.left;
|
||||
}
|
||||
|
||||
setFocusOnNodeItem(treeNode: TreeNodeModel, hasFocus: boolean): void {
|
||||
let presentationItemElement: HTMLElement = null;
|
||||
let expansionColumnElement: HTMLElement = null;
|
||||
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
treeNode.hasFocus = hasFocus;
|
||||
|
||||
if (!hasFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.latestParentActionKey = `${treeNode.uuid}-focused-item`;
|
||||
|
||||
presentationItemElement = this.treeNodesSelectorService.getPresentationItemElement(
|
||||
this.treeNodesSelector,
|
||||
treeNode
|
||||
);
|
||||
|
||||
expansionColumnElement = this.treeNodesSelectorService.getExpansionColumnElement(presentationItemElement);
|
||||
|
||||
this.scrollElementInContainer(presentationItemElement);
|
||||
this.scrollElementInContainer(expansionColumnElement);
|
||||
}
|
||||
|
||||
toggleAllChildLeafNodes(treeNode: TreeNodeModel): void {
|
||||
let allChildLeafNodesAreSelected = false;
|
||||
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
allChildLeafNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode);
|
||||
|
||||
treeNode.children = treeNode.children.map(child => {
|
||||
return { ...child, isSelected: !allChildLeafNodesAreSelected };
|
||||
});
|
||||
|
||||
this.latestParentActionKey = `${treeNode.uuid}-selected-${allChildLeafNodesAreSelected.toString()}`;
|
||||
this.pendingRootNode.isRoot = true;
|
||||
this.updateExpandedChildPanel(this.pendingRootNode);
|
||||
}
|
||||
|
||||
clearSelections(): void {
|
||||
this.pendingRootNode = this.treeNodesSelectorService.getTreeNodeWithNoNodesSelected(this.pendingRootNode);
|
||||
this.latestParentActionKey = `cleared-selections`;
|
||||
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
|
||||
this.updateExpandedChildPanel(this.pendingRootNode);
|
||||
}
|
||||
|
||||
closePanel(): void {
|
||||
@@ -168,65 +51,21 @@ export class TreeNodesSelectorPanelComponent implements OnInit {
|
||||
this.closePanelRequested.emit();
|
||||
}
|
||||
|
||||
filterTreeNode(filterTreeNodeData: FilterTreeNodeData): void {
|
||||
if (!filterTreeNodeData || !filterTreeNodeData.treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren(
|
||||
filterTreeNodeData
|
||||
);
|
||||
|
||||
this.latestParentActionKey = `${filterTreeNodeData.treeNode.uuid}-filtered-${filterTreeNodeData.text}`;
|
||||
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
|
||||
}
|
||||
|
||||
clickAndFocusElementByQuerySelector(selector: string): void {
|
||||
const selectedItem = this.treeNodesSelector
|
||||
? this.treeNodesSelectorService.getElementByQuerySelector(this.treeNodesSelector, selector)
|
||||
: null;
|
||||
|
||||
if (!selectedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedItem.focus();
|
||||
selectedItem.click();
|
||||
}
|
||||
|
||||
getVisibleChildren(treeNode: TreeNodeModel): Array<TreeNodeModel> | null {
|
||||
return this.treeNodesSelectorService.getVisibleChildren(treeNode);
|
||||
}
|
||||
|
||||
hasChildLeafNodes(node: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.hasChildLeafNodes(node);
|
||||
}
|
||||
|
||||
allChildLeafNodesAreSelected(node: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.allChildLeafNodesAreSelected(node);
|
||||
}
|
||||
|
||||
hasSelectedDescendant(treeNode: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.hasSelectedLeafNodeDescendant(treeNode);
|
||||
}
|
||||
|
||||
getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getAriaLabelTextForExpansionButton(parentTreeNode, treeNode);
|
||||
updateExpandedChildPanel(treeNode: TreeNodeModel): void {
|
||||
this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(treeNode);
|
||||
}
|
||||
|
||||
getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getAriaLabelForToggleAllButton(treeNode);
|
||||
}
|
||||
updateVisibleChildren(filterTreeNodeData: FilterTreeNodeData): void {
|
||||
filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren(
|
||||
filterTreeNodeData
|
||||
);
|
||||
|
||||
isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean {
|
||||
return this.treeNodesSelectorService.isExpandedNode(parentTreeNode, treeNode);
|
||||
}
|
||||
filterTreeNodeData.treeNode.filterText = filterTreeNodeData.text;
|
||||
|
||||
getToggleAllButtonId(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getToggleAllButtonId(treeNode);
|
||||
}
|
||||
|
||||
getItemButtonId(treeNode: TreeNodeModel): string {
|
||||
return this.treeNodesSelectorService.getItemButtonId(treeNode);
|
||||
this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(filterTreeNodeData.treeNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<button
|
||||
#togglePanelBtn
|
||||
(click)="togglePanel()"
|
||||
[attr.id]="buttonElementId"
|
||||
[attr.aria-controls]="panelId"
|
||||
[ngClass]="{'tree-nodes-selector__toggle-panel-btn--invalid': isInvalid && showValidation}"
|
||||
type="button"
|
||||
|
||||
@@ -36,6 +36,7 @@ interface PropagateTouchedFn {
|
||||
],
|
||||
})
|
||||
export class TreeNodesSelectorComponent implements ControlValueAccessor {
|
||||
@Input() buttonElementId: string = UUID.UUID();
|
||||
@Input() headingText: string;
|
||||
@Input() confirmationButtonText = 'Spara';
|
||||
@Input() isInvalid = false;
|
||||
|
||||
@@ -59,71 +59,6 @@ describe('TreeNodesSelectorService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExpandedTreeNodes', () => {
|
||||
it('should return a list of all tree nodes that are in an expanded chain starting with the provided tree node.', () => {
|
||||
const treeNode: TreeNodeModel = {
|
||||
label: 'root',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1',
|
||||
expandedChildUuid: '1_1',
|
||||
children: [
|
||||
{
|
||||
label: 'level 1',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1_1',
|
||||
expandedChildUuid: '1_1_2',
|
||||
children: [
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1_1_1',
|
||||
},
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: true,
|
||||
value: null,
|
||||
uuid: '1_1_2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'level 1',
|
||||
isSelected: true,
|
||||
value: null,
|
||||
uuid: '1_2',
|
||||
expandedChildUuid: '1_2_2',
|
||||
children: [
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1_2_1',
|
||||
},
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: true,
|
||||
value: null,
|
||||
uuid: '1_2_2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = service.getExpandedTreeNodes(treeNode);
|
||||
|
||||
expect(result).not.toBeUndefined();
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.length).toEqual(3);
|
||||
expect(result[0].uuid).toEqual('1');
|
||||
expect(result[1].uuid).toEqual('1_1');
|
||||
expect(result[2].uuid).toEqual('1_1_2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFilteredTreeNodeChildren', () => {
|
||||
it('should set the propert hideTreeNode to true for all children with a label that contains the provided text when compared case insensitive, trimming white space and return them.', () => {
|
||||
const treeNode: TreeNodeModel = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ElementRef, Injectable } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UUID } from 'angular2-uuid';
|
||||
|
||||
export interface TreeNode {
|
||||
@@ -18,8 +18,9 @@ export interface TreeNodeModel extends TreeNode {
|
||||
children?: Array<TreeNodeModel>;
|
||||
hasFocus?: boolean;
|
||||
toggleAllHasFocus?: boolean;
|
||||
showGeneralInfoAboutGrandChildren?: boolean;
|
||||
hideTreeNode?: boolean;
|
||||
isRoot?: boolean;
|
||||
filterText?: string;
|
||||
}
|
||||
|
||||
export interface FilterTreeNodeData {
|
||||
@@ -82,30 +83,7 @@ export class TreeNodesSelectorService {
|
||||
return `${isActiveNode ? 'Döljer' : 'Visar'} ${treeNode.grandChildrenItemType} för ${treeNode.label}`;
|
||||
}
|
||||
|
||||
isFullyVisibleElement(element: HTMLElement, container: HTMLElement): boolean {
|
||||
const { bottom, top, left, right } = element.getBoundingClientRect();
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
if (top <= containerRect.top && containerRect.top - top > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bottom - containerRect.bottom > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (left <= containerRect.left && containerRect.left - left > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (right - containerRect.right > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hasChildLeafNodes(node: TreeNodeModel): boolean {
|
||||
hasChildLeafNodes(node: TreeNode): boolean {
|
||||
if (!node || !node.children || node.children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -125,42 +103,6 @@ export class TreeNodesSelectorService {
|
||||
return !node.children.some(child => this.isLeafNode(child) && !child.isSelected);
|
||||
}
|
||||
|
||||
getElementByQuerySelector(elementRef: ElementRef, selector: string): HTMLElement {
|
||||
const element = elementRef?.nativeElement as HTMLElement;
|
||||
|
||||
return element?.querySelector(selector);
|
||||
}
|
||||
|
||||
getPresentationItemElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
|
||||
return this.getElementByQuerySelector(elementRef, `#${this.getPresentationItemId(treeNode)}`);
|
||||
}
|
||||
|
||||
getPresentationToggleAllElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
|
||||
return this.getElementByQuerySelector(elementRef, `#${this.getPresentationToggleAllId(treeNode)}`);
|
||||
}
|
||||
|
||||
getExpansionColumnElement(element: HTMLElement): HTMLElement {
|
||||
return element?.closest('.tree-nodes-selector-panel__expanded-node');
|
||||
}
|
||||
|
||||
getExpandedTreeNodes(treeNode: TreeNodeModel): Array<TreeNodeModel> {
|
||||
let expandedChildNode: TreeNodeModel | null = null;
|
||||
|
||||
if (!treeNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
expandedChildNode = treeNode.children?.find(childNode => this.isExpandedNode(treeNode, childNode));
|
||||
|
||||
if (expandedChildNode) {
|
||||
return [treeNode].concat(this.getExpandedTreeNodes(expandedChildNode));
|
||||
}
|
||||
|
||||
return treeNode.children && treeNode.children.some(child => child.children && child.children.length > 0)
|
||||
? [treeNode, { ...treeNode, children: undefined, showGeneralInfoAboutGrandChildren: true }]
|
||||
: [treeNode];
|
||||
}
|
||||
|
||||
isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean {
|
||||
if (!parentTreeNode || !treeNode) {
|
||||
return false;
|
||||
@@ -210,35 +152,7 @@ export class TreeNodesSelectorService {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `Filtrera ${treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label}`;
|
||||
}
|
||||
|
||||
getTreeNodeHeadingText(treeNode: TreeNodeModel): string {
|
||||
if (!treeNode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label;
|
||||
}
|
||||
|
||||
getToggleAllButtonId(treeNode: TreeNodeModel): string {
|
||||
return `toggle-all-${treeNode.uuid}`;
|
||||
}
|
||||
|
||||
getItemButtonId(treeNode: TreeNodeModel): string {
|
||||
return `item-${treeNode.uuid}`;
|
||||
}
|
||||
|
||||
getPresentationItemId(treeNode: TreeNodeModel): string {
|
||||
return `presentation-item-${treeNode?.uuid}`;
|
||||
}
|
||||
|
||||
getPresentationToggleAllId(treeNode: TreeNodeModel): string {
|
||||
return `presentation-toggle-all-${treeNode?.uuid}`;
|
||||
}
|
||||
|
||||
getFilterDescriptionId(treeNode: TreeNodeModel): string {
|
||||
return `filter-description-${treeNode?.uuid}`;
|
||||
return `Filtrera ${treeNode.label}`;
|
||||
}
|
||||
|
||||
getCleanedText(text: string): string {
|
||||
@@ -360,4 +274,20 @@ export class TreeNodesSelectorService {
|
||||
? treeNode.isSelected
|
||||
: !treeNode.children.some(childNode => !this.hasSelectedAllLeafNodes(childNode));
|
||||
}
|
||||
|
||||
hasExpandedChild(treeNode: TreeNode): boolean {
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return treeNode.children.some(childNode => this.isExpandedNode(treeNode, childNode));
|
||||
}
|
||||
|
||||
showGeneralInfoTemplate(treeNode: TreeNode): boolean {
|
||||
if (!treeNode || !treeNode.grandChildrenInfo || this.hasExpandedChild(treeNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.getCleanedText(treeNode.grandChildrenInfo).length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AnchorLinkDirective } from './anchor-link.directive';
|
||||
|
||||
describe('AnchorLinkDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new AnchorLinkDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Directive, HostListener } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[msfaAnchorLink]',
|
||||
})
|
||||
export class AnchorLinkDirective {
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(event: MouseEvent): void {
|
||||
const target = event.target as HTMLElement;
|
||||
const link = target.tagName === 'a' ? target : target.closest('a');
|
||||
const href = link?.getAttribute('href');
|
||||
const element = document.getElementById(href?.trim().replace('#', ''));
|
||||
|
||||
if (element && element.focus) {
|
||||
element.focus();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AnchorLinkDirective } from './anchor-link.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AnchorLinkDirective],
|
||||
exports: [AnchorLinkDirective],
|
||||
})
|
||||
export class AnchorLinkModule {}
|
||||
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { environment } from '@msfa-environment';
|
||||
import { Params } from '@msfa-models/api/params.model';
|
||||
import { UtforandeVerksamhetResponse } from '@msfa-models/api/utforande-verksamhet.response.model';
|
||||
import { EmployeeUtforandeVerksamhet } from '@msfa-models/employee-utforande-verksamhet.model';
|
||||
import { UtforandeAdress } from '@msfa-models/utforande-adress.model';
|
||||
import { mapResponseToUtforandeVerksamhet, UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model';
|
||||
import {
|
||||
@@ -21,7 +22,7 @@ export class UtforandeVerksamheterService {
|
||||
constructor(private treeNodesSelectorService: TreeNodesSelectorService, private httpClient: HttpClient) {}
|
||||
|
||||
fetchUtforandeVerksamheter$(tjanstIds: number[]): Observable<UtforandeVerksamhet[]> {
|
||||
if (!tjanstIds.length) {
|
||||
if (!tjanstIds?.length) {
|
||||
return of<UtforandeVerksamhet[]>([]);
|
||||
}
|
||||
|
||||
@@ -38,10 +39,18 @@ export class UtforandeVerksamheterService {
|
||||
return selectedUtforandeVerksamheter.map(uv => uv.adresser.map(adress => adress.id)).flat();
|
||||
}
|
||||
|
||||
getTreeNodeDataFromUtforandeVerksamheter(utforandeVerksamhetList: UtforandeVerksamhet[]): TreeNode | null {
|
||||
getTreeNodeDataFromUtforandeVerksamheter(
|
||||
availableUtforandeVerksamhetList: UtforandeVerksamhet[],
|
||||
selectedUtforandeVerksamhetList: EmployeeUtforandeVerksamhet[],
|
||||
selectAll = false
|
||||
): TreeNode | null {
|
||||
let treeNode: TreeNode | null = null;
|
||||
|
||||
if (!utforandeVerksamhetList || utforandeVerksamhetList.length === 0 || !Array.isArray(utforandeVerksamhetList)) {
|
||||
if (
|
||||
!availableUtforandeVerksamhetList ||
|
||||
availableUtforandeVerksamhetList.length === 0 ||
|
||||
!Array.isArray(availableUtforandeVerksamhetList)
|
||||
) {
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
@@ -52,17 +61,24 @@ export class UtforandeVerksamheterService {
|
||||
isSelected: false,
|
||||
value: null,
|
||||
childItemType: 'Utförande verksamheter',
|
||||
children: utforandeVerksamhetList.map(
|
||||
children: availableUtforandeVerksamhetList.map(
|
||||
(utforandeVerksamhet: UtforandeVerksamhet): TreeNode => {
|
||||
const utforandeVerksahmetTreeNode: TreeNode = {
|
||||
label: utforandeVerksamhet.name,
|
||||
toggleAllChildrenLabel: 'Välj alla adresser',
|
||||
isSelected: false,
|
||||
isSelected:
|
||||
selectAll || this.isSelectedUtforandeVerksamhet(utforandeVerksamhet, selectedUtforandeVerksamhetList),
|
||||
value: utforandeVerksamhet,
|
||||
childItemType: 'Adresser',
|
||||
children: utforandeVerksamhet.adresser
|
||||
? utforandeVerksamhet.adresser.map(adress => {
|
||||
return { label: adress.name, isSelected: false, value: adress };
|
||||
return {
|
||||
label: adress.name,
|
||||
isSelected:
|
||||
selectAll ||
|
||||
this.isSelectedUtforandeAdress(utforandeVerksamhet.id, adress, selectedUtforandeVerksamhetList),
|
||||
value: adress,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
};
|
||||
@@ -75,6 +91,52 @@ export class UtforandeVerksamheterService {
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
isSelectedUtforandeVerksamhet(
|
||||
utforandeVerksamhet: UtforandeVerksamhet,
|
||||
selectedUtforandeVerksamhetList: EmployeeUtforandeVerksamhet[]
|
||||
): boolean {
|
||||
if (
|
||||
!utforandeVerksamhet ||
|
||||
!selectedUtforandeVerksamhetList ||
|
||||
selectedUtforandeVerksamhetList.length === 0 ||
|
||||
!Array.isArray(selectedUtforandeVerksamhetList)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selectedUtforandeVerksamhetList.some(
|
||||
selectedUtforandeVerksamhet => selectedUtforandeVerksamhet.id === utforandeVerksamhet.id
|
||||
);
|
||||
}
|
||||
|
||||
isSelectedUtforandeAdress(
|
||||
utforandeVerksamhetId: number,
|
||||
utforandeAdress: UtforandeAdress,
|
||||
selectedUtforandeVerksamhetList: EmployeeUtforandeVerksamhet[]
|
||||
): boolean {
|
||||
let selectedUtforandeVerksamhet: EmployeeUtforandeVerksamhet | null = null;
|
||||
|
||||
if (
|
||||
!utforandeAdress ||
|
||||
!selectedUtforandeVerksamhetList ||
|
||||
selectedUtforandeVerksamhetList.length === 0 ||
|
||||
!Array.isArray(selectedUtforandeVerksamhetList)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
selectedUtforandeVerksamhet = selectedUtforandeVerksamhetList.find(
|
||||
selectedUtforandeVerksamhet => selectedUtforandeVerksamhet.id === utforandeVerksamhetId
|
||||
);
|
||||
|
||||
return selectedUtforandeVerksamhet
|
||||
? selectedUtforandeVerksamhet.allaAdresser ||
|
||||
selectedUtforandeVerksamhet.adresser.some(
|
||||
selectedUtforandeAdress => selectedUtforandeAdress.id === utforandeAdress.id
|
||||
)
|
||||
: false;
|
||||
}
|
||||
|
||||
getSelectedUtforandeVerksamheterFromTreeNode(treeNode: TreeNode): UtforandeVerksamhet[] {
|
||||
let utforandeVerksamhetList: UtforandeVerksamhet[] = [];
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FormGroup, ValidatorFn } from '@angular/forms';
|
||||
import { TreeNode } from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service';
|
||||
|
||||
export class EmployeeValidator {
|
||||
static HasSelectedAtLeastOneRole(roleFormControlNames: Array<string>): ValidatorFn {
|
||||
@@ -12,4 +13,20 @@ export class EmployeeValidator {
|
||||
: { noRoleSelected: true };
|
||||
};
|
||||
}
|
||||
|
||||
static HasSelectedAtLeastOneUtforandeVerksamhet(
|
||||
utforandeVerksamheterFormControlName: string,
|
||||
selectAllUtforandeVerksamheterFormControlName: string,
|
||||
validationFn: (treeNode: TreeNode | null | undefined) => boolean
|
||||
): ValidatorFn {
|
||||
return (fg: FormGroup): { [key: string]: unknown } => {
|
||||
if (fg?.get(selectAllUtforandeVerksamheterFormControlName)?.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return validationFn(fg?.get(utforandeVerksamheterFormControlName)?.value)
|
||||
? null
|
||||
: { noUtforandeVerksamhetSelected: true };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import { TreeNode } from '@msfa-shared/components/tree-nodes-selector/services/t
|
||||
export class TreeNodeValidator {
|
||||
static IsValidTreeNode(
|
||||
validationFn: (treeNode: TreeNode | null | undefined) => boolean,
|
||||
nameOfError: string,
|
||||
allUtforandeVerksamheterFormControl: AbstractControl
|
||||
nameOfError: string
|
||||
): ValidatorFn {
|
||||
return (control: AbstractControl): { [key: string]: unknown } => {
|
||||
const isValid = validationFn(control.value);
|
||||
|
||||
const validationObj = {};
|
||||
|
||||
if (isValid || allUtforandeVerksamheterFormControl?.value) {
|
||||
if (isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user