feat(employee): Showing employee-data inside employee-card page. (TV-341)

Squashed commit of the following:

commit 4fd71e0d0655a0d75dda59151ac74e2361f187bc
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Aug 20 10:59:46 2021 +0200

    Updated RoleEnum and mock-data för roles

commit f05a93239727ce1245650ece3b48cf75dc7091ca
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Aug 20 08:34:20 2021 +0200

    Fixed issue with mock-api

commit c31e94da6b90e442fd84c5113789db245be81c6d
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Aug 20 08:26:14 2021 +0200

    Fixed issue with tjanster

commit a183a08f0446cdaea7d01c8935d88ac436b0438f
Merge: eb310c1 1e2d925
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Aug 20 07:59:06 2021 +0200

    Merge branch 'develop' into feature/TV-341-erik

commit eb310c10bdf0f4b60032bdda97df75c19bdbf447
Merge: 877b68b fae7d9a
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Aug 20 07:21:39 2021 +0200

    Merged develop and fixed conflicts

commit 877b68b8827e89cfd230856c9d8247f1cd8db264
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Aug 19 15:01:55 2021 +0200

    Now fetching tjanster from mock-api

commit 1ecd26595b21ea46ce6fb0c193c6642f66250ae3
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Aug 19 14:24:11 2021 +0200

    Some more changes to employee-card

commit e42ae254e7aa7b994627fdccb7037493b116d6a2
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Aug 19 13:44:23 2021 +0200

    Added new enum Role and fixed some issues mapping roles inside employee-card

commit 7801831d83feae5ef5e5b92e6421b18863b2a1db
Merge: c78f3f8 d6e4666
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Aug 19 12:37:56 2021 +0200

    Merge branch 'develop' into feature/TV-341-fe-anpassa-personal-kortet-sa-att-den-ar-enligt-skiss

commit c78f3f886752477d2dbc4af20356252af4128440
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Thu Aug 19 11:19:56 2021 +0200

    Authorization

commit 9c043720a9cfa5fd6943013f643d948a50c8f135
Merge: 77d6a9c ffeb372
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Thu Aug 19 11:19:43 2021 +0200

    Merge branch 'develop' into feature/TV-341-fe-anpassa-personal-kortet-sa-att-den-ar-enligt-skiss

    # Conflicts:
    #	apps/mina-sidor-fa/src/app/pages/administration/pages/employee-card/employee-card.component.scss
    #	apps/mina-sidor-fa/src/app/pages/administration/pages/employee-card/employee-card.module.ts
    #	apps/mina-sidor-fa/src/app/shared/enums/employee-authorization.enum.ts

commit 77d6a9c600e6b42e97fa5431ed37ad430c5febd3
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Wed Aug 18 10:22:45 2021 +0200

    Moved subscription, added icons, refactured markup

commit b43c18e28b5aabb8115fa659a98b4ae8c0a7bf40
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Tue Aug 17 10:50:02 2021 +0200

    unsubscribed

commit 9f48cddc75872fc3f740c53de998cd54666b0b1d
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Mon Aug 16 16:40:08 2021 +0200

    Unsubscription behöver läggas till efter genomgång av hur unsubscribeOnDestroy fungerar

commit b2cef346f18482e72c11c09d0a6e629370d01bd5
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Mon Aug 16 16:37:13 2021 +0200

    Authorization

commit 5fe0b5d5fc725551f9e794cbaa70dc0f077f8717
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Fri Aug 13 15:46:50 2021 +0200

    - Changed first h2 to h3
    - Changed link to routerLink

commit fd1cb3c6249535ce84e61df035cae63352b1b00b
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Fri Aug 13 11:53:36 2021 +0200

    Amends after PR

commit 13e9881e3680bd829736205b3fef57b2228638d5
Author: WP\holno <nikola.holst-nikolic@arbetsformedlingen.se>
Date:   Wed Aug 11 16:19:36 2021 +0200

    Employee-card amends to look more like the sketch.
This commit is contained in:
Erik Tiekstra
2021-08-20 11:01:23 +02:00
parent d4447933f8
commit e6f80901ea
22 changed files with 307 additions and 217 deletions

View File

@@ -1,93 +1,97 @@
<msfa-layout> <msfa-layout>
<section class="employee-card"> <section class="employee-card">
<digi-typography *ngIf="detailedEmployeeData$ | async as detailedEmployeeData; else loadingRef"> <digi-typography *ngIf="employee$ | async as employee; else loadingRef">
<div class="employee-card__editcontainer"> <header class="employee-card__header">
<h1>{{ detailedEmployeeData.fullName }}</h1> <a class="employee-card__edit-button" [routerLink]="['/administration/andra-konto', employee.id]">Redigera</a>
<span class="employee-card__editbutton"> <h1>{{ employee.fullName }}</h1>
<a href="./administration/redigera-konto/{{detailedEmployeeData.id}}">Redigera</a> </header>
</span> <p>Här kan du se och ändra personalkontots behörigheter. Ändra behörighet genom att klicka på redigera.</p>
</div>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus accusantium sit, reprehenderit, esse suscipit
quis similique harum est eum eveniet aspernatur delectus magni asperiores porro aliquam voluptate! Architecto,
perferendis commodi.
</p>
<div class="employee-card__contents"> <div class="employee-card__contents">
<div class="employee-card__column"> <div class="employee-card__column">
<h2>Kontaktuppgifter</h2> <h2>Personuppgifter</h2>
<dl class="employee-card__description-list">
<dl> <dt>Förnamn</dt>
<dt>Namn</dt> <dd *ngIf="employee.firstName; else emptyDD">{{ employee.firstName }}</dd>
<dd *ngIf="detailedEmployeeData.fullName; else emptyDD">{{ detailedEmployeeData.fullName }}</dd> <dt>Efternamn</dt>
<dd *ngIf="employee.lastName; else emptyDD">{{ employee.lastName }}</dd>
<dt>Personnummer</dt> <dt>Personnummer</dt>
<dd *ngIf="detailedEmployeeData.ssn; else emptyDD"> <dd *ngIf="employee.ssn; else emptyDD">
<msfa-hide-text <msfa-hide-text
symbols="********-****" symbols="********-****"
[changingText]="detailedEmployeeData.ssn" [changingText]="employee.ssn"
ariaLabelType="personnummer" ariaLabelType="personnummer"
></msfa-hide-text> ></msfa-hide-text>
</dd> </dd>
<dt>E-postadress</dt>
<dd *ngIf="employee.email; else emptyDD">
<a href="mailto:{{employee.email}}">{{employee.email}}</a>
</dd>
</dl> </dl>
</div> </div>
<div class="employee-card__column"> <div class="employee-card__column">
<h2>Tjänst</h2> <h2>Tjänst</h2>
<ul class="employee-card__list"> <ul class="employee-card__list" *ngIf="employee.tjanster.length; else emptyText">
<ng-container *ngIf="detailedEmployeeData.services.length; else emptyDD"> <li *ngFor="let tjanst of employee.tjanster">{{ tjanst.name }}</li>
<li class="employee-card__column--listitem" *ngFor="let service of detailedEmployeeData.services">
{{ service.name }}
</li>
</ng-container>
</ul> </ul>
</div> </div>
<div class="employee-card__organizations"> <div class="employee-card__organizations">
<h2>Utförande verksamheter och utförande adresser</h2> <h2>Utförande verksamheter och utförande adresser</h2>
<ul class="employee-card__list" *ngIf="detailedEmployeeData.organizations?.length"> <p>TODO: Behöver göras så en utförande adress hamnar under rätt utförande verksamhet.</p>
<li class="employee-card__list" *ngFor="let organization of detailedEmployeeData.organizations"> <h3>Utförande verksamheter</h3>
{{ organization.name }} <ul
<ul> class="employee-card__list employee-card__list--secondary"
<li class="employee-card__listitem--indent"> *ngIf="employee.utforandeVerksamhetIds.length; else emptyText"
{{ organization.address.street }} {{ organization.address.postalCode }} {{ >
organization.address.houseNumber }} {{ organization.address.city }} <li class="employee-card__list" *ngFor="let utforandeVerksamhet of employee.utforandeVerksamhetIds">
</li> {{ utforandeVerksamhet }}
</ul> </li>
</ul>
<h3>Utförande adresser</h3>
<ul
class="employee-card__list employee-card__list--secondary"
*ngIf="employee.utforandeAdressIds.length; else emptyText"
>
<li class="employee-card__list" *ngFor="let utforandeAdress of employee.utforandeAdressIds">
{{ utforandeAdress }}
</li> </li>
</ul> </ul>
</div> </div>
<div class="employee-card__column"> <div class="employee-card__column">
<h2>Behörigheter</h2> <h2>Behörigheter</h2>
<ul class="employee-card__list"> <ul class="employee-card__list">
<ng-container *ngIf="detailedEmployeeData.authorizations.length; else emptyDD"> <li *ngFor="let role of allRoles">
<li *ngFor="let authorization of detailedEmployeeData.authorizations">{{ authorization.name }}</li> <digi-icon-check-circle
</ng-container> *ngIf="employee.roles.includes(role.type); else unauthorized"
class="employee-card__authorization-icon employee-card__authorization-icon--authorized"
></digi-icon-check-circle>
<ng-template #unauthorized>
<digi-icon-x-button
class="employee-card__authorization-icon employee-card__authorization-icon--unauthorized"
></digi-icon-x-button>
</ng-template>
{{role.name}}
</li>
</ul> </ul>
</div> </div>
</div> </div>
<p></p>
</digi-typography> </digi-typography>
<div class="employee-card__footer"> <footer class="employee-card__footer">
<span class="employee-card__secondarybutton"> <msfa-back-link [route]="['/administration/personal']">Tillbaka till deltagarlistan</msfa-back-link>
<a href="./administration/personal">Tillbaka till personallistan</a> </footer>
</span>
<span class="employee-card__primarybutton">
<a href="./administration/skapa-konto">Skapa nytt konto</a>
</span>
</div>
</section> </section>
<ng-template #loadingRef> <ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personalkortet"></digi-ng-skeleton-base> <digi-ng-skeleton-base [afCount]="3" afText="Laddar personalkortet"></digi-ng-skeleton-base>
</ng-template> </ng-template>
<ng-template #emptyDD class="employee-card__list"> <ng-template #emptyDD class="employee-card__list">
<dd> <dd>
<span aria-hidden="true">-</span> <span aria-hidden="true">-</span>
<span class="msfa__a11y-sr-only">Info saknas</span> <span class="msfa__a11y-sr-only">Info saknas</span>
</dd> </dd>
</ng-template> </ng-template>
<ng-template #emptyText>
<p>
<span aria-hidden="true">-</span>
<span class="msfa__a11y-sr-only">Info saknas</span>
</p>
</ng-template>
</msfa-layout> </msfa-layout>

View File

@@ -7,17 +7,7 @@
&__contents { &__contents {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $digi--layout--gutter--xl $digi--layout--gutter--l; gap: $digi--layout--gutter--l $digi--layout--gutter--l;
}
&__editcontainer {
display: flex;
justify-content: space-between;
align-items: center;
}
&__h2 {
margin-top: 0;
} }
&__column { &__column {
@@ -28,65 +18,60 @@
&__organizations { &__organizations {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.25rem; }
&__header,
&__footer {
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: space-between;
} }
&__footer { &__footer {
margin-top: 5rem; margin-top: $digi--layout--gutter--l;
} }
//LISTS &__description-list {
dd {
margin: 0;
}
dt {
font-weight: var(--digi--typography--font-weight--semibold);
}
dd {
margin-bottom: 0.5rem;
}
}
&__list { &__list {
@include msfa__reset-list; @include msfa__reset-list;
}
&__listitem--indent { li {
@include msfa__reset-list; margin-bottom: 0.5rem;
margin-left: 1rem; display: flex;
}
&__description {
margin-left: 0.1rem;
grid-column: 1;
}
&__term {
margin: 0;
grid-column: 1;
font-weight: var(--digi--typography--font-weight--semibold);
}
//BUTTONS
&__primarybutton {
a {
@include msfa_buttontemplate(
$msfa-button--background--primary,
$msfa-button--text--primary,
$msfa-button--hover--primary
);
} }
} }
&__secondarybutton { &__edit-button {
a { @include msfa-button-template(
@include msfa_buttontemplate( $msfa-button--background--secondary,
$msfa-button--background--secondary, $msfa-button--text--secondary,
$msfa-button--text--secondary, $msfa-button--hover--secondary
$msfa-button--hover--secondary );
); width: var(--digi-button--width);
}
} }
&__editbutton { &__authorization-icon {
a { margin-right: var(--digi--layout--gutter--s);
@include msfa_buttontemplate(
$msfa-button--background--secondary, &--authorized {
$msfa-button--text--secondary, color: var(--digi--ui--color--border--success);
$msfa-button--hover--secondary }
);
width: var(--digi-button--width); &--unauthorized {
color: var(--digi--ui--color--border--error);
} }
} }
} }

View File

@@ -1,10 +1,10 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { RoleEnum } from '@msfa-enums/role.enum';
import { Employee } from '@msfa-models/employee.model'; import { Employee } from '@msfa-models/employee.model';
import { Participant } from '@msfa-models/participant.model'; import { Role } from '@msfa-models/role.model';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { EmployeeService } from '@msfa-services/api/employee.service';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@Component({ @Component({
selector: 'msfa-employee-card', selector: 'msfa-employee-card',
@@ -14,35 +14,14 @@ import { map, switchMap } from 'rxjs/operators';
}) })
export class EmployeeCardComponent { export class EmployeeCardComponent {
private _pendingSelectedParticipants$ = new BehaviorSubject<string[]>([]); private _pendingSelectedParticipants$ = new BehaviorSubject<string[]>([]);
private _employeeId$: Observable<string> = this.activatedRoute.params.pipe( employee$: Observable<Employee> = this.employeeService.employee$;
map(({ employeeId }) => employeeId as string) allRoles: Role[] = Object.entries(RoleEnum).map(([key, value]) => ({ type: key as RoleEnum, name: value }));
);
detailedEmployeeData$: Observable<Employee> = this._employeeId$.pipe( constructor(private activatedRoute: ActivatedRoute, private employeeService: EmployeeService) {
switchMap(employeeId => this.employeeService.fetchDetailedEmployeeData$(employeeId)) this.employeeService.setCurrentEmployeeId(this.activatedRoute.snapshot.params.employeeId);
); }
constructor(private activatedRoute: ActivatedRoute, private employeeService: EmployeeService) {}
get pendingSelectedParticipants(): string[] { get pendingSelectedParticipants(): string[] {
return this._pendingSelectedParticipants$.getValue(); return this._pendingSelectedParticipants$.getValue();
} }
handleChangeEmployee(): void {
console.log('change employee: ', this.pendingSelectedParticipants);
}
handleChangeParticipant(id: string, checked: boolean): void {
const currentPendingSelectedParticipants = this.pendingSelectedParticipants;
if (checked) {
this._pendingSelectedParticipants$.next([...this.pendingSelectedParticipants, id]);
} else {
this._pendingSelectedParticipants$.next(currentPendingSelectedParticipants.filter(currentId => currentId !== id));
}
}
handleChangeAllParticipants(participants: Participant[], checked: boolean): void {
this._pendingSelectedParticipants$.next(checked ? participants.map(participant => participant.id) : []);
}
} }

View File

@@ -6,6 +6,7 @@ import { RouterModule } from '@angular/router';
import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module'; import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
import { LayoutModule } from '@msfa-shared/components/layout/layout.module'; import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
import { LocalDatePipeModule } from '@msfa-shared/pipes/local-date/local-date.module'; import { LocalDatePipeModule } from '@msfa-shared/pipes/local-date/local-date.module';
import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
import { EmployeeCardComponent } from './employee-card.component'; import { EmployeeCardComponent } from './employee-card.component';
@NgModule({ @NgModule({
@@ -19,6 +20,7 @@ import { EmployeeCardComponent } from './employee-card.component';
DigiNgLayoutExpansionPanelModule, DigiNgLayoutExpansionPanelModule,
LocalDatePipeModule, LocalDatePipeModule,
HideTextModule, HideTextModule,
BackLinkModule
], ],
}) })
export class EmployeeCardModule {} export class EmployeeCardModule {}

View File

@@ -1,17 +1,17 @@
import { FormSelectBaseItem } from '@af/digi-ng/_form/form-select-base'; import { FormSelectBaseItem } from '@af/digi-ng/_form/form-select-base';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { Authorization } from '@msfa-models/authorization.model'; import { Authorization } from '@msfa-models/authorization.model';
import { Employee } from '@msfa-models/employee.model'; import { Employee } from '@msfa-models/employee.model';
import { Service } from '@msfa-models/service.model'; import { Service } from '@msfa-models/service.model';
import { AuthorizationService } from '@msfa-services/api/authorizations.service'; import { AuthorizationService } from '@msfa-services/api/authorizations.service';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { EmployeeService } from '@msfa-services/api/employee.service';
import { ServiceService } from '@msfa-services/api/service.service'; import { ServiceService } from '@msfa-services/api/service.service';
import { EmailValidator } from '@msfa-utils/validators/email.validator';
import { RequiredValidator } from '@msfa-validators/required.validator'; import { RequiredValidator } from '@msfa-validators/required.validator';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { EditEmployeeFormData } from './edit-employee-form/edit-employee-form.component'; import { EditEmployeeFormData } from './edit-employee-form/edit-employee-form.component';
@Component({ @Component({
@@ -20,8 +20,8 @@ import { EditEmployeeFormData } from './edit-employee-form/edit-employee-form.co
styleUrls: ['./employee-form.component.scss'], styleUrls: ['./employee-form.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class EmployeeFormComponent extends UnsubscribeDirective implements OnInit { export class EmployeeFormComponent {
employee$: Observable<Employee>; employee$: Observable<Employee> = this.employeeService.employee$;
services$: Observable<Service[]> = this.serviceService.services$; services$: Observable<Service[]> = this.serviceService.services$;
authorizations$: Observable<Authorization[]> = this.authorizationService.authorizations$; authorizations$: Observable<Authorization[]> = this.authorizationService.authorizations$;
employeeCurrentServices: Service[] | null | undefined; employeeCurrentServices: Service[] | null | undefined;
@@ -33,7 +33,7 @@ export class EmployeeFormComponent extends UnsubscribeDirective implements OnIni
modalAuthInfo: { name: string } = { name: 'Test Behörighetsnamn' }; modalAuthInfo: { name: string } = { name: 'Test Behörighetsnamn' };
formGroup: FormGroup = this.formBuilder.group({ formGroup: FormGroup = this.formBuilder.group({
email: this.formBuilder.control('', [Validators.required, Validators.email]), email: this.formBuilder.control('', [RequiredValidator('E-postadress'), EmailValidator('E-postadress')]),
services: this.formBuilder.control([], [RequiredValidator('en tjänst')]), services: this.formBuilder.control([], [RequiredValidator('en tjänst')]),
authorizations: new FormGroup({}), authorizations: new FormGroup({}),
}); });
@@ -48,20 +48,7 @@ export class EmployeeFormComponent extends UnsubscribeDirective implements OnIni
private router: Router, private router: Router,
private activatedRoute: ActivatedRoute private activatedRoute: ActivatedRoute
) { ) {
super(); this.employeeService.setCurrentEmployeeId(this.activatedRoute.snapshot.params.employeeId);
super.unsubscribeOnDestroy();
}
ngOnInit(): void {
this.employee$ = this.activatedRoute.params.pipe(
switchMap((params: { employeeId: string }) => {
return this.employeeService.fetchDetailedEmployeeData$(params.employeeId);
})
);
this.services$.subscribe(services => {
/* this.employeeCurrentServices = services; */
});
} }
updateEmployee(editEmployeeFormData: EditEmployeeFormData): void { updateEmployee(editEmployeeFormData: EditEmployeeFormData): void {

View File

@@ -0,0 +1,7 @@
export enum RoleEnum {
OrganizationUser = 'OrganizationUser', // Default role
ReportAndPlanning = 'ReportAndPlanning',
ReceiveDeltagare = 'ReceiveDeltagare',
AuthAdmin = 'AuthAdmin',
ContactPerson = 'ContactPerson',
}

View File

@@ -0,0 +1,4 @@
export interface AvropTjanstResponse {
code: string;
name: string;
}

View File

@@ -1,3 +1,4 @@
import { RoleEnum } from '@msfa-enums/role.enum';
import { PaginationMeta } from '@msfa-models/pagination-meta.model'; import { PaginationMeta } from '@msfa-models/pagination-meta.model';
export interface EmployeeCompactResponse { export interface EmployeeCompactResponse {
@@ -13,10 +14,10 @@ export interface EmployeeResponse {
lastName: string; lastName: string;
email: string; email: string;
personnummer: string; personnummer: string;
roles: string[]; roles: RoleEnum[];
tjansteKoder: string[]; tjansteKoder: string[];
utforandeVerksamhetIds: string[]; utforandeVerksamhetIds: number[];
adressIds: string[]; adressIds: number[];
} }
export interface EmployeesApiResponse { export interface EmployeesApiResponse {

View File

@@ -1,4 +1,6 @@
export interface TjanstResponse { export interface TjanstResponse {
code: string; id: string;
name: string; name: string;
tjanstekod: string;
tjanstId: number;
} }

View File

@@ -0,0 +1,15 @@
import { AvropTjanstResponse } from './api/avrop-tjanst.response.model';
export interface AvropTjanst {
code: string;
name: string;
}
export function mapResponseToAvropTjanst(data: AvropTjanstResponse): AvropTjanst {
const { code, name } = data;
return {
code,
name,
};
}

View File

@@ -1,5 +1,7 @@
import { RoleEnum } from '@msfa-enums/role.enum';
import { EmployeeCompactResponse, EmployeeResponse } from './api/employee.response.model'; import { EmployeeCompactResponse, EmployeeResponse } from './api/employee.response.model';
import { PaginationMeta } from './pagination-meta.model'; import { PaginationMeta } from './pagination-meta.model';
import { Tjanst } from './tjanst.model';
export interface EmployeeCompact { export interface EmployeeCompact {
id: string; id: string;
@@ -12,12 +14,14 @@ export interface Employee {
id: string; id: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
fullName: string;
email: string; email: string;
ssn: string; ssn: string;
roles: string[]; roles: RoleEnum[];
tjanstCodes: string[]; tjanstCodes: string[];
utforandeVerksamhetIds: string[]; tjanster?: Tjanst[];
utforandeAdressIds: string[]; utforandeVerksamhetIds: number[];
utforandeAdressIds: number[];
} }
export interface EmployeesData { export interface EmployeesData {
@@ -29,8 +33,8 @@ export interface EmployeeRequestData {
email: string; email: string;
roles: string[]; roles: string[];
tjansteKoder: string[]; tjansteKoder: string[];
utforandeVerksamhetIds: string[]; utforandeVerksamhetIds: number[];
adressIds: string[]; adressIds: number[];
} }
export function mapEmployeeToRequestData(data: Employee): EmployeeRequestData { export function mapEmployeeToRequestData(data: Employee): EmployeeRequestData {
@@ -71,11 +75,12 @@ export function mapResponseToEmployee(data: EmployeeResponse): Employee {
id: ciamUserId, id: ciamUserId,
firstName, firstName,
lastName, lastName,
fullName: `${firstName} ${lastName}`,
email, email,
ssn: personnummer, ssn: personnummer,
roles, roles: roles || [],
tjanstCodes: tjansteKoder, tjanstCodes: tjansteKoder || [],
utforandeVerksamhetIds, utforandeVerksamhetIds: utforandeVerksamhetIds || [],
utforandeAdressIds: adressIds, utforandeAdressIds: adressIds || [],
}; };
} }

View File

@@ -0,0 +1,6 @@
import { RoleEnum } from '@msfa-enums/role.enum';
export interface Role {
name: string;
type: RoleEnum;
}

View File

@@ -1,15 +1,19 @@
import { TjanstResponse } from './api/tjanst.response.model'; import { TjanstResponse } from './api/tjanst.response.model';
export interface Tjanst { export interface Tjanst {
code: string; id: string;
name: string; name: string;
code: string;
tjanstId: number;
} }
export function mapTjanstResponseToTjanst(data: TjanstResponse): Tjanst { export function mapResponseToTjanst(data: TjanstResponse): Tjanst {
const { code, name } = data; const { id, name, tjanstekod, tjanstId } = data;
return { return {
code, id,
name, name,
code: tjanstekod,
tjanstId,
}; };
} }

View File

@@ -1,14 +1,14 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { environment } from '@msfa-environment'; import { environment } from '@msfa-environment';
import { AvropTjanstResponse } from '@msfa-models/api/avrop-tjanst.response.model';
import { AvropResponse } from '@msfa-models/api/avrop.response.model'; import { AvropResponse } from '@msfa-models/api/avrop.response.model';
import { KommunResponse } from '@msfa-models/api/kommun.response.model'; import { KommunResponse } from '@msfa-models/api/kommun.response.model';
import { TjanstResponse } from '@msfa-models/api/tjanst.response.model';
import { UtforandeVerksamhetResponse } from '@msfa-models/api/utforande-verksamhet.response.model'; import { UtforandeVerksamhetResponse } from '@msfa-models/api/utforande-verksamhet.response.model';
import { mapResponseToAvropTjanst } from '@msfa-models/avrop-tjanst.model';
import { Avrop, mapAvropResponseToAvrop } from '@msfa-models/avrop.model'; import { Avrop, mapAvropResponseToAvrop } from '@msfa-models/avrop.model';
import { mapKommunResponseToKommun } from '@msfa-models/kommun.model'; import { mapKommunResponseToKommun } from '@msfa-models/kommun.model';
import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option'; import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option';
import { mapTjanstResponseToTjanst } from '@msfa-models/tjanst.model';
import { mapUtforandeVerksamhetResponseToUtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model'; import { mapUtforandeVerksamhetResponseToUtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { delay, filter, map } from 'rxjs/operators'; import { delay, filter, map } from 'rxjs/operators';
@@ -56,9 +56,9 @@ export class AvropApiService {
selectedKommuner: MultiselectFilterOption[], selectedKommuner: MultiselectFilterOption[],
selectedUtforandeVerksamheter: MultiselectFilterOption[] selectedUtforandeVerksamheter: MultiselectFilterOption[]
): Observable<MultiselectFilterOption[]> { ): Observable<MultiselectFilterOption[]> {
return this.httpClient.get<{ data: TjanstResponse[] }>(`${this._apiBaseUrl}/tjanster`).pipe( return this.httpClient.get<{ data: AvropTjanstResponse[] }>(`${this._apiBaseUrl}/tjanster`).pipe(
filter(response => !!response?.data), filter(response => !!response?.data),
map(({ data }) => data.map(tjanster => ({ label: mapTjanstResponseToTjanst(tjanster).name }))) map(({ data }) => data.map(tjanster => ({ label: mapResponseToAvropTjanst(tjanster).name })))
); );
} }

View File

@@ -1,10 +1,11 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { ErrorType } from '@msfa-enums/error-type.enum'; import { ErrorType } from '@msfa-enums/error-type.enum';
import { SortOrder } from '@msfa-enums/sort-order.enum'; import { SortOrder } from '@msfa-enums/sort-order.enum';
import { environment } from '@msfa-environment'; import { environment } from '@msfa-environment';
import { EmployeeInviteMockApiResponse } from '@msfa-models/api/employee-invite.response.model';
import { DeleteEmployeeMockApiResponse } from '@msfa-models/api/delete-employee.response.model'; import { DeleteEmployeeMockApiResponse } from '@msfa-models/api/delete-employee.response.model';
import { EmployeeInviteMockApiResponse } from '@msfa-models/api/employee-invite.response.model';
import { import {
EmployeeCompactResponse, EmployeeCompactResponse,
EmployeeResponse, EmployeeResponse,
@@ -18,23 +19,62 @@ import {
mapResponseToEmployee, mapResponseToEmployee,
mapResponseToEmployeeCompact, mapResponseToEmployeeCompact,
} from '@msfa-models/employee.model'; } from '@msfa-models/employee.model';
import { errorToCustomError } from '@msfa-models/error/custom-error';
import { Sort } from '@msfa-models/sort.model'; import { Sort } from '@msfa-models/sort.model';
import { BehaviorSubject, combineLatest, Observable, throwError } from 'rxjs'; import { ErrorService } from '@msfa-services/error.service';
import { catchError, map, switchMap, take } from 'rxjs/operators'; import { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs';
import { catchError, filter, map, switchMap, take } from 'rxjs/operators';
import { TjanstService } from './tjanst.service';
const API_HEADERS = { headers: environment.api.headers }; const API_HEADERS = { headers: environment.api.headers };
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class EmployeeService { export class EmployeeService extends UnsubscribeDirective {
private _apiUrl = `${environment.api.url}/users`; private _apiUrl = `${environment.api.url}/users`;
private _currentEmployeeId$ = new BehaviorSubject<string>(null);
private _limit$ = new BehaviorSubject<number>(20); private _limit$ = new BehaviorSubject<number>(20);
private _page$ = new BehaviorSubject<number>(1); private _page$ = new BehaviorSubject<number>(1);
private _sort$ = new BehaviorSubject<Sort<keyof EmployeeCompactResponse>>({ key: 'name', order: SortOrder.ASC }); private _sort$ = new BehaviorSubject<Sort<keyof EmployeeCompactResponse>>({ key: 'name', order: SortOrder.ASC });
public sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this._sort$.asObservable(); public sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this._sort$.asObservable();
private _searchFilter$ = new BehaviorSubject<string>(''); private _searchFilter$ = new BehaviorSubject<string>('');
private _onlyEmployeesWithoutAuthorization$ = new BehaviorSubject<boolean>(false); private _onlyEmployeesWithoutAuthorization$ = new BehaviorSubject<boolean>(false);
private _employee$ = new BehaviorSubject<Employee>(null);
public employee$: Observable<Employee> = this._employee$.asObservable();
constructor(
private httpClient: HttpClient,
private errorService: ErrorService,
private tjanstService: TjanstService
) {
super();
super.unsubscribeOnDestroy(
this._currentEmployeeId$
.pipe(
filter(currentEmployeeId => !!currentEmployeeId),
switchMap(currentEmployeeId =>
combineLatest([this._fetchEmployee$(currentEmployeeId), this.tjanstService.tjanster$]).pipe(
filter(([employee, allTjanster]) => !!(employee && allTjanster?.length)),
map(([employee, allTjanster]) => {
const tjanster = [];
employee.tjanstCodes.forEach(code => {
const currentTjanst = allTjanster.find(tjanst => tjanst.code === code);
if (currentTjanst) {
tjanster.push(currentTjanst);
}
});
return { ...employee, tjanster };
})
)
)
)
.subscribe(employee => {
this._employee$.next(employee as Employee);
})
);
}
public employeesData$: Observable<EmployeesData> = combineLatest([ public employeesData$: Observable<EmployeesData> = combineLatest([
this._limit$, this._limit$,
@@ -48,6 +88,13 @@ export class EmployeeService {
) )
); );
public setCurrentEmployeeId(currentEmployeeId: string): void {
if (this._currentEmployeeId$.getValue() !== currentEmployeeId) {
this._employee$.next(null);
this._currentEmployeeId$.next(currentEmployeeId);
}
}
private _fetchEmployees$( private _fetchEmployees$(
limit: number, limit: number,
page: number, page: number,
@@ -82,14 +129,18 @@ export class EmployeeService {
); );
} }
public fetchDetailedEmployeeData$(id: string): Observable<Employee> { private _fetchEmployee$(id: string): Observable<Employee | Partial<Employee>> {
return this.httpClient return this.httpClient
.get<{ data: EmployeeResponse }>(`${this._apiUrl}/${id}`, { ...API_HEADERS }) .get<{ data: EmployeeResponse }>(`${this._apiUrl}/${id}`, { ...API_HEADERS })
.pipe(map(result => mapResponseToEmployee(result.data))); .pipe(
map(({ data }) => mapResponseToEmployee(data)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of({});
})
);
} }
constructor(private httpClient: HttpClient) {}
public setSearchFilter(value: string): void { public setSearchFilter(value: string): void {
this._searchFilter$.next(value); this._searchFilter$.next(value);
} }
@@ -107,8 +158,8 @@ export class EmployeeService {
map(response => { map(response => {
return { return {
status: response.status || 200, // mockresponse status: response.status || 200, // mockresponse
message: response.message || 'deleted succeeded' // mockresponse message: response.message || 'deleted succeeded', // mockresponse
} };
}), }),
catchError(error => throwError({ message: error as string, type: ErrorType.API })) catchError(error => throwError({ message: error as string, type: ErrorType.API }))
); );

View File

@@ -0,0 +1,38 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { environment } from '@msfa-environment';
import { TjanstResponse } from '@msfa-models/api/tjanst.response.model';
import { mapResponseToTjanst, Tjanst } from '@msfa-models/tjanst.model';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
const API_HEADERS = { headers: environment.api.headers };
@Injectable({
providedIn: 'root',
})
export class TjanstService extends UnsubscribeDirective {
private _apiUrl = `${environment.api.url}/tjanster`;
private _tjanster$ = new BehaviorSubject<Tjanst[]>(null);
public tjanster$: Observable<Tjanst[]> = this._tjanster$.asObservable();
private _fetchTjanster$: Observable<Tjanst[]> = this.tjanster$.pipe(
filter(tjanster => !tjanster?.length),
switchMap(() =>
this.httpClient
.get<{ data: TjanstResponse[] }>(this._apiUrl, API_HEADERS)
.pipe(map(({ data }) => data.map(tjanst => mapResponseToTjanst(tjanst))))
)
);
constructor(private httpClient: HttpClient) {
super();
super.unsubscribeOnDestroy(
this._fetchTjanster$.subscribe(tjanster => {
this._tjanster$.next(tjanster);
})
);
}
}

View File

@@ -13,7 +13,7 @@ $msfa-button--font-font-size: var(--digi-button--font-size);
//A basic link button //A basic link button
@mixin msfa_buttontemplate($backgroundcolor, $textcolor, $hovercolor) { @mixin msfa-button-template($backgroundcolor, $textcolor, $hovercolor) {
background: $backgroundcolor; background: $backgroundcolor;
padding: $msfa-button--padding; padding: $msfa-button--padding;
margin: $msfa-button--margin; margin: $msfa-button--margin;

View File

@@ -25,4 +25,4 @@ function generateAuthorizations() {
export default { export default {
generate: generateAuthorizations, generate: generateAuthorizations,
}; };

View File

@@ -7,6 +7,7 @@ faker.locale = 'sv';
const TJANSTER = tjanster.generate(); const TJANSTER = tjanster.generate();
const ORGANIZATIONS = organizations.generate(); const ORGANIZATIONS = organizations.generate();
const ROLES = ['ReportAndPlanning', 'ReceiveDeltagare', 'AuthAdmin', 'ContactPerson'];
function generateEmployees(amount = 10) { function generateEmployees(amount = 10) {
const employees = []; const employees = [];
@@ -31,10 +32,12 @@ function generateEmployees(amount = 10) {
min: 1000, min: 1000,
max: 9999, max: 9999,
})}`, })}`,
email: '', email: faker.internet.email(firstName.toLowerCase(), lastName.toLowerCase()),
roles: hasBehorigheter ? ['Admin'] : [], roles: hasBehorigheter
? ['OrganizationUser', ...chooseRandom(ROLES, faker.datatype.number({ min: 1, max: ROLES.length }))]
: ['OrganizationUser'],
tjanst: hasBehorigheter ? currentTjanster.map(tjanst => tjanst.name) : [], tjanst: hasBehorigheter ? currentTjanster.map(tjanst => tjanst.name) : [],
tjansteKoder: hasBehorigheter ? currentTjanster.map(tjanst => tjanst.code) : [], tjansteKoder: hasBehorigheter ? currentTjanster.map(tjanst => tjanst.tjanstekod) : [],
utforandeVerksamhet: hasBehorigheter ? currentOrganizations.map(organization => organization.name) : [], utforandeVerksamhet: hasBehorigheter ? currentOrganizations.map(organization => organization.name) : [],
utforandeVerksamhetIds: hasBehorigheter ? currentOrganizations.map(organization => organization.id) : [], utforandeVerksamhetIds: hasBehorigheter ? currentOrganizations.map(organization => organization.id) : [],
}; };

View File

@@ -1,23 +1,23 @@
import fs from 'fs'; import fs from 'fs';
import { authTokens } from './auth-tokens.js'; import { authTokens } from './auth-tokens.js';
import authorizations from './authorizations.js';
import avrop from './avrop.js'; import avrop from './avrop.js';
import currentUser from './current-user.js'; import currentUser from './current-user.js';
import deltagare from './deltagare.js'; import deltagare from './deltagare.js';
import employees from './employees.js'; import employees from './employees.js';
import languages from './languages.js'; import languages from './languages.js';
import participants from './participants.js'; import participants from './participants.js';
import tjanster from './tjanster.js';
const generatedEmployees = employees.generate(50); const generatedEmployees = employees.generate(50);
const generatedDeltagare = deltagare.generate(50); const generatedDeltagare = deltagare.generate(50);
const generatedAvrop = avrop.generate(10, generatedDeltagare.slice(0, 10)); const generatedAvrop = avrop.generate(10, generatedDeltagare.slice(0, 10));
const auths = authorizations.generate(); const generatedTjanster = tjanster.generate();
const tjanster = []; const avropTjanster = [];
const organizations = []; const organizations = [];
const kommuner = []; const kommuner = [];
generatedAvrop.forEach(({ tjanstekod, tjansteNamn, utforandeVerksamhetId, utforandeverksamhet, kommunKod, kommun }) => { generatedAvrop.forEach(({ tjanstekod, tjansteNamn, utforandeVerksamhetId, utforandeverksamhet, kommunKod, kommun }) => {
const tjanstExists = tjanster.find(tjanst => tjanst.code === tjanstekod); const tjanstExists = avropTjanster.find(tjanst => tjanst.code === tjanstekod);
const organizationExists = organizations.find(organization => organization.id === utforandeVerksamhetId); const organizationExists = organizations.find(organization => organization.id === utforandeVerksamhetId);
const kommunExists = kommuner.find(kommun => kommun.kommunCode === kommunKod); const kommunExists = kommuner.find(kommun => kommun.kommunCode === kommunKod);
@@ -29,7 +29,7 @@ generatedAvrop.forEach(({ tjanstekod, tjansteNamn, utforandeVerksamhetId, utfora
tjanstExists.related_kommunCodes.push(kommunKod); tjanstExists.related_kommunCodes.push(kommunKod);
} }
} else { } else {
tjanster.push({ avropTjanster.push({
code: tjanstekod, code: tjanstekod,
name: tjansteNamn, name: tjansteNamn,
related_utforandeverksamhetIds: [utforandeVerksamhetId], related_utforandeverksamhetIds: [utforandeVerksamhetId],
@@ -72,7 +72,8 @@ const apiData = {
languages: languages.generate(), languages: languages.generate(),
employees: generatedEmployees, employees: generatedEmployees,
avrop: generatedAvrop, avrop: generatedAvrop,
tjanster, avropTjanster,
tjanster: generatedTjanster,
organizations, organizations,
kommuner, kommuner,
deltagare: generatedDeltagare, deltagare: generatedDeltagare,

View File

@@ -5,24 +5,20 @@ faker.locale = 'sv';
function generateTjanster() { function generateTjanster() {
const tjanster = [ const tjanster = [
{ {
code: faker.datatype.uuid(), id: 'A012',
name: 'Kundval Rusta och matcha', name: 'Kundval Rusta och matcha',
tjanstekod: 'A012',
tjanstId: 25,
count: 8, // Behövs för avrop-tjanst
label: 'Kundval Rusta och matcha', // Behövs för avrop-tjanst
}, },
// { // {
// code: faker.datatype.uuid(), // id: 'KVL',
// name: 'Karriärvägledning', // name: 'Karriärvägledning',
// }, // tjanstekod: 'KVL',
// { // tjanstId: 33,
// code: faker.datatype.uuid(), // count: 8, // Behövs för avrop-tjanst
// name: 'STOM', // label: 'Karriärvägledning', // Behövs för avrop-tjanst
// },
// {
// code: faker.datatype.uuid(),
// name: 'YSM',
// },
// {
// code: faker.datatype.uuid(),
// name: 'AUB',
// }, // },
]; ];

View File

@@ -14,7 +14,7 @@ server.use(
'/users/:id': '/employees?ciamUserId=:id', '/users/:id': '/employees?ciamUserId=:id',
'/users*': '/employees$1', '/users*': '/employees$1',
'/employees*search=*': '/employees$1fullName_like=$2', '/employees*search=*': '/employees$1fullName_like=$2',
'/employees*onlyEmployeesWithoutAuthorization=*': '/employees$1roles.length_lte=0', '/employees*onlyEmployeesWithoutAuthorization=*': '/employees$1roles.length_lte=1',
'/employees/invite': '/invites', '/employees/invite': '/invites',
'/employees*': '/employees$1', '/employees*': '/employees$1',
'/services*': '/tjanster$1', '/services*': '/tjanster$1',
@@ -22,7 +22,7 @@ server.use(
'/participant/:id': '/participants/:id?_embed=employees', '/participant/:id': '/participants/:id?_embed=employees',
'/auth/userinfo': '/currentUser', '/auth/userinfo': '/currentUser',
'/auth/organizations': '/currentUser', '/auth/organizations': '/currentUser',
'/avrop/tjanster*': '/tjanster$1', '/avrop/tjanster*': '/avropTjanster$1',
'/avrop/utforandeverksamheter*': '/organizations$1', '/avrop/utforandeverksamheter*': '/organizations$1',
'/avrop/kommuner*': '/kommuner$1', '/avrop/kommuner*': '/kommuner$1',
'/deltagare?*': '/avrop?$1', '/deltagare?*': '/avrop?$1',