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

@@ -6,6 +6,7 @@
- **authorization:** Whenever the API throws a "403 Forbidden" error we now show an unauthorized message. [TV-695](https://jira.arbetsformedlingen.se/browse/TV-695) - **authorization:** Whenever the API throws a "403 Forbidden" error we now show an unauthorized message. [TV-695](https://jira.arbetsformedlingen.se/browse/TV-695)
- **avrop:** Now showing spårnamn instead of spårkod inside avrop. [TV-691](https://jira.arbetsformedlingen.se/browse/TV-691) - **avrop:** Now showing spårnamn instead of spårkod inside avrop. [TV-691](https://jira.arbetsformedlingen.se/browse/TV-691)
- **avrop:** Fixed error-state on handledare select. [TV-699](https://jira.arbetsformedlingen.se/browse/TV-699) - **avrop:** Fixed error-state on handledare select. [TV-699](https://jira.arbetsformedlingen.se/browse/TV-699)
- **avrop:** Several bugfixes related to avrop. [TV-701](https://jira.arbetsformedlingen.se/browse/TV-701)
## [2.0.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv1.5.0&sourceBranch=refs%2Ftags%2Fv2.0.0) (2021-09-24) ## [2.0.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv1.5.0&sourceBranch=refs%2Ftags%2Fv2.0.0) (2021-09-24)

View File

@@ -1,7 +1,9 @@
@import 'variables/gutters'; @import 'variables/gutters';
@import 'variables/z-index';
.employees-list { .employees-list {
position: relative; position: relative;
z-index: $msfa__z-index-default;
// &__column-head { // &__column-head {
// // padding: 0; // // padding: 0;
// } // }

View File

@@ -19,7 +19,7 @@
<h2>Personallista</h2> <h2>Personallista</h2>
<form class="employees__search-wrapper" (ngSubmit)="setSearchFilter()"> <form class="employees__search-wrapper" (ngSubmit)="setSearchString()">
<digi-form-input-search <digi-form-input-search
af-label="Sök på personalens namn" af-label="Sök på personalens namn"
(afOnInput)="setSearchValue($event)" (afOnInput)="setSearchValue($event)"

View File

@@ -26,8 +26,8 @@ export class EmployeesComponent {
return this._searchValue$.getValue(); return this._searchValue$.getValue();
} }
setSearchFilter(): void { setSearchString(): void {
this.employeeService.setSearchFilter(this.searchValue); this.employeeService.setSearchString(this.searchValue);
} }
setSearchValue($event: CustomEvent<{ target: { value: string } }>): void { setSearchValue($event: CustomEvent<{ target: { value: string } }>): void {

View File

@@ -5,11 +5,22 @@
<section class="avrop" *ngIf="currentStep$ | async as currentStep"> <section class="avrop" *ngIf="currentStep$ | async as currentStep">
<header class="avrop__header"> <header class="avrop__header">
<h1>Nya deltagare</h1> <h1>Nya deltagare</h1>
<p> <ng-container [ngSwitch]="currentStep">
Här ser du alla nya deltagare. Deltagarna ska tilldelas en handledare innan tjänstens start. Kryssa för de <p *ngSwitchCase="1">
deltagare du vill tilldela en handledare. Du kan välja en, eller flera personer samtidigt, genom att Här ser du alla nya deltagare. Deltagarna ska tilldelas en handledare innan tjänstens start. Kryssa för
kryssa i boxarna nedan. de deltagare du vill tilldela en handledare. Du kan välja en, eller flera personer samtidigt, genom att
</p> 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 <digi-notification-alert
*ngIf="currentStep === 4 && selectedHandledare$ | async as selectedHandledare" *ngIf="currentStep === 4 && selectedHandledare$ | async as selectedHandledare"
@@ -26,8 +37,8 @@
<ng-container [ngSwitch]="currentStep"> <ng-container [ngSwitch]="currentStep">
<ng-container *ngSwitchCase="1">Välj deltagare att tilldela</ng-container> <ng-container *ngSwitchCase="1">Välj deltagare att tilldela</ng-container>
<ng-container *ngSwitchCase="2">Tilldela handledare</ng-container> <ng-container *ngSwitchCase="2">Tilldela handledare</ng-container>
<ng-container *ngSwitchCase="3">Förehandsgranska och tilldela</ng-container> <ng-container *ngSwitchCase="3">Förhandsgranska och tilldela</ng-container>
<ng-container *ngSwitchCase="4">Tilldelade delgare</ng-container> <ng-container *ngSwitchCase="4">Tilldelade deltagare</ng-container>
</ng-container> </ng-container>
</h2> </h2>
@@ -100,7 +111,7 @@
<digi-button af-variation="secondary" af-size="m" (afOnClick)="unlockSelectedAvrop()" <digi-button af-variation="secondary" af-size="m" (afOnClick)="unlockSelectedAvrop()"
>Tillbaka >Tillbaka
</digi-button> </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>
<ng-container *ngSwitchCase="3"> <ng-container *ngSwitchCase="3">
<digi-button af-variation="secondary" af-size="m" (afOnClick)="unconfirmHandledare()" <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 { AvropCompact, AvropCompactData } from '@msfa-models/avrop.model';
import { Handledare } from '@msfa-models/handledare.model'; import { Handledare } from '@msfa-models/handledare.model';
import { AvropService } from '@msfa-services/avrop.service'; import { AvropService } from '@msfa-services/avrop.service';
@@ -10,7 +10,7 @@ import { Observable } from 'rxjs';
styleUrls: ['./avrop.component.scss'], styleUrls: ['./avrop.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AvropComponent { export class AvropComponent implements OnDestroy {
readonly totalAmountOfSteps = 3; readonly totalAmountOfSteps = 3;
currentStep$: Observable<number> = this.avropService.currentStep$; currentStep$: Observable<number> = this.avropService.currentStep$;
error$: Observable<string> = this.avropService.error$; error$: Observable<string> = this.avropService.error$;
@@ -26,6 +26,10 @@ export class AvropComponent {
constructor(private avropService: AvropService) {} constructor(private avropService: AvropService) {}
ngOnDestroy(): void {
this.returnToStep1();
}
updateSelectedAvrop(deltagareList: AvropCompact[], currentStep: number): void { updateSelectedAvrop(deltagareList: AvropCompact[], currentStep: number): void {
if (currentStep !== 1 && !deltagareList.length) { if (currentStep !== 1 && !deltagareList.length) {
this.avropService.goToStep1(); 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> <digi-ng-skeleton-base *ngIf="avropLoading" [afCount]="3" afText="Laddar nya deltagare"></digi-ng-skeleton-base>
<div *ngIf="availableAvrop?.length" class="avrop-list"> <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"> <ul class="avrop-list__list">
<li *ngFor="let avrop of avropRows"> <li *ngFor="let avrop of avropRows">
<msfa-avrop-row <msfa-avrop-row
@@ -27,6 +19,7 @@
*ngIf="totalPage > 1 && !isLocked" *ngIf="totalPage > 1 && !isLocked"
class="avrop-list__pagination" class="avrop-list__pagination"
[afTotalPages]="totalPage" [afTotalPages]="totalPage"
[afInitActivePage]="currentPage"
[afCurrentResultStart]="currentResultStart" [afCurrentResultStart]="currentResultStart"
[afCurrentResultEnd]="currentResultEnd" [afCurrentResultEnd]="currentResultEnd"
[afTotalResults]="count" [afTotalResults]="count"

View File

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

View File

@@ -24,10 +24,6 @@ export class AvropListComponent {
get avropRows(): AvropCompact[] { get avropRows(): AvropCompact[] {
return this.isLocked ? this.selectedAvrop : this.availableAvrop; return this.isLocked ? this.selectedAvrop : this.availableAvrop;
} }
get isAllSelected(): boolean {
return this.selectedAvrop?.length === this.availableAvrop?.length;
}
get currentPage(): number { get currentPage(): number {
return this.paginationMeta.page; return this.paginationMeta.page;
} }
@@ -53,14 +49,6 @@ export class AvropListComponent {
return !!this.selectedAvrop?.find(selectedAvrop => selectedAvrop.id === avrop.id); 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 { toggleSelectedAvrop(avrop: Avrop, selected: boolean): void {
const avropIsSelected = !!this.selectedAvrop?.find(selectedAvrop => selectedAvrop.id === avrop.id); const avropIsSelected = !!this.selectedAvrop?.find(selectedAvrop => selectedAvrop.id === avrop.id);

View File

@@ -4,38 +4,40 @@
</p> </p>
<digi-table af-size="s" *ngIf="deltagareHandelser$ | async; let deltagareHandelser"> <digi-table af-size="s" *ngIf="deltagareHandelser$ | async; let deltagareHandelser">
<ng-container <ng-container
*ngIf="deltagareHandelser.length > 0; else noEvents" *ngIf="deltagareHandelser.length > 0; else noEvents"
> >
<table> <table>
<caption class="msfa__a11y-sr-only">Lista med alla händelser för {{deltagare?.fullName}}</caption> <caption class="msfa__a11y-sr-only">Lista med alla händelser för {{deltagare?.fullName}}</caption>
<thead> <thead>
<tr> <tr>
<th scope="col" class="deltagare-list-handelser__heading-row">Inkom</th> <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">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">Effekt</th>
<th scope="col" class="deltagare-list-handelser__heading-row">Datum förklaring</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody>
<tr *ngFor="let handelse of deltagareHandelser;"> <tr *ngFor="let handelse of deltagareHandelser;">
<td class="deltagare-list-handelser__table-cell">{{handelse.receivedDate | date}}</td> <td class="deltagare-list-handelser__table-cell">{{handelse.receivedDate | date}}</td>
<td class="deltagare-list-handelser__table-cell" *ngIf="!handelse.isAvbrott; else isAvbrottCell"> <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> {{handelse.description}}</td>
<td class="deltagare-list-handelser__table-cell">{{handelse.tidpunkt | date }}</td> </ng-template>
<td class="deltagare-list-handelser__table-cell">{{handelse.tidpunktDescription}}</td> </tr>
<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>
</tbody> </tbody>
</table> </table>
</ng-container> </ng-container>
</digi-table> </digi-table>
</div> </div>

View File

@@ -17,7 +17,6 @@
(afOnChange)="setOnlyMyDeltagare($event.detail.target.checked)" (afOnChange)="setOnlyMyDeltagare($event.detail.target.checked)"
></digi-form-checkbox> ></digi-form-checkbox>
</div> </div>
{{showUnauthorizedError$ | async}}
<ng-container *ngIf="(deltagareLoading$ | async) === false; else loadingRef"> <ng-container *ngIf="(deltagareLoading$ | async) === false; else loadingRef">
<msfa-deltagare-list <msfa-deltagare-list
*ngIf="allDeltagareData.data.length; else noDeltagare" *ngIf="allDeltagareData.data.length; else noDeltagare"

View File

@@ -31,7 +31,6 @@
grid-area: content; grid-area: content;
max-width: $digi--layout--breakpoint--l; max-width: $digi--layout--breakpoint--l;
padding: $digi--layout--gutter--l $digi--layout--gutter--l $digi--layout--gutter--xxl; padding: $digi--layout--gutter--l $digi--layout--gutter--l $digi--layout--gutter--xxl;
z-index: $msfa__z-index-default;
} }
&__breadcrumbs { &__breadcrumbs {

View File

@@ -13,7 +13,7 @@ export interface AvropCompact {
sprakstod: string; // sprakstod sprakstod: string; // sprakstod
utforandeAdress: string; // adress utforandeAdress: string; // adress
trackCode: string; // sparkod trackCode: string; // sparkod
trackName: string; // sparNamn trackName: TrackName; // sparNamn
} }
export interface Avrop extends AvropCompact { export interface Avrop extends AvropCompact {
@@ -60,11 +60,11 @@ export function mapAvropResponseToAvrop(data: AvropResponse): Avrop {
sprakstod: sprakstod, sprakstod: sprakstod,
utforandeAdress: adress, utforandeAdress: adress,
trackCode: sparkod, trackCode: sparkod,
trackName: TrackName[sparkod] || TrackName.UNKNOWN, trackName: (TrackName[sparkod] || TrackName.UNKNOWN) as TrackName,
genomforandeReferens, genomforandeReferens,
participationFrequency: deltagandeGrad, participationFrequency: deltagandeGrad,
utforandeVerksamhet: utforandeverksamhet, utforandeVerksamhet: utforandeverksamhet,
handledareCiamUserId, handledareCiamUserId: handledareCiamUserId,
handledare, handledare,
}; };
} }

View File

@@ -15,13 +15,18 @@ export interface ContactInformation {
export function mapResponseToContactInformation(data: ContactInformationResponse): ContactInformation { export function mapResponseToContactInformation(data: ContactInformationResponse): ContactInformation {
const { fornamn, efternamn, personnummer, epost, telekomadresser, adresser } = data; const { fornamn, efternamn, personnummer, epost, telekomadresser, adresser } = data;
return { return {
firstName: fornamn || '', firstName: fornamn || '',
lastName: efternamn || '', lastName: efternamn || '',
fullName: fornamn && efternamn ? `${fornamn} ${efternamn}` : '', fullName: fornamn && efternamn ? `${fornamn} ${efternamn}` : '',
ssn: personnummer ? mapStringToSsn(personnummer) : '', ssn: personnummer ? mapStringToSsn(personnummer) : '',
email: epost || '', 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, 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 { export interface DeltagareHandelse {
description: string;
receivedDate: Date;
isAvbrott: boolean;
effectDate: Date;
effectDescription: string;
}
interface DeltagareHandelseApiResponse {
description: string; description: string;
receivedDate: Date; receivedDate: Date;
isAvbrott: boolean; isAvbrott: boolean;
@@ -6,6 +29,18 @@ export interface DeltagareHandelse {
tidpunktDescription: string; tidpunktDescription: string;
} }
export interface DeltagareHandelseApiResponse { export interface DeltagareHandelserApiResponse {
data: DeltagareHandelse[]; 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 { 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 { DeltagareHandelse, DeltagareHandelseApiResponse } from '@msfa-models/deltagare-handelse.model'; import {
DeltagareHandelse,
DeltagareHandelserApiResponse,
mapDeltagareHandelseApiResponse,
} from '@msfa-models/deltagare-handelse.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@@ -18,11 +22,11 @@ export class DeltagareHandelserApiService {
} }
return this.httpClient return this.httpClient
.get<DeltagareHandelseApiResponse>(`${this._apiBaseUrl}/deltagare/${genomforandeReferens}/handelser`) .get<DeltagareHandelserApiResponse>(`${this._apiBaseUrl}/deltagare/${genomforandeReferens}/handelser`)
.pipe( .pipe(
map(({ data }) => { map(({ data }) => {
if (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 { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs';
import { catchError, distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators'; 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({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class EmployeeService extends UnsubscribeDirective { export class EmployeeService extends UnsubscribeDirective {
private _apiBaseUrl = `${environment.api.url}/users`; private _apiBaseUrl = `${environment.api.url}/users`;
private _currentEmployeeId$ = new BehaviorSubject<string>(null); private _currentEmployeeId$ = new BehaviorSubject<string>(null);
private _limit$ = new BehaviorSubject<number>(10); private _params$ = new BehaviorSubject<EmployeeParams>({
private _page$ = new BehaviorSubject<number>(1); page: 1,
private _sort$ = new BehaviorSubject<Sort<keyof EmployeeCompactResponse>>({ key: 'name', order: SortOrder.ASC }); limit: 10,
public sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this._sort$.asObservable(); sort: 'name',
private _searchFilter$ = new BehaviorSubject<string>(''); order: SortOrder.ASC,
private _onlyEmployeesWithoutAuthorization$ = new BehaviorSubject<boolean>(false); search: '',
public onlyEmployeesWithoutAuthorization$: Observable<boolean> = this._onlyEmployeesWithoutAuthorization$.asObservable(); 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); private _employee$ = new BehaviorSubject<Employee>(null);
public employee$: Observable<Employee> = this._employee$.asObservable(); public employee$: Observable<Employee> = this._employee$.asObservable();
private _lastUpdatedEmployeeId$ = new BehaviorSubject<string>(null); private _lastUpdatedEmployeeId$ = new BehaviorSubject<string>(null);
@@ -67,17 +83,8 @@ export class EmployeeService extends UnsubscribeDirective {
); );
} }
public employeesData$: Observable<EmployeesData> = combineLatest([ public employeesData$: Observable<EmployeesData> = combineLatest([this._params$, this._lastDeletedEmployee$]).pipe(
this._limit$, switchMap(([params]) => this._fetchEmployees$(params))
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 setCurrentEmployeeId(currentEmployeeId: string): void { public setCurrentEmployeeId(currentEmployeeId: string): void {
@@ -91,26 +98,21 @@ export class EmployeeService extends UnsubscribeDirective {
this._lastUpdatedEmployeeId$.next(null); this._lastUpdatedEmployeeId$.next(null);
} }
private _fetchEmployees$( private _fetchEmployees$(employeeParams: EmployeeParams): Observable<EmployeesData> {
limit: number, const { sort, order, limit, page, search, onlyEmployeesWithoutAuthorization } = employeeParams;
page: number,
sort: Sort<keyof EmployeeCompactResponse>,
searchFilter: string,
onlyEmployeesWithoutAuthorization?: boolean
): Observable<EmployeesData> {
const params: Params = { const params: Params = {
sort: sort.key as string, sort,
order: sort.order as string, order,
limit: limit.toString(), limit: limit.toString(),
page: page.toString(), page: page.toString(),
}; };
if (searchFilter) { if (search) {
params.search = searchFilter; params.search = search;
} }
if (onlyEmployeesWithoutAuthorization) { if (onlyEmployeesWithoutAuthorization) {
params.onlyEmployeesWithoutAuthorization = onlyEmployeesWithoutAuthorization?.toString(); params.onlyEmployeesWithoutAuthorization = onlyEmployeesWithoutAuthorization.toString();
} }
this._employeesLoading$.next(true); this._employeesLoading$.next(true);
@@ -134,12 +136,20 @@ export class EmployeeService extends UnsubscribeDirective {
); );
} }
public setSearchFilter(value: string): void { public setSearchString(value: string): void {
this._searchFilter$.next(value); this._params$.next({
...this._params$.getValue(),
search: value,
page: 1,
});
} }
public setOnlyEmployeesWithoutAuthorization(value: boolean): void { public setOnlyEmployeesWithoutAuthorization(value: boolean): void {
this._onlyEmployeesWithoutAuthorization$.next(value); this._params$.next({
...this._params$.getValue(),
onlyEmployeesWithoutAuthorization: value,
page: 1,
});
} }
public setEmployeeToDelete(employee: Employee): void { public setEmployeeToDelete(employee: Employee): void {
@@ -156,16 +166,17 @@ export class EmployeeService extends UnsubscribeDirective {
); );
} }
public setSort(newSortKey: keyof EmployeeCompactResponse): void { public setSort(sort: keyof EmployeeCompactResponse): void {
const currentSort = this._sort$.getValue(); const currentParams = this._params$.getValue();
const order = const currentSort = currentParams.sort;
currentSort.key === newSortKey && currentSort.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; 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 { public setPage(page: number): void {
this._page$.next(page); this._params$.next({ ...this._params$.getValue(), page });
} }
public postEmployeeInvitation(emails: string[]): Observable<EmployeeInviteResponse | null> { public postEmployeeInvitation(emails: string[]): Observable<EmployeeInviteResponse | null> {

View File

@@ -95,8 +95,11 @@ export class AvropService {
this._avropIsLocked$, this._avropIsLocked$,
this._avropIsSubmitted$, this._avropIsSubmitted$,
]).pipe( ]).pipe(
map(([confirmedHandledare, avropIsLocked, avropIsSubmitted]) => { map(([confirmedHandledare, avropIsLocked, avropIsSubmitted]) =>
return AvropService.calculateStep(confirmedHandledare, avropIsLocked, avropIsSubmitted); AvropService.calculateStep(confirmedHandledare, avropIsLocked, avropIsSubmitted)
),
tap(() => {
window.scrollTo(0, 0);
}) })
); );

View File

@@ -6,6 +6,7 @@
- **authorization:** Whenever the API throws a "403 Forbidden" error we now show an unauthorized message. [TV-695](https://jira.arbetsformedlingen.se/browse/TV-695) - **authorization:** Whenever the API throws a "403 Forbidden" error we now show an unauthorized message. [TV-695](https://jira.arbetsformedlingen.se/browse/TV-695)
- **avrop:** Now showing spårnamn instead of spårkod inside avrop. [TV-691](https://jira.arbetsformedlingen.se/browse/TV-691) - **avrop:** Now showing spårnamn instead of spårkod inside avrop. [TV-691](https://jira.arbetsformedlingen.se/browse/TV-691)
- **avrop:** Fixed error-state on handledare select. [TV-699](https://jira.arbetsformedlingen.se/browse/TV-699) - **avrop:** Fixed error-state on handledare select. [TV-699](https://jira.arbetsformedlingen.se/browse/TV-699)
- **avrop:** Several bugfixes related to avrop. [TV-701](https://jira.arbetsformedlingen.se/browse/TV-701)
## [2.0.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv1.5.0&sourceBranch=refs%2Ftags%2Fv2.0.0) (2021-09-24) ## [2.0.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv1.5.0&sourceBranch=refs%2Ftags%2Fv2.0.0) (2021-09-24)

View File

@@ -24,13 +24,31 @@ Den här webbplatsen är delvis förenlig med lagen om tillgänglighet till digi
Vi är medvetna om följande brister som inte följer lagkraven: Vi är medvetna om följande brister som inte följer lagkraven:
- Inga brister hittades - Användare kan inte hoppa över innehåll som upprepas på flera sidor (till exempel menyer) genom en skip-länk som leder till huvudinnehållet. (WCAG 2.4.1 Hoppa över grupperat innehåll)
- Tabeller ihar inte en överskrift i form av en *caption* som kort beskriver vilken information tabellen innehåller. Tex tabellen för personallistan ska ha ett namn.(WCAG Tabeller har överskrifter1.3.1 Information och relationer)
- Alla obligatoriska fält är inte uppmärkta med (obligatoriskt). Finns fält som inte är uppmärkta tex personal. Dvs där det inte står att dem fälten är obligatoriska fast dem är obligatoriska att fylla. (WCAG 3.3.2 Etiketter och instruktioner)
- Inmatningsfel upptäcks inte automatiskt så att fältet markeras och felet beskrivs inte för användaren med text. Fältet får inte attributet aria-invalid. (WCAG 3.3.1 Identifiering av fel).
- Meddelande visas inte om en användare skickar in ett formulär som innehåller fel, kommuniceras inte heller till användare med skärmläsare och får fokus. Behöver stämmas av med tillgänglighetstestare gällande implementering av felmeddelande med fokus.(WCAG 3.3.1 Identifiering av fel)
- Ändra text mellanrum har vissa brister som kommer kollas på framöver under 2021.(WCAG 1.4.12 Textmellanrum)
- Brister gällande förutsägbarhet vid inmatning. Förklarande text för skärmläsare i filtersökfältet behöver adderas.Lägga till area attribut.Skärmläsare behöver få notifieringar på vad som händer , dvs gällande resultat och vad som söks på (WCAG 3.2.2 Förutsägbarhet vid inmatning).
- Läs ordning för hjälpmedel har testats och fungerar, men behöver testas mer strukturerat och fokuserat tillsammans med tillgänglighetstestare (WCAG 1.3.2 Meningsfull ordning)
- Brister gällande dölj dekorativa bilder. Bild behöver döljas med attribut "area hidden" så att det inte läses upp för skärmläsaren (WCAG 1.1.1 Innehåll som inte är text).
- Brister gällande semantiska länkar tex länkar länkar till samma sidor, pga navigeringen ligger fast (WCAG 1.3.1 Information och relationer).
- Brister gällande tydliga länktexter vissa knappar är oklara gällande sin betydelse tex står avbryt på en knapp men det behöver förtydligas vad man avbryter . Det står på en annan knapp "Redigera" men det behöver förtydligas vad man redigerar (WCAG 2.4.4 / 2.4.9 Länksyfte).
- Beskrivande etiketter behöver testas en gång till tillsammans med tillgänglighetstestare (WCAG 2.4.6 Rubriker och etiketter).
- Brist gällande felmeddelande kommuniceras till användare med skärmläsare utan att felmeddelandet får fokus. Behöver lägga in area-liv i felmeddelande och det behöver även testas en gång till tillsammans med tillgänglighetstestare (WCAG 4.1.3 Statusmeddelande).
- Brist gällande innehåll som förändras dynamiskt tex man får inte positiv feedback att man genomfört en sökning i personallistan. Tex att sökningen genomförts, inte heller sökträffar. Samt när man filtrerar sökningen så får man inte feedback på att filtreringen genomförts. (WCAG 4.1.3 Statusmeddelande).
- Brist gällande dölj innehåll som inte ska visas för skärmläsare, tex dekorativ välkomstbild bör döljas (WCAG 1.1.1 Innehåll som inte är text).
Områden som behöver testas framöver:
- Ytterligare områden som behöver testas är gällande kodkvalitet bl.a. gällande rätt html-markup, att alla HTML-element har fullständiga start & slut-taggar och är nästlade enligt deras specifikationer (WCAG 4.1.1 Se till att koden validerar). Behöver även testas att inga dubbla attribut dvs inga HTML-element innehåller dubbla attributangivelser (WCAG 4.1.1 Se till att koden validerar). Behöver även testas att alla HTML-element har unika id, dvs ett id förekommer endast en gång på sidan (WCAG 4.1.1 Se till att koden validerar).
- Ytterligare test behöver även göras kring konsekvent identifiering i tjänsten och konsekvent identifiering på AF (3.2.4 Konsekvent identifiering). Dvs test med tillgänglighetstestare bör göras kring funktioner som finns på flera sidor i tjänsten identifieras på ett konsekvent sätt. Samma termer används t.ex för knappar som gör samma sak (både synlig text och textalternativ). Gäller funktioner i tjänsten. Även test med tillgänglighetstestare bör göras gällande funktioner i tjänsten identifieras på samma sätt som på andra ställen där de förekommer på webbplatsen. Samma termer används t.ex för knappar som gör samma sak.
## Hur vi har testat webbplatsen ## Hur vi har testat webbplatsen
Vi har gjort en självskattning (intern testning) av webbplatsen. Senaste bedömningen gjordes 2021-09-10. Vi har gjort en självskattning (intern testning) av webbplatsen. Senaste bedömningen gjordes 2021-09-27.
Redogörelsen uppdaterades den 2021-09-10. Redogörelsen uppdaterades den 2021-09-28.
## Hur vi jobbar med digital tillgänglighet ## Hur vi jobbar med digital tillgänglighet

View File

@@ -32,7 +32,7 @@ function generateAvrop(amount = 10, deltagare, handledare) {
fornamn: currentDeltagare.contact.fornamn, fornamn: currentDeltagare.contact.fornamn,
efternamn: currentDeltagare.contact.efternamn, efternamn: currentDeltagare.contact.efternamn,
deltagare: `${currentDeltagare.contact.fornamn} ${currentDeltagare.contact.efternamn}`, deltagare: `${currentDeltagare.contact.fornamn} ${currentDeltagare.contact.efternamn}`,
genomforandeReferens: faker.datatype.number({ min: 100000000, max: 999999999 }), genomforandeReferens: currentDeltagare.genomforandeReferens,
orgId: faker.datatype.uuid(), orgId: faker.datatype.uuid(),
leverantorId: faker.datatype.number({ min: 1000, max: 99999 }), leverantorId: faker.datatype.number({ min: 1000, max: 99999 }),
organisationsnummer: organization.organizationNumber, organisationsnummer: organization.organizationNumber,

View File

@@ -13,7 +13,21 @@ const HANDELSER = [
'Byte av leverantör bifall', 'Byte av leverantör bifall',
]; ];
const TIDPUNKT_DESCRIPTION = ['Handläggning startad', 'Inrapporteringsdatum slutredovisning']; const TIDPUNKT_DESCRIPTION = [
'Inrapporteringsdatum_GP',
'Godkannandedatum_GP',
'Avvisatdatum_GP',
'Inrapporteringsdatum_PR',
'Godkannandedatum_PR',
'Avvisatdatum_PR',
'Inrapporteringsdatum_SR',
'Godkannandedatum_SR',
'Avvisatdatum_SR',
'Handlaggning_startad',
'Andringsbeslutdatum',
'Slut_avropsperiod',
'',
];
function generateHandelser(amount = 10) { function generateHandelser(amount = 10) {
const handelser = []; const handelser = [];

View File

@@ -44,9 +44,12 @@ function generateDeltagare(amount = 10) {
const educationLevel = EDUCATION_LEVELS[Math.floor(Math.random() * EDUCATION_LEVELS.length)]; const educationLevel = EDUCATION_LEVELS[Math.floor(Math.random() * EDUCATION_LEVELS.length)];
const sunKod = SUN_KODER[Math.floor(Math.random() * SUN_KODER.length)]; const sunKod = SUN_KODER[Math.floor(Math.random() * SUN_KODER.length)];
const genomforandeReferens = faker.datatype.number({ min: 100000000, max: 999999999 });
const deltagare = { const deltagare = {
id, id: genomforandeReferens,
sokandeId: id, sokandeId: id,
genomforandeReferens,
contact: { contact: {
fornamn, fornamn,
efternamn, efternamn,

View File

@@ -147,7 +147,7 @@ router.render = (req, res) => {
startdatumAvrop, startdatumAvrop,
slutdatumAvrop, slutdatumAvrop,
hasAvbrott, hasAvbrott,
genomforandeReferens, genomforandeReferens: genomforandeReferens.toString(),
}) })
); );
} }