Merge branch 'next' into develop

This commit is contained in:
Daniel Appelgren
2021-09-28 13:56:06 +02:00
24 changed files with 213 additions and 124 deletions
@@ -1,7 +1,9 @@
@import 'variables/gutters';
@import 'variables/z-index';
.employees-list {
position: relative;
z-index: $msfa__z-index-default;
// &__column-head {
// // padding: 0;
// }
@@ -19,7 +19,7 @@
<h2>Personallista</h2>
<form class="employees__search-wrapper" (ngSubmit)="setSearchFilter()">
<form class="employees__search-wrapper" (ngSubmit)="setSearchString()">
<digi-form-input-search
af-label="Sök på personalens namn"
(afOnInput)="setSearchValue($event)"
@@ -26,8 +26,8 @@ export class EmployeesComponent {
return this._searchValue$.getValue();
}
setSearchFilter(): void {
this.employeeService.setSearchFilter(this.searchValue);
setSearchString(): void {
this.employeeService.setSearchString(this.searchValue);
}
setSearchValue($event: CustomEvent<{ target: { value: string } }>): void {
@@ -5,11 +5,22 @@
<section class="avrop" *ngIf="currentStep$ | async as currentStep">
<header class="avrop__header">
<h1>Nya deltagare</h1>
<p>
Här ser du alla nya deltagare. Deltagarna ska tilldelas en handledare innan tjänstens start. Kryssa för de
deltagare du vill tilldela en handledare. Du kan välja en, eller flera personer samtidigt, genom att
kryssa i boxarna nedan.
</p>
<ng-container [ngSwitch]="currentStep">
<p *ngSwitchCase="1">
Här ser du alla nya deltagare. Deltagarna ska tilldelas en handledare innan tjänstens start. Kryssa för
de deltagare du vill tilldela en handledare. Du kan välja en, eller flera personer samtidigt, genom att
kryssa i boxarna nedan.
</p>
<p *ngSwitchCase="2">
Välj vilken handledare du vill tilldela till deltagarna. Gå vidare genom att klicka på Nästa. I nästa
steg kommer du att kunna granska tilldelningen innan du bekräftar. Återgå till föregående steg genom att
klicka på tillbaka.
</p>
<p *ngSwitchCase="3">
Bekräfta valet av tilldelad handledare genom att klicka på bekräfta tilldelning. Återgå till föregående
steg genom att klicka på tillbaka.
</p>
</ng-container>
<digi-notification-alert
*ngIf="currentStep === 4 && selectedHandledare$ | async as selectedHandledare"
@@ -26,8 +37,8 @@
<ng-container [ngSwitch]="currentStep">
<ng-container *ngSwitchCase="1">Välj deltagare att tilldela</ng-container>
<ng-container *ngSwitchCase="2">Tilldela handledare</ng-container>
<ng-container *ngSwitchCase="3">Förehandsgranska och tilldela</ng-container>
<ng-container *ngSwitchCase="4">Tilldelade delgare</ng-container>
<ng-container *ngSwitchCase="3">Förhandsgranska och tilldela</ng-container>
<ng-container *ngSwitchCase="4">Tilldelade deltagare</ng-container>
</ng-container>
</h2>
@@ -100,7 +111,7 @@
<digi-button af-variation="secondary" af-size="m" (afOnClick)="unlockSelectedAvrop()"
>Tillbaka
</digi-button>
<digi-button af-size="m" (afOnClick)="confirmHandledare()">Tilldela</digi-button>
<digi-button af-size="m" (afOnClick)="confirmHandledare()">Nästa</digi-button>
</ng-container>
<ng-container *ngSwitchCase="3">
<digi-button af-variation="secondary" af-size="m" (afOnClick)="unconfirmHandledare()"
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { AvropCompact, AvropCompactData } from '@msfa-models/avrop.model';
import { Handledare } from '@msfa-models/handledare.model';
import { AvropService } from '@msfa-services/avrop.service';
@@ -10,7 +10,7 @@ import { Observable } from 'rxjs';
styleUrls: ['./avrop.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvropComponent {
export class AvropComponent implements OnDestroy {
readonly totalAmountOfSteps = 3;
currentStep$: Observable<number> = this.avropService.currentStep$;
error$: Observable<string> = this.avropService.error$;
@@ -26,6 +26,10 @@ export class AvropComponent {
constructor(private avropService: AvropService) {}
ngOnDestroy(): void {
this.returnToStep1();
}
updateSelectedAvrop(deltagareList: AvropCompact[], currentStep: number): void {
if (currentStep !== 1 && !deltagareList.length) {
this.avropService.goToStep1();
@@ -1,13 +1,5 @@
<digi-ng-skeleton-base *ngIf="avropLoading" [afCount]="3" afText="Laddar nya deltagare"></digi-ng-skeleton-base>
<div *ngIf="availableAvrop?.length" class="avrop-list">
<div class="avrop-list__select-all">
<digi-form-checkbox
*ngIf="!isLocked"
af-label="Välj alla deltagare"
[afChecked]="isAllSelected"
(change)="toggleAllAvrop($event.target.checked)"
></digi-form-checkbox>
</div>
<ul class="avrop-list__list">
<li *ngFor="let avrop of avropRows">
<msfa-avrop-row
@@ -27,6 +19,7 @@
*ngIf="totalPage > 1 && !isLocked"
class="avrop-list__pagination"
[afTotalPages]="totalPage"
[afInitActivePage]="currentPage"
[afCurrentResultStart]="currentResultStart"
[afCurrentResultEnd]="currentResultEnd"
[afTotalResults]="count"
@@ -1,10 +1,6 @@
@import 'mixins/list';
.avrop-list {
&__select-all {
padding: var(--digi--layout--gutter);
}
&__list {
@include msfa__reset-list;
display: flex;
@@ -24,10 +24,6 @@ export class AvropListComponent {
get avropRows(): AvropCompact[] {
return this.isLocked ? this.selectedAvrop : this.availableAvrop;
}
get isAllSelected(): boolean {
return this.selectedAvrop?.length === this.availableAvrop?.length;
}
get currentPage(): number {
return this.paginationMeta.page;
}
@@ -53,14 +49,6 @@ export class AvropListComponent {
return !!this.selectedAvrop?.find(selectedAvrop => selectedAvrop.id === avrop.id);
}
toggleAllAvrop(selected: boolean): void {
if (selected && this.selectedAvrop?.length !== this.availableAvrop?.length) {
this.selectionChanged.emit(this.availableAvrop);
} else if (!selected && this.selectedAvrop.length) {
this.selectionChanged.emit([]);
}
}
toggleSelectedAvrop(avrop: Avrop, selected: boolean): void {
const avropIsSelected = !!this.selectedAvrop?.find(selectedAvrop => selectedAvrop.id === avrop.id);
@@ -4,38 +4,40 @@
</p>
<digi-table af-size="s" *ngIf="deltagareHandelser$ | async; let deltagareHandelser">
<ng-container
*ngIf="deltagareHandelser.length > 0; else noEvents"
>
<table>
<caption class="msfa__a11y-sr-only">Lista med alla händelser för {{deltagare?.fullName}}</caption>
<thead>
<tr>
<th scope="col" class="deltagare-list-handelser__heading-row">Inkom</th>
<th scope="col" class="deltagare-list-handelser__heading-row">Händelse</th>
<th scope="col" class="deltagare-list-handelser__heading-row">Datum för händelse</th>
<th scope="col" class="deltagare-list-handelser__heading-row">Datum förklaring</th>
</tr>
</thead>
<tbody>
<ng-container
*ngIf="deltagareHandelser.length > 0; else noEvents"
>
<table>
<caption class="msfa__a11y-sr-only">Lista med alla händelser för {{deltagare?.fullName}}</caption>
<thead>
<tr>
<th scope="col" class="deltagare-list-handelser__heading-row">Inkom</th>
<th scope="col" class="deltagare-list-handelser__heading-row">Händelse</th>
<th scope="col" class="deltagare-list-handelser__heading-row">Effekt</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let handelse of deltagareHandelser;">
<td class="deltagare-list-handelser__table-cell">{{handelse.receivedDate | date}}</td>
<td class="deltagare-list-handelser__table-cell" *ngIf="!handelse.isAvbrott; else isAvbrottCell">
<tr *ngFor="let handelse of deltagareHandelser;">
<td class="deltagare-list-handelser__table-cell">{{handelse.receivedDate | date}}</td>
<td class="deltagare-list-handelser__table-cell" *ngIf="!handelse.isAvbrott; else isAvbrottCell">
{{handelse.description}}</td>
<td class="deltagare-list-handelser__table-cell">
<ng-container *ngIf="handelse.effectDescription && handelse.effectDate">
{{handelse.effectDescription}}: {{handelse.effectDate | date }}
</ng-container>
</td>
<ng-template #isAvbrottCell>
<td class="deltagare-list-handelser__avbrott-cell">
<msfa-icon [icon]="iconType.WARNING" size="l"></msfa-icon>
{{handelse.description}}</td>
<td class="deltagare-list-handelser__table-cell">{{handelse.tidpunkt | date }}</td>
<td class="deltagare-list-handelser__table-cell">{{handelse.tidpunktDescription}}</td>
<ng-template #isAvbrottCell>
<td class="deltagare-list-handelser__avbrott-cell">
<msfa-icon [icon]="iconType.WARNING" size="l"></msfa-icon>
{{handelse.description}}</td>
</ng-template>
</tr>
</ng-template>
</tr>
</tbody>
</table>
</ng-container>
</tbody>
</table>
</ng-container>
</digi-table>
</div>
@@ -17,7 +17,6 @@
(afOnChange)="setOnlyMyDeltagare($event.detail.target.checked)"
></digi-form-checkbox>
</div>
{{showUnauthorizedError$ | async}}
<ng-container *ngIf="(deltagareLoading$ | async) === false; else loadingRef">
<msfa-deltagare-list
*ngIf="allDeltagareData.data.length; else noDeltagare"
@@ -31,7 +31,6 @@
grid-area: content;
max-width: $digi--layout--breakpoint--l;
padding: $digi--layout--gutter--l $digi--layout--gutter--l $digi--layout--gutter--xxl;
z-index: $msfa__z-index-default;
}
&__breadcrumbs {
@@ -13,7 +13,7 @@ export interface AvropCompact {
sprakstod: string; // sprakstod
utforandeAdress: string; // adress
trackCode: string; // sparkod
trackName: string; // sparNamn
trackName: TrackName; // sparNamn
}
export interface Avrop extends AvropCompact {
@@ -60,11 +60,11 @@ export function mapAvropResponseToAvrop(data: AvropResponse): Avrop {
sprakstod: sprakstod,
utforandeAdress: adress,
trackCode: sparkod,
trackName: TrackName[sparkod] || TrackName.UNKNOWN,
trackName: (TrackName[sparkod] || TrackName.UNKNOWN) as TrackName,
genomforandeReferens,
participationFrequency: deltagandeGrad,
utforandeVerksamhet: utforandeverksamhet,
handledareCiamUserId,
handledareCiamUserId: handledareCiamUserId,
handledare,
};
}
@@ -15,13 +15,18 @@ export interface ContactInformation {
export function mapResponseToContactInformation(data: ContactInformationResponse): ContactInformation {
const { fornamn, efternamn, personnummer, epost, telekomadresser, adresser } = data;
return {
firstName: fornamn || '',
lastName: efternamn || '',
fullName: fornamn && efternamn ? `${fornamn} ${efternamn}` : '',
ssn: personnummer ? mapStringToSsn(personnummer) : '',
email: epost || '',
phoneNumbers: telekomadresser ? telekomadresser.map(phoneNumber => mapResponseToPhoneNumber(phoneNumber)) : [],
phoneNumbers: telekomadresser
? telekomadresser
.filter(phoneNumber => phoneNumber.landskod && phoneNumber.nummer_utan_inledande_nolla)
.map(phoneNumber => mapResponseToPhoneNumber(phoneNumber))
: [],
addresses: adresser ? adresser.map(address => mapResponseToAddress(address)) : null,
};
}
@@ -1,4 +1,27 @@
const GENOMFORANDEHANDELSE_EFFECT_MAP = {
Inrapporteringsdatum_GP: 'Inskickad datum',
Godkannandedatum_GP: 'Godkänd datum',
Avvisatdatum_GP: 'Ej godkänd datum',
Inrapporteringsdatum_PR: 'Inrapporterad datum',
Godkannandedatum_PR: 'Godkänd datum',
Avvisatdatum_PR: 'Ej godkänd datum',
Inrapporteringsdatum_SR: 'Inskickad datum',
Godkannandedatum_SR: 'Godkänd datum',
Avvisatdatum_SR: 'Ej godkänd datum',
Handlaggning_startad: 'Handläggning startad',
Andringsbeslutdatum: 'Ändringsbeslut datum',
Slut_avropsperiod: 'Nytt slutdatum',
};
export interface DeltagareHandelse {
description: string;
receivedDate: Date;
isAvbrott: boolean;
effectDate: Date;
effectDescription: string;
}
interface DeltagareHandelseApiResponse {
description: string;
receivedDate: Date;
isAvbrott: boolean;
@@ -6,6 +29,18 @@ export interface DeltagareHandelse {
tidpunktDescription: string;
}
export interface DeltagareHandelseApiResponse {
data: DeltagareHandelse[];
export interface DeltagareHandelserApiResponse {
data: DeltagareHandelseApiResponse[];
}
export function mapDeltagareHandelseApiResponse(
deltagareHandelseApiResponse: DeltagareHandelseApiResponse
): DeltagareHandelse {
const { description, receivedDate, isAvbrott } = deltagareHandelseApiResponse;
const effectDate = deltagareHandelseApiResponse.tidpunkt;
const effectDescription =
GENOMFORANDEHANDELSE_EFFECT_MAP[deltagareHandelseApiResponse.tidpunktDescription] ??
deltagareHandelseApiResponse.tidpunktDescription ??
'';
return { description, receivedDate, isAvbrott, effectDate, effectDescription };
}
@@ -1,7 +1,11 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@msfa-environment';
import { DeltagareHandelse, DeltagareHandelseApiResponse } from '@msfa-models/deltagare-handelse.model';
import {
DeltagareHandelse,
DeltagareHandelserApiResponse,
mapDeltagareHandelseApiResponse,
} from '@msfa-models/deltagare-handelse.model';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -18,11 +22,11 @@ export class DeltagareHandelserApiService {
}
return this.httpClient
.get<DeltagareHandelseApiResponse>(`${this._apiBaseUrl}/deltagare/${genomforandeReferens}/handelser`)
.get<DeltagareHandelserApiResponse>(`${this._apiBaseUrl}/deltagare/${genomforandeReferens}/handelser`)
.pipe(
map(({ data }) => {
if (data) {
return data;
return data.map(genomforandeHandelse => mapDeltagareHandelseApiResponse(genomforandeHandelse));
}
})
);
@@ -23,19 +23,35 @@ import { ErrorService } from '@msfa-services/error.service';
import { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs';
import { catchError, distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
interface EmployeeParams {
page: number;
limit: number;
sort: keyof EmployeeCompactResponse;
order: SortOrder;
search: string;
onlyEmployeesWithoutAuthorization: boolean;
}
@Injectable({
providedIn: 'root',
})
export class EmployeeService extends UnsubscribeDirective {
private _apiBaseUrl = `${environment.api.url}/users`;
private _currentEmployeeId$ = new BehaviorSubject<string>(null);
private _limit$ = new BehaviorSubject<number>(10);
private _page$ = new BehaviorSubject<number>(1);
private _sort$ = new BehaviorSubject<Sort<keyof EmployeeCompactResponse>>({ key: 'name', order: SortOrder.ASC });
public sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this._sort$.asObservable();
private _searchFilter$ = new BehaviorSubject<string>('');
private _onlyEmployeesWithoutAuthorization$ = new BehaviorSubject<boolean>(false);
public onlyEmployeesWithoutAuthorization$: Observable<boolean> = this._onlyEmployeesWithoutAuthorization$.asObservable();
private _params$ = new BehaviorSubject<EmployeeParams>({
page: 1,
limit: 10,
sort: 'name',
order: SortOrder.ASC,
search: '',
onlyEmployeesWithoutAuthorization: false,
});
public sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this._params$.pipe(
map(({ sort, order }) => ({ key: sort, order }))
);
public onlyEmployeesWithoutAuthorization$: Observable<boolean> = this._params$.pipe(
map(({ onlyEmployeesWithoutAuthorization }) => onlyEmployeesWithoutAuthorization)
);
private _employee$ = new BehaviorSubject<Employee>(null);
public employee$: Observable<Employee> = this._employee$.asObservable();
private _lastUpdatedEmployeeId$ = new BehaviorSubject<string>(null);
@@ -67,17 +83,8 @@ export class EmployeeService extends UnsubscribeDirective {
);
}
public employeesData$: Observable<EmployeesData> = combineLatest([
this._limit$,
this._page$,
this._sort$,
this._searchFilter$,
this._onlyEmployeesWithoutAuthorization$,
this._lastDeletedEmployee$,
]).pipe(
switchMap(([limit, page, sort, searchFilter, onlyEmployeesWithoutAuthorization]) =>
this._fetchEmployees$(limit, page, sort, searchFilter, onlyEmployeesWithoutAuthorization)
)
public employeesData$: Observable<EmployeesData> = combineLatest([this._params$, this._lastDeletedEmployee$]).pipe(
switchMap(([params]) => this._fetchEmployees$(params))
);
public setCurrentEmployeeId(currentEmployeeId: string): void {
@@ -91,26 +98,21 @@ export class EmployeeService extends UnsubscribeDirective {
this._lastUpdatedEmployeeId$.next(null);
}
private _fetchEmployees$(
limit: number,
page: number,
sort: Sort<keyof EmployeeCompactResponse>,
searchFilter: string,
onlyEmployeesWithoutAuthorization?: boolean
): Observable<EmployeesData> {
private _fetchEmployees$(employeeParams: EmployeeParams): Observable<EmployeesData> {
const { sort, order, limit, page, search, onlyEmployeesWithoutAuthorization } = employeeParams;
const params: Params = {
sort: sort.key as string,
order: sort.order as string,
sort,
order,
limit: limit.toString(),
page: page.toString(),
};
if (searchFilter) {
params.search = searchFilter;
if (search) {
params.search = search;
}
if (onlyEmployeesWithoutAuthorization) {
params.onlyEmployeesWithoutAuthorization = onlyEmployeesWithoutAuthorization?.toString();
params.onlyEmployeesWithoutAuthorization = onlyEmployeesWithoutAuthorization.toString();
}
this._employeesLoading$.next(true);
@@ -134,12 +136,20 @@ export class EmployeeService extends UnsubscribeDirective {
);
}
public setSearchFilter(value: string): void {
this._searchFilter$.next(value);
public setSearchString(value: string): void {
this._params$.next({
...this._params$.getValue(),
search: value,
page: 1,
});
}
public setOnlyEmployeesWithoutAuthorization(value: boolean): void {
this._onlyEmployeesWithoutAuthorization$.next(value);
this._params$.next({
...this._params$.getValue(),
onlyEmployeesWithoutAuthorization: value,
page: 1,
});
}
public setEmployeeToDelete(employee: Employee): void {
@@ -156,16 +166,17 @@ export class EmployeeService extends UnsubscribeDirective {
);
}
public setSort(newSortKey: keyof EmployeeCompactResponse): void {
const currentSort = this._sort$.getValue();
const order =
currentSort.key === newSortKey && currentSort.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
public setSort(sort: keyof EmployeeCompactResponse): void {
const currentParams = this._params$.getValue();
const currentSort = currentParams.sort;
const currentOrder = currentParams.order;
const order = currentSort === sort && currentOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
this._sort$.next({ key: newSortKey, order });
this._params$.next({ ...this._params$.getValue(), sort, order });
}
public setPage(page: number): void {
this._page$.next(page);
this._params$.next({ ...this._params$.getValue(), page });
}
public postEmployeeInvitation(emails: string[]): Observable<EmployeeInviteResponse | null> {
@@ -95,8 +95,11 @@ export class AvropService {
this._avropIsLocked$,
this._avropIsSubmitted$,
]).pipe(
map(([confirmedHandledare, avropIsLocked, avropIsSubmitted]) => {
return AvropService.calculateStep(confirmedHandledare, avropIsLocked, avropIsSubmitted);
map(([confirmedHandledare, avropIsLocked, avropIsSubmitted]) =>
AvropService.calculateStep(confirmedHandledare, avropIsLocked, avropIsSubmitted)
),
tap(() => {
window.scrollTo(0, 0);
})
);