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

View File

@@ -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;
// }

View File

@@ -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)"

View File

@@ -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 {

View File

@@ -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()"

View File

@@ -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();

View File

@@ -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"

View File

@@ -1,10 +1,6 @@
@import 'mixins/list';
.avrop-list {
&__select-all {
padding: var(--digi--layout--gutter);
}
&__list {
@include msfa__reset-list;
display: flex;

View File

@@ -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);

View File

@@ -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>

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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 };
}

View File

@@ -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));
}
})
);

View File

@@ -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> {

View File

@@ -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);
})
);