Compare commits

..

10 Commits

Author SHA1 Message Date
Daniel Appelgren
33149f2fcb fix(Gemensam planering): Ändrat datum då det tillåts att skicka in gemensam planering och slutredovisning från 15 resp 100 till 84 (dvs 60*7/5) 2022-01-25 12:20:59 +01:00
Daniel Appelgren
79f5ef34a5 chore(changelog): Updated CHANGELOG.md inside assets directory 2022-01-11 10:45:42 +01:00
semantic-release-bot
6ebc01fc59 chore(release): 2.5.1 [skip ci]
### [2.5.1](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.5.0&sourceBranch=refs%2Ftags%2Fv2.5.1) (2022-01-11)

### Features

* **Avvikelserapporten:** Använd nya dialogen ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([8909f7c](8909f7c3d1))
* **Nya deltagare:** Använd nya dialogen ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([b943474](b943474138))
* **automatisk utloggning:** Använder nu den nya dialog-komponenten ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([1de7624](1de7624200)), closes [feature/TV-845-ui-dialog-idle-timout-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-idle-timout-dialog-2) [feature/TV-845-ui-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-2) [feature/TV-845-ui-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-2)
* **Deltagarlista:** Nu går det att sortera när man klickar på kolumnnamnet ([TV-989](https://jira.arbetsformedlingen.se/browse/TV-989)) ([fb6239a](fb6239afbe))
* **Signal om arbete eller studier:** Uppdaterad UX för val av sysselsättning (fyra radioknappar, ingen drop-down ([TV-440](https://jira.arbetsformedlingen.se/browse/TV-440)) ([65c29e0](65c29e065d))

### Bug Fixes

* **informativ-rapport:** Added file-upload inside informativ-rapport to feature toggling ([92cf04e](92cf04e802))
* **gemensam-planering:** Implemented new API response for GP ([a158b4a](a158b4ac21))
* **login:** Removed login with username/password from prod builds ([a6f3792](a6f37923ab))
* **texter:** Updated button text and changed aria texts for filter functionality inside utförande verksamheter ([TV-953](https://jira.arbetsformedlingen.se/browse/TV-953)) ([8fb58d6](8fb58d698c))
2022-01-11 10:45:39 +01:00
Daniel Appelgren
3ffd591cf5 fixed tests 2022-01-11 10:39:59 +01:00
Daniel Appelgren
48d864a628 Update release.sh 2022-01-11 10:24:23 +01:00
Daniel Appelgren
65c29e065d feature(Signal om arbete eller studier): Uppdaterad UX för val av sysselsättning (fyra radioknappar, ingen drop-down (TV-440)
Merge in TEA/mina-sidor-fa-web from feature/TV-440 to develop

Squashed commit of the following:

commit 654b809ae6900a98cc568a7e3fde9b820fe5d515
Author: fueno <nicolas.fuentes-maturana@arbetsformedlingen.se>
Date:   Thu Dec 30 15:47:46 2021 +0100

    TV-440  some renameing

commit 5eb2fb70dc8791badf3ba3e15b4454065db36c4a
Author: fueno <nicolas.fuentes-maturana@arbetsformedlingen.se>
Date:   Thu Dec 30 11:06:15 2021 +0100

    TV-440 renamed enums for work and education

commit b1c80d583277d434dfb837d46d7d469aa8ed1fb6
Author: fueno <nicolas.fuentes-maturana@arbetsformedlingen.se>
Date:   Wed Dec 29 15:32:33 2021 +0100

    TV-440 UX updated, changed select to radiobuttons
2022-01-04 15:39:02 +01:00
Daniel Appelgren
fb6239afbe feature(Deltagarlista): Nu går det att sortera när man klickar på kolumnnamnet (TV-989)
Squashed commit of the following:

commit 80fd78e85fca208ce35a1731a2fbd8e913da27db
Author: fueno <nicolas.fuentes-maturana@arbetsformedlingen.se>
Date:   Thu Dec 30 16:17:43 2021 +0100

    TV-989 sort by click column name, corrected icon position
2022-01-04 11:20:06 +01:00
Daniel Appelgren
8909f7c3d1 feature(Avvikelserapporten): Använd nya dialogen (TV-845)
Merge in TEA/mina-sidor-fa-web from feature/TV-845-ersätt-dialogen-i-Avvikelserapporten-(TV-845) to develop

Squashed commit of the following:

commit 780f93baab0891a2f2ba49bf14d2c3add99e03c8
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Dec 30 11:27:28 2021 +0100

    Update avvikelse-report-form.component.ts

commit bea883f6aedfe2655efbb69d66788c1366e48a43
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Dec 30 11:19:03 2021 +0100

    cleanup

commit ffdf13dfcb3d97960ddb585b09fa5e6446504a68
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:52:37 2021 +0100

    Update app.component.ts

commit 1631cb763bc7023a9e95682272fb63dcbe15d84e
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:52:03 2021 +0100

    fix deltagarelist

commit c6080ac50cb6773aac8d4e45336fc1ba2f053a8d
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:24:44 2021 +0100

    Update avrop.component.html

commit 85057d0860ddceef8309253c983a1674a57291c4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:21:33 2021 +0100

    wip

commit a73164bda8a8ae06c5700e382e197d823bef6767
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:20:04 2021 +0100

    wip

commit 83f3ada5c4c60c9e46d7b01bbbf92053eb1b29ff
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:18:30 2021 +0100

    wip

commit 5e184bc0e1a3f7bb6a3040d4da54a9b0e562dad3
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 14:18:39 2021 +0100

    wip

commit 30a90ce726dde31974e26c974215f6a5a60b025e
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 15:11:14 2021 +0100

    added scrollbars inside dialog

commit 600dd20f3281b4206c12d851cba91ae0ecf1e21a
Merge: 7af19054 b9434741
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 14:30:49 2021 +0100

    Merge branch 'develop' into feature/TV-845-ersätt-dialogen-i-Avvikelserapporten-(TV-845)

commit 7af190549c0109f71af87157e8099aa0483c879f
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 12:05:22 2021 +0100

    wip

commit 6e47e4a641daf4cef121e96c3855e4ce4944c6c4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 09:38:36 2021 +0100

    wip

commit f9354d04f51425cce29f13a10b32555113edaaa2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 09:16:54 2021 +0100

    feature(Nya deltagare): Använd nya dialogen (TV-845)
2021-12-30 14:18:58 +01:00
Daniel Appelgren
7c48ec175e Refactor extract component logic from Employee api service
Merge in TEA/mina-sidor-fa-web from Refactor-extract-component-logic-from-Employee-api-service to develop

Squashed commit of the following:

commit e57ac1b6f34bee042f53f21166feedcef0d72ac2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 14:29:50 2021 +0100

    refactor

commit e4d9bfd514576f6de8ae7095a0b1e41a096d39c2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 09:02:16 2021 +0100

    remove reduntant component

commit 2b624a7e99e76e8eece3e59720f2de99f44ec9ba
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Dec 27 15:40:27 2021 +0100

    rename

commit ec87d82063bef38325255e168485962a5d988575
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Dec 27 15:38:35 2021 +0100

    refactor: extract component logic from  Employee-api-service
2021-12-29 14:19:07 +01:00
Daniel Appelgren
b943474138 feature(Nya deltagare): Använd nya dialogen (TV-845)
Merge in TEA/mina-sidor-fa-web from feature(Nya-deltagare)-Använd-nya-dialogen-(TV-845) to develop

Squashed commit of the following:

commit cdc3acefcdd6e209e8009fb767ae375a0db2935a
Merge: f9354d04 1de76242
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 14:26:35 2021 +0100

    Merge branch 'develop' into feature(Nya-deltagare)-Använd-nya-dialogen-(TV-845)

commit f9354d04f51425cce29f13a10b32555113edaaa2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 09:16:54 2021 +0100

    feature(Nya deltagare): Använd nya dialogen (TV-845)
2021-12-28 14:30:25 +01:00
59 changed files with 803 additions and 568 deletions

View File

@@ -1,3 +1,22 @@
### [2.5.1](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.5.0&sourceBranch=refs%2Ftags%2Fv2.5.1) (2022-01-11)
### Features
* **Avvikelserapporten:** Använd nya dialogen ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([8909f7c](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/8909f7c3d1e91833988c69d1f8485cd217654e32))
* **Nya deltagare:** Använd nya dialogen ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([b943474](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/b943474138442cec88f371e8477126f8dacc7e07))
* **automatisk utloggning:** Använder nu den nya dialog-komponenten ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([1de7624](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/1de762420054ca6782660dd950c938541f543291)), closes [feature/TV-845-ui-dialog-idle-timout-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-idle-timout-dialog-2) [feature/TV-845-ui-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-2) [feature/TV-845-ui-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-2)
* **Deltagarlista:** Nu går det att sortera när man klickar på kolumnnamnet ([TV-989](https://jira.arbetsformedlingen.se/browse/TV-989)) ([fb6239a](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/fb6239afbe2cfe2652f0f9b407f684ddc77e239a))
* **Signal om arbete eller studier:** Uppdaterad UX för val av sysselsättning (fyra radioknappar, ingen drop-down ([TV-440](https://jira.arbetsformedlingen.se/browse/TV-440)) ([65c29e0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/65c29e065d66cda8e6c6fa71af30558d303d9d39))
### Bug Fixes
* **informativ-rapport:** Added file-upload inside informativ-rapport to feature toggling ([92cf04e](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/92cf04e802cce05c1ed799cabfabf73b1c370c0c))
* **gemensam-planering:** Implemented new API response for GP ([a158b4a](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/a158b4ac21f9dbab84865f461010dab63c50a7fa))
* **login:** Removed login with username/password from prod builds ([a6f3792](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/a6f37923abc70627e8b0b98f18f5f3791321da3d))
* **texter:** Updated button text and changed aria texts for filter functionality inside utförande verksamheter ([TV-953](https://jira.arbetsformedlingen.se/browse/TV-953)) ([8fb58d6](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/8fb58d698ce26e5031174040978f69ba1af1f57e))
## [2.5.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.4.0&sourceBranch=refs%2Ftags%2Fv2.5.0) (2021-12-14) ## [2.5.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.4.0&sourceBranch=refs%2Ftags%2Fv2.5.0) (2021-12-14)

View File

@@ -3,13 +3,14 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AppModule } from './app.module';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AppComponent], declarations: [AppComponent],
imports: [RouterTestingModule, HttpClientTestingModule], imports: [AppModule, RouterTestingModule, HttpClientTestingModule],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -33,6 +33,7 @@ export class AppComponent extends UnsubscribeDirective implements OnInit {
private _dialogRef: UiDialogRef; private _dialogRef: UiDialogRef;
private _userIsIdle$: Observable<boolean> = this.idleService.isIdle$; private _userIsIdle$: Observable<boolean> = this.idleService.isIdle$;
private _idleDialogConfig: UiDialogConfig = { private _idleDialogConfig: UiDialogConfig = {
includeBasicFooter: true,
primaryButtonText: 'Fortsätt sessionen', primaryButtonText: 'Fortsätt sessionen',
primaryAction: () => this.setUserAsActive(), primaryAction: () => this.setUserAsActive(),
secondaryButtonText: 'Logga ut', secondaryButtonText: 'Logga ut',

View File

@@ -1,27 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AdministrationComponent } from './administration.component';
describe('AdministrationComponent', () => {
let component: AdministrationComponent;
let fixture: ComponentFixture<AdministrationComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [AdministrationComponent],
imports: [RouterTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(AdministrationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,9 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'msfa-administration',
templateUrl: './administration.component.html',
styleUrls: ['./administration.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdministrationComponent {}

View File

@@ -1,10 +1,9 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { AdministrationRoutingModule } from './administration-routing.module'; import { AdministrationRoutingModule } from './administration-routing.module';
import { AdministrationComponent } from './administration.component';
@NgModule({ @NgModule({
declarations: [AdministrationComponent], declarations: [],
imports: [CommonModule, AdministrationRoutingModule], imports: [CommonModule, AdministrationRoutingModule],
}) })
export class AdministrationModule {} export class AdministrationModule {}

View File

@@ -0,0 +1,141 @@
import { Injectable } from '@angular/core';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { SortOrder } from '@msfa-enums/sort-order.enum';
import { EmployeeEditRequest } from '@msfa-models/api/employee-edit.request.model';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
import { EmployeeCompactResponse } from '@msfa-models/api/employee.response.model';
import { EmployeeParams } from '@msfa-models/api/params.model';
import { Employee, EmployeesData } from '@msfa-models/employee.model';
import { Sort } from '@msfa-models/sort.model';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, switchMap, tap } from 'rxjs/operators';
import { EmployeeApiService } from '@msfa-services/api/employee.api.service';
const DEFAULT_PARAMS: EmployeeParams = {
page: 1,
limit: 10,
sort: 'name',
order: SortOrder.ASC,
search: '',
onlyEmployeesWithoutAuthorization: false,
};
@Injectable({
providedIn: 'root',
})
export class AdministrationService extends UnsubscribeDirective {
private _currentEmployeeId$ = new BehaviorSubject<string>(null);
private _params$ = new BehaviorSubject<EmployeeParams>(DEFAULT_PARAMS);
sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this._params$.pipe(
map(({ sort, order }) => ({ key: sort, order }))
);
onlyEmployeesWithoutAuthorization$: Observable<boolean> = this._params$.pipe(
map(({ onlyEmployeesWithoutAuthorization }) => onlyEmployeesWithoutAuthorization)
);
private _employee$ = new BehaviorSubject<Employee>(null);
employee$: Observable<Employee> = this._employee$.asObservable();
private _lastUpdatedEmployeeId$ = new BehaviorSubject<string>(null);
lastUpdatedEmployeeId$: Observable<string> = this._lastUpdatedEmployeeId$.asObservable();
private _lastDeletedEmployee$ = new BehaviorSubject<Employee>(null);
lastDeletedEmployee$: Observable<Employee> = this._lastDeletedEmployee$.asObservable();
private _employeeToDelete$ = new BehaviorSubject<Employee>(null);
employeeToDelete$: Observable<Employee> = this._employeeToDelete$.asObservable();
private _employeesLoading$ = new BehaviorSubject<boolean>(false);
employeesLoading$: Observable<boolean> = this._employeesLoading$.asObservable();
constructor(private employeeApiService: EmployeeApiService) {
super();
super.unsubscribeOnDestroy(
combineLatest([this._currentEmployeeId$, this._lastUpdatedEmployeeId$])
.pipe(
distinctUntilChanged(
([prevEmployeeId], [currEmployeeId, currLastUpdatedEmployeeId]) =>
!currLastUpdatedEmployeeId && prevEmployeeId === currEmployeeId
),
filter(([currentEmployeeId]) => !!currentEmployeeId),
switchMap(([currentEmployeeId]) =>
this._fetchEmployee$(currentEmployeeId).pipe(filter(employee => !!employee))
)
)
.subscribe(employee => {
this._employee$.next(employee);
})
);
}
employeesData$: Observable<EmployeesData> = combineLatest([this._params$, this._lastDeletedEmployee$]).pipe(
switchMap(([params]) => this._fetchEmployees$(params))
);
resetParams(): void {
this._params$.next(DEFAULT_PARAMS);
}
setCurrentEmployeeId(currentEmployeeId: string): void {
if (this._currentEmployeeId$.getValue() !== currentEmployeeId) {
this._employee$.next(null);
this._currentEmployeeId$.next(currentEmployeeId);
}
}
resetLastUpdatedEmployeeId(): void {
this._lastUpdatedEmployeeId$.next(null);
}
private _fetchEmployees$(employeeParams: EmployeeParams): Observable<EmployeesData> {
this._employeesLoading$.next(true);
return this.employeeApiService.fetchEmployees$(employeeParams).pipe(tap(() => this._employeesLoading$.next(false)));
}
private _fetchEmployee$(ciamUserId: string): Observable<Employee> {
return this.employeeApiService.fetchEmployee$(ciamUserId);
}
setSearchString(value: string): void {
this._params$.next({
...this._params$.getValue(),
search: value,
page: 1,
});
}
setOnlyEmployeesWithoutAuthorization(value: boolean): void {
this._params$.next({
...this._params$.getValue(),
onlyEmployeesWithoutAuthorization: value,
page: 1,
});
}
setEmployeeToDelete(employee: Employee): void {
this._employeeToDelete$.next(employee);
}
deleteEmployee(employee: Employee): Observable<Employee> {
return this.employeeApiService.deleteEmployee(employee).pipe(
tap(() => {
this._lastDeletedEmployee$.next(employee);
})
);
}
setSort(sort: keyof EmployeeCompactResponse): void {
const params = this._params$.getValue();
const order = params.sort === sort && params.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
this._params$.next({ ...params, sort, order });
}
setPage(page: number): void {
this._params$.next({ ...this._params$.getValue(), page });
}
postEmployeeInvitation$(emails: string[]): Observable<EmployeeInviteResponse | null> {
return this.employeeApiService.postEmployeeInvitation(emails);
}
updateEmployee$(ciamUserId: string, data: EmployeeEditRequest): Observable<boolean> {
return this.employeeApiService.updateEmployee$(ciamUserId, data);
}
}

View File

@@ -1,32 +1,32 @@
<ng-container *ngIf="deleteEmployeeData$ | async as deleteEmployeeData"> <ng-container *ngIf="deleteEmployeeData$ | async as deleteEmployeeData">
<digi-ng-dialog <!-- <digi-ng-dialog-->
*ngIf="deleteEmployeeData.toDelete" <!-- *ngIf="deleteEmployeeData.toDelete"-->
[afActive]="deleteEmployeeData.toDelete" <!-- [afActive]="deleteEmployeeData.toDelete"-->
(afOnPrimaryClick)="deleteEmployeeModelPrimaryClick(deleteEmployeeData)" <!-- (afOnPrimaryClick)="deleteEmployeeModelPrimaryClick(deleteEmployeeData)"-->
(afOnSecondaryClick)="closeDeleteEmployeeModal()" <!-- (afOnSecondaryClick)="closeDeleteEmployeeModal()"-->
(afOnInactive)="closeDeleteEmployeeModal()" <!-- (afOnInactive)="closeDeleteEmployeeModal()"-->
afHeading="Ta bort personalkonto" <!-- afHeading="Ta bort personalkonto"-->
afHeadingLevel="h2" <!-- afHeadingLevel="h2"-->
[afPrimaryButtonText]="getPrimaryButtonText(deleteEmployeeData.lastDeleted)" <!-- [afPrimaryButtonText]="getPrimaryButtonText(deleteEmployeeData.lastDeleted)"-->
[afSecondaryButtonText]="getSecondaryButtonText(deleteEmployeeData.lastDeleted)" <!-- [afSecondaryButtonText]="getSecondaryButtonText(deleteEmployeeData.lastDeleted)"-->
> <!-- >-->
<ng-container *ngIf="deleteEmployeeData.lastDeleted; else deletionWarning"> <!-- <ng-container *ngIf="deleteEmployeeData.lastDeleted; else deletionWarning">-->
<digi-notification-alert af-variation="success" af-heading="Allt gick bra" af-heading-level="h3"> <!-- <digi-notification-alert af-variation="success" af-heading="Allt gick bra" af-heading-level="h3">-->
<p>Personalkonto för {{deleteEmployeeData.lastDeleted.fullName}} är borttaget.</p> <!-- <p>Personalkonto för {{deleteEmployeeData.lastDeleted.fullName}} är borttaget.</p>-->
</digi-notification-alert> <!-- </digi-notification-alert>-->
</ng-container> <!-- </ng-container>-->
<ng-template #deletionWarning> <!-- <ng-template #deletionWarning>-->
<p>Är du säker på att du vill ta bort personalkontot för {{deleteEmployeeData.toDelete.fullName}}?</p> <!-- <p>Är du säker på att du vill ta bort personalkontot för {{deleteEmployeeData.toDelete.fullName}}?</p>-->
</ng-template> <!-- </ng-template>-->
<ng-container *ngIf="errorDuringDeletion$ | async as error"> <!-- <ng-container *ngIf="errorDuringDeletion$ | async as error">-->
<digi-notification-alert af-variation="danger" af-heading="Någonting gick fel" af-heading-level="h3"> <!-- <digi-notification-alert af-variation="danger" af-heading="Någonting gick fel" af-heading-level="h3">-->
<p> <!-- <p>-->
Vi kunde inte radera personalkontot för {{deleteEmployeeData.toDelete.fullName}}. Ladda om sidan och försök <!-- Vi kunde inte radera personalkontot för {{deleteEmployeeData.toDelete.fullName}}. Ladda om sidan och försök-->
igen. <!-- igen.-->
</p> <!-- </p>-->
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p> <!-- <p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>-->
</digi-notification-alert> <!-- </digi-notification-alert>-->
</ng-container> <!-- </ng-container>-->
</digi-ng-dialog> <!-- </digi-ng-dialog>-->
</ng-container> </ng-container>

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Employee } from '@msfa-models/employee.model'; import { Employee } from '@msfa-models/employee.model';
import { CustomError } from '@msfa-models/error/custom-error'; import { CustomError } from '@msfa-models/error/custom-error';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { AdministrationService } from '../../administration.service';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@@ -21,8 +21,8 @@ export class EmployeeDeleteComponent {
@Input() returnToEmployeeList = false; @Input() returnToEmployeeList = false;
deleteEmployeeData$: Observable<DeleteEmployeeData> = combineLatest([ deleteEmployeeData$: Observable<DeleteEmployeeData> = combineLatest([
this.employeeService.employeeToDelete$, this.administrationService.employeeToDelete$,
this.employeeService.lastDeletedEmployee$, this.administrationService.lastDeletedEmployee$,
]).pipe( ]).pipe(
map(([employeeToDelete, lastDeletedEmployee]) => { map(([employeeToDelete, lastDeletedEmployee]) => {
return { return {
@@ -42,7 +42,7 @@ export class EmployeeDeleteComponent {
return lastDeleted ? null : 'Avbryt'; return lastDeleted ? null : 'Avbryt';
} }
constructor(private employeeService: EmployeeService, private router: Router) {} constructor(private administrationService: AdministrationService, private router: Router) {}
deleteEmployeeModelPrimaryClick(deleteEmployeeData: DeleteEmployeeData): void { deleteEmployeeModelPrimaryClick(deleteEmployeeData: DeleteEmployeeData): void {
const { lastDeleted, toDelete } = deleteEmployeeData; const { lastDeleted, toDelete } = deleteEmployeeData;
@@ -54,7 +54,7 @@ export class EmployeeDeleteComponent {
return; return;
} }
const deleteEmployeeSubscription = this.employeeService.deleteEmployee(toDelete).subscribe({ const deleteEmployeeSubscription = this.administrationService.deleteEmployee(toDelete).subscribe({
error: (error: CustomError) => { error: (error: CustomError) => {
this._errorDuringDeletion$.next(error); this._errorDuringDeletion$.next(error);
}, },
@@ -65,7 +65,7 @@ export class EmployeeDeleteComponent {
} }
closeDeleteEmployeeModal(): void { closeDeleteEmployeeModal(): void {
this.employeeService.setEmployeeToDelete(null); this.administrationService.setEmployeeToDelete(null);
this._errorDuringDeletion$.next(null); this._errorDuringDeletion$.next(null);
} }
} }

View File

@@ -1,4 +1,3 @@
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@@ -7,7 +6,11 @@ import { EmployeeDeleteComponent } from './employee-delete.component';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [EmployeeDeleteComponent], declarations: [EmployeeDeleteComponent],
imports: [CommonModule, RouterModule, DigiNgDialogModule], imports: [
CommonModule,
RouterModule,
// DigiNgDialogModule
],
exports: [EmployeeDeleteComponent], exports: [EmployeeDeleteComponent],
}) })
export class EmployeeDeleteModule {} export class EmployeeDeleteModule {}

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Employee } from '@msfa-models/employee.model'; import { Employee } from '@msfa-models/employee.model';
import { Role } from '@msfa-models/role.model'; import { Role } from '@msfa-models/role.model';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { AdministrationService } from '../../administration.service';
import { RoleService } from '@msfa-services/role.service'; import { RoleService } from '@msfa-services/role.service';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
@@ -14,22 +14,22 @@ import { BehaviorSubject, Observable } from 'rxjs';
}) })
export class EmployeeCardComponent implements OnDestroy { export class EmployeeCardComponent implements OnDestroy {
private _employeeId$ = new BehaviorSubject<string>(this.activatedRoute.snapshot.params['employeeId']); private _employeeId$ = new BehaviorSubject<string>(this.activatedRoute.snapshot.params['employeeId']);
employee$: Observable<Employee> = this.employeeService.employee$; employee$: Observable<Employee> = this.administrationService.employee$;
lastUpdatedEmployeeId$: Observable<string> = this.employeeService.lastUpdatedEmployeeId$; lastUpdatedEmployeeId$: Observable<string> = this.administrationService.lastUpdatedEmployeeId$;
allRoles: Role[] = this.roleService.allRoles; allRoles: Role[] = this.roleService.allRoles;
accordionsExpanded = []; accordionsExpanded = [];
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private employeeService: EmployeeService, private administrationService: AdministrationService,
private roleService: RoleService private roleService: RoleService
) { ) {
this.employeeService.setCurrentEmployeeId(this.employeeId); this.administrationService.setCurrentEmployeeId(this.employeeId);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.employeeService.resetLastUpdatedEmployeeId(); this.administrationService.resetLastUpdatedEmployeeId();
this.employeeService.setCurrentEmployeeId(null); this.administrationService.setCurrentEmployeeId(null);
} }
get employeeId(): string { get employeeId(): string {
@@ -41,7 +41,7 @@ export class EmployeeCardComponent implements OnDestroy {
} }
closeUpdatedNotificationAlert(): void { closeUpdatedNotificationAlert(): void {
this.employeeService.resetLastUpdatedEmployeeId(); this.administrationService.resetLastUpdatedEmployeeId();
} }
toggleAccordionExpanded(currentId: number): void { toggleAccordionExpanded(currentId: number): void {
@@ -52,7 +52,7 @@ export class EmployeeCardComponent implements OnDestroy {
} }
} }
hasAccess(employee: Employee, role: Role): boolean{ hasAccess(employee: Employee, role: Role): boolean {
return employee.roles.includes(role.type); return employee.roles.includes(role.type);
} }
} }

View File

@@ -6,7 +6,7 @@ import { CustomError } from '@msfa-models/error/custom-error';
import { Role } from '@msfa-models/role.model'; import { Role } from '@msfa-models/role.model';
import { Tjanst } from '@msfa-models/tjanst.model'; import { Tjanst } from '@msfa-models/tjanst.model';
import { UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model'; import { UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { AdministrationService } from '../../administration.service';
import { RoleService } from '@msfa-services/role.service'; import { RoleService } from '@msfa-services/role.service';
import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service'; import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service';
import { BehaviorSubject, Observable, of } from 'rxjs'; import { BehaviorSubject, Observable, of } from 'rxjs';
@@ -29,14 +29,14 @@ export class EmployeeFormComponent implements OnInit {
); );
errorWhileUpdating$: Observable<CustomError> = this._errorWhileUpdating$.asObservable(); errorWhileUpdating$: Observable<CustomError> = this._errorWhileUpdating$.asObservable();
employee$ = this.employeeService.employee$; employee$ = this.administrationService.employee$;
tjanster$: Observable<Tjanst[]> = this.employeeFormService.fetchTjanster$(); tjanster$: Observable<Tjanst[]> = this.employeeFormService.fetchTjanster$();
availableRoles: Role[] = this.roleService.allRoles; availableRoles: Role[] = this.roleService.allRoles;
isLoadingUtforandeVerksamheter$: Observable<boolean>; isLoadingUtforandeVerksamheter$: Observable<boolean>;
constructor( constructor(
private employeeService: EmployeeService, private administrationService: AdministrationService,
private roleService: RoleService, private roleService: RoleService,
private employeeFormService: EmployeeFormService, private employeeFormService: EmployeeFormService,
private utforandeVerksamheterService: UtforandeVerksamheterService, private utforandeVerksamheterService: UtforandeVerksamheterService,
@@ -49,11 +49,11 @@ export class EmployeeFormComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.employeeService.setCurrentEmployeeId(this.employeeId); this.administrationService.setCurrentEmployeeId(this.employeeId);
} }
updateEmployee(employeeFormData: EmployeeEditRequest): void { updateEmployee(employeeFormData: EmployeeEditRequest): void {
const updateEmployeeSubscription = this.employeeService const updateEmployeeSubscription = this.administrationService
.updateEmployee$(this.employeeId, employeeFormData) .updateEmployee$(this.employeeId, employeeFormData)
.subscribe({ .subscribe({
next: () => { next: () => {
@@ -78,7 +78,7 @@ export class EmployeeFormComponent implements OnInit {
); );
} }
setEmployeeToDelete(employee: Employee): void { setEmployeeToDelete(employee: Employee): void {
this.employeeService.setEmployeeToDelete(employee); this.administrationService.setEmployeeToDelete(employee);
} }
closeError(): void { closeError(): void {

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model'; import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
import { CustomError } from '@msfa-models/error/custom-error'; import { CustomError } from '@msfa-models/error/custom-error';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { AdministrationService } from '../../administration.service';
import { CommaSeparatedEmailValidator } from '@msfa-utils/validators/email.validator'; import { CommaSeparatedEmailValidator } from '@msfa-utils/validators/email.validator';
import { RequiredValidator } from '@msfa-utils/validators/required.validator'; import { RequiredValidator } from '@msfa-utils/validators/required.validator';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
@@ -21,7 +21,7 @@ export class EmployeeInviteComponent {
lastInvites$: Observable<EmployeeInviteResponse> = this._lastInvites$.asObservable(); lastInvites$: Observable<EmployeeInviteResponse> = this._lastInvites$.asObservable();
submitIsLoading$ = new BehaviorSubject<boolean>(false); submitIsLoading$ = new BehaviorSubject<boolean>(false);
constructor(private employeeService: EmployeeService) {} constructor(private administrationService: AdministrationService) {}
get emailsControl(): AbstractControl { get emailsControl(): AbstractControl {
return this.formGroup.get('emails'); return this.formGroup.get('emails');
@@ -93,7 +93,7 @@ export class EmployeeInviteComponent {
return; return;
} }
const post = this.employeeService.postEmployeeInvitation(this.emailsControlValueAsArray).subscribe({ const post = this.administrationService.postEmployeeInvitation$(this.emailsControlValueAsArray).subscribe({
next: data => { next: data => {
this.submitIsLoading$.next(false); this.submitIsLoading$.next(false);
this._lastInvites$.next(data); this._lastInvites$.next(data);

View File

@@ -3,7 +3,7 @@ import { UiIconType } from '@ui/icon/icon-type.enum';
import { EmployeeCompactResponse } from '@msfa-models/api/employee.response.model'; import { EmployeeCompactResponse } from '@msfa-models/api/employee.response.model';
import { Employee, EmployeesData } from '@msfa-models/employee.model'; import { Employee, EmployeesData } from '@msfa-models/employee.model';
import { Sort } from '@msfa-models/sort.model'; import { Sort } from '@msfa-models/sort.model';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { AdministrationService } from '../../administration.service';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { UiLinkButtonType } from '@ui/link-button/link-button-type.enum'; import { UiLinkButtonType } from '@ui/link-button/link-button-type.enum';
@@ -16,16 +16,17 @@ import { UiLinkButtonType } from '@ui/link-button/link-button-type.enum';
export class EmployeesComponent implements OnDestroy { export class EmployeesComponent implements OnDestroy {
UiLinkButtonType = UiLinkButtonType; UiLinkButtonType = UiLinkButtonType;
private _searchValue$ = new BehaviorSubject<string>(''); private _searchValue$ = new BehaviorSubject<string>('');
onlyEmployeesWithoutAuthorization$: Observable<boolean> = this.employeeService.onlyEmployeesWithoutAuthorization$; onlyEmployeesWithoutAuthorization$: Observable<boolean> = this.administrationService
employeesData$: Observable<EmployeesData> = this.employeeService.employeesData$; .onlyEmployeesWithoutAuthorization$;
employeesLoading$: Observable<boolean> = this.employeeService.employeesLoading$; employeesData$: Observable<EmployeesData> = this.administrationService.employeesData$;
sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this.employeeService.sort$; employeesLoading$: Observable<boolean> = this.administrationService.employeesLoading$;
sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this.administrationService.sort$;
iconType = UiIconType; iconType = UiIconType;
constructor(private employeeService: EmployeeService) {} constructor(private administrationService: AdministrationService) {}
ngOnDestroy(): void { ngOnDestroy(): void {
this.employeeService.resetParams(); this.administrationService.resetParams();
} }
get searchValue(): string { get searchValue(): string {
@@ -33,7 +34,7 @@ export class EmployeesComponent implements OnDestroy {
} }
setSearchString(): void { setSearchString(): void {
this.employeeService.setSearchString(this.searchValue); this.administrationService.setSearchString(this.searchValue);
} }
setSearchValue($event: CustomEvent<{ target: { value: string } }>): void { setSearchValue($event: CustomEvent<{ target: { value: string } }>): void {
@@ -41,18 +42,18 @@ export class EmployeesComponent implements OnDestroy {
} }
handleEmployeesSort(key: keyof EmployeeCompactResponse): void { handleEmployeesSort(key: keyof EmployeeCompactResponse): void {
this.employeeService.setSort(key); this.administrationService.setSort(key);
} }
setNewPage(page: number): void { setNewPage(page: number): void {
this.employeeService.setPage(page); this.administrationService.setPage(page);
} }
setOnlyEmployeesWithoutAuthorization(checked: boolean): void { setOnlyEmployeesWithoutAuthorization(checked: boolean): void {
this.employeeService.setOnlyEmployeesWithoutAuthorization(checked); this.administrationService.setOnlyEmployeesWithoutAuthorization(checked);
} }
setEmployeeToDelete(employee: Employee): void { setEmployeeToDelete(employee: Employee): void {
this.employeeService.setEmployeeToDelete(employee); this.administrationService.setEmployeeToDelete(employee);
} }
} }

View File

@@ -85,17 +85,7 @@
Läs mer information här Läs mer information här
</digi-button> </digi-button>
<digi-ng-dialog <ng-template #informationDialog>
class="avrop-dialog"
[afActive]="displayAvropDialog$ | async"
(afOnInactive)="closeAvropDialog()"
(afOnPrimaryClick)="closeAvropDialog()"
afHeading="Information"
afHeadingLevel="h2"
afPrimaryButtonText="Stäng"
afSecondaryButtonText=""
>
<h3>Genomförandereferens</h3>
<p> <p>
Genomförandereferens är det referensnummer du ska använda dig av i kontakten med Arbetsförmedlingen. Genomförandereferens är det referensnummer du ska använda dig av i kontakten med Arbetsförmedlingen.
Du kan också använda genomförandereferensen till att leta fram en order i leverantörsportalen. Du kan också använda genomförandereferensen till att leta fram en order i leverantörsportalen.
@@ -117,7 +107,8 @@
språkstöd som ingår i upphandlingen av olika tjänster och utbildningar. Du hittar mer information om språkstöd som ingår i upphandlingen av olika tjänster och utbildningar. Du hittar mer information om
språkstöd och tolk i förfrågningsunderlaget för specifik upphandling. språkstöd och tolk i förfrågningsunderlaget för specifik upphandling.
</p> </p>
</digi-ng-dialog> <!-- </ui-dialog-layout>-->
</ng-template>
</div> </div>
<msfa-avrop-list <msfa-avrop-list
[availableAvrop]="avropData.data" [availableAvrop]="avropData.data"

View File

@@ -3,6 +3,8 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { AvropComponent } from './avrop.component'; import { AvropComponent } from './avrop.component';
import { AvropModule } from './avrop.module';
import { ApmModule } from '@elastic/apm-rum-angular';
describe('AvropComponent', () => { describe('AvropComponent', () => {
let component: AvropComponent; let component: AvropComponent;
@@ -12,7 +14,7 @@ describe('AvropComponent', () => {
waitForAsync(() => { waitForAsync(() => {
void TestBed.configureTestingModule({ void TestBed.configureTestingModule({
declarations: [AvropComponent], declarations: [AvropComponent],
imports: [RouterTestingModule, HttpClientTestingModule], imports: [AvropModule, RouterTestingModule, HttpClientTestingModule, ApmModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents(); }).compileComponents();
}) })

View File

@@ -1,8 +1,10 @@
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
import { Avrop, AvropAndMeta } from '@msfa-models/avrop.model'; import { Avrop, AvropAndMeta } 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';
import { BehaviorSubject, Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { UiDialog } from '@ui/dialog/ui-dialog.service';
import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
@Component({ @Component({
selector: 'msfa-avrop', selector: 'msfa-avrop',
@@ -11,6 +13,8 @@ import { BehaviorSubject, Observable } from 'rxjs';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AvropComponent implements OnDestroy { export class AvropComponent implements OnDestroy {
@ViewChild('informationDialog') informationDialog: TemplateRef<unknown>;
uiDialogRef: UiDialogRef;
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$;
@@ -23,9 +27,8 @@ export class AvropComponent implements OnDestroy {
avropIsSubmitted$: Observable<boolean> = this.avropService.avropIsSubmitted$; avropIsSubmitted$: Observable<boolean> = this.avropService.avropIsSubmitted$;
avropLoading$: Observable<boolean> = this.avropService.avropLoading$; avropLoading$: Observable<boolean> = this.avropService.avropLoading$;
showUnauthorizedError$: Observable<boolean> = this.avropService.showUnauthorizedError$; showUnauthorizedError$: Observable<boolean> = this.avropService.showUnauthorizedError$;
displayAvropDialog$ = new BehaviorSubject(false);
constructor(private avropService: AvropService) {} constructor(private avropService: AvropService, private uiDialog: UiDialog) {}
ngOnDestroy(): void { ngOnDestroy(): void {
this.returnToStep1(); this.returnToStep1();
@@ -76,10 +79,6 @@ export class AvropComponent implements OnDestroy {
} }
openAvropDialog(): void { openAvropDialog(): void {
this.displayAvropDialog$.next(true); this.uiDialogRef = this.uiDialog.open(this.informationDialog, { heading: 'Information', includeBasicFooter: true });
}
closeAvropDialog(): void {
this.displayAvropDialog$.next(false);
} }
} }

View File

@@ -9,7 +9,7 @@ import { UiSkeletonModule } from '@ui/skeleton/skeleton.module';
import { AvropComponent } from './avrop.component'; import { AvropComponent } from './avrop.component';
import { AvropFiltersModule } from './components/avrop-filters/avrop-filters.module'; import { AvropFiltersModule } from './components/avrop-filters/avrop-filters.module';
import { AvropListModule } from './components/avrop-list/avrop-list.module'; import { AvropListModule } from './components/avrop-list/avrop-list.module';
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog'; import { UiDialogModule } from '@ui/dialog/ui-dialog.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -24,7 +24,7 @@ import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
UiLoaderModule, UiLoaderModule,
HandledarePickerFormModule, HandledarePickerFormModule,
UnauthorizedAlertModule, UnauthorizedAlertModule,
DigiNgDialogModule, UiDialogModule,
], ],
}) })
export class AvropModule {} export class AvropModule {}

View File

@@ -0,0 +1,30 @@
<ui-dialog-layout [isLoading]="submitIsLoading$ | async">
<h2 uiDialogHeading>Vill du skicka in Avvikelserapport (avvikelse)</h2>
<msfa-report-description-list [avrop]="data.avrop">
<dt>Orsak till avvikelse:</dt>
<dd>{{data.chosenReason.name }}</dd>
<ng-container *ngFor="let question of data.avvikelseSubmitData.avvikelseAlternativ.frageformular">
<dd>{{question.svar.length === 0 ? 'Inget svar' : question.svar }}</dd>
</ng-container>
<dt>Dag för avvikelse:</dt>
<dd>{{data.avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
</msfa-report-description-list>
<digi-notification-alert
*ngIf="submitError$ | async as error"
class="franvaro-report-form__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Avvikelserapport (frånvaro). Ladda om sidan och försök igen.</p>
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>
</digi-notification-alert>
<ng-container uiDialogFooter>
<digi-button af-type="button" (click)="submitAndCloseConfirmDialog(data.avvikelseSubmitData)"
>Skicka in</digi-button
>
<digi-button af-type="button" af-variation="secondary" (click)="close()"> Avbryt </digi-button>
</ng-container>
</ui-dialog-layout>

View File

@@ -0,0 +1,26 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AvvikelseConfirmDialogComponent } from './avvikelse-confirm-dialog.component';
import { AvvikelseReportFormModule } from '../avvikelse-report-form.module';
xdescribe('AvvikelseConfirmDialogComponent', () => {
let component: AvvikelseConfirmDialogComponent;
let fixture: ComponentFixture<AvvikelseConfirmDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AvvikelseConfirmDialogComponent],
imports: [AvvikelseReportFormModule],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AvvikelseConfirmDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,49 @@
import { Component } from '@angular/core';
import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
import { DeltagareAvrop } from '@msfa-models/avrop.model';
import { AvvikelseReason } from '@msfa-models/avvikelse-reason.model';
import { AvvikelseReportRequest } from '@msfa-models/api/avvikelse-request.model';
import { AvvikelseReportFormService } from '../avvikelse-report-form.service';
import { CustomError } from '@msfa-models/error/custom-error';
import { BehaviorSubject } from 'rxjs';
export interface AvvikelseConfirmDialogData {
avrop: DeltagareAvrop;
chosenReason: AvvikelseReason;
avvikelseSubmitData: AvvikelseReportRequest;
}
@Component({
selector: 'msfa-avvikelse-confirm-dialog',
templateUrl: './avvikelse-confirm-dialog.component.html',
styleUrls: ['./avvikelse-confirm-dialog.component.scss'],
})
export class AvvikelseConfirmDialogComponent {
submitIsLoading$ = new BehaviorSubject<boolean>(false);
submitError$ = new BehaviorSubject<CustomError>(null);
constructor(public uiDialogRef: UiDialogRef, private avvikelseReportFormService: AvvikelseReportFormService) {}
get data(): AvvikelseConfirmDialogData {
return this.uiDialogRef.config.data as AvvikelseConfirmDialogData;
}
close(): void {
this.uiDialogRef.close();
}
submitAndCloseConfirmDialog(avvikelseSubmitData: AvvikelseReportRequest): void {
this.submitIsLoading$.next(true);
this.avvikelseReportFormService.createAvvikelse$(avvikelseSubmitData).subscribe({
next: () => {
this.submitIsLoading$.next(false);
this.uiDialogRef.close({ submitted: new Date() });
},
error: (customError: CustomError) => {
this.submitError$.next({ ...customError, message: customError.error.message });
this.submitIsLoading$.next(false);
throw { ...customError, avoidToast: true };
},
});
}
}

View File

@@ -33,7 +33,11 @@
</div> </div>
<ng-template #formRef> <ng-template #formRef>
<form class="avvikelse-report-form__form" [formGroup]="avvikelseFormGroup" (ngSubmit)="openConfirmDialog()"> <form
class="avvikelse-report-form__form"
[formGroup]="avvikelseFormGroup"
(ngSubmit)="openConfirmDialog(avrop)"
>
<div class="avvikelse-report-form__form-item"> <div class="avvikelse-report-form__form-item">
<ui-select <ui-select
*ngIf="reasonsAsUiSelectOptions$ | async as reason; else loadingRef" *ngIf="reasonsAsUiSelectOptions$ | async as reason; else loadingRef"
@@ -90,40 +94,15 @@
</ui-link-button> </ui-link-button>
</div> </div>
</form> </form>
<digi-ng-dialog
[afActive]="confirmDialogIsOpen$ | async" <digi-notification-alert
(afOnPrimaryClick)="submitAndCloseConfirmDialog()" *ngIf="submitError$ | async as error"
(afOnInactive)="cancelConfirmDialog()" af-variation="danger"
afHeadingLevel="h2" af-heading="Någonting gick fel"
afPrimaryButtonText="Skicka in"
afSecondaryButtonText="Avbryt"
(afOnSecondaryClick)="cancelConfirmDialog()"
afHeading="Vill du skicka in Avvikelserapport (avvikelse)"
afAriaLabel="Förhandsgranska och skicka in Avvikelserapport (avvikelse)"
id="confirmAvvikelserapport"
> >
<ui-loader *ngIf="submitIsLoading$ | async" uiType="absolute"></ui-loader> <p>Kunde inte spara Avvikelserapport (avvikelse). Försök igen om en stund.</p>
<msfa-report-description-list [avrop]="avrop"> <p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
<dt>Orsak till avvikelse:</dt> </digi-notification-alert>
<dd>{{(chosenReason$ | async)?.name }}</dd>
<ng-container *ngIf="avvikelseSubmitData$ | async as avvikelseSubmitData; else loadingRef">
<ng-container *ngFor="let question of avvikelseSubmitData.avvikelseAlternativ.frageformular">
<dt>{{getCurrentQuestionFromId(question.fraga).name}}</dt>
<dd>{{question.svar.length === 0 ? 'Inget svar' : question.svar }}</dd>
</ng-container>
<dt>Dag för avvikelse:</dt>
<dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
</ng-container>
</msfa-report-description-list>
<digi-notification-alert
*ngIf="submitError$ | async as error"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Avvikelserapport (avvikelse). Ladda om sidan och försök igen.</p>
<p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
</digi-notification-alert>
</digi-ng-dialog>
</ng-template> </ng-template>
</ng-template> </ng-template>
</div> </div>

View File

@@ -1,13 +1,12 @@
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { LayoutComponent } from '@msfa-shared/components/layout/layout.component'; import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
import { AvvikelseReportFormComponent } from './avvikelse-report-form.component'; import { AvvikelseReportFormComponent } from './avvikelse-report-form.component';
import { AvvikelseReportFormService } from './avvikelse-report-form.service'; import { AvvikelseReportFormService } from './avvikelse-report-form.service';
import { ApmModule } from '@elastic/apm-rum-angular'; import { ApmModule } from '@elastic/apm-rum-angular';
import { AvvikelseReportFormModule } from './avvikelse-report-form.module';
describe('AvvikelseReportFormComponent', () => { describe('AvvikelseReportFormComponent', () => {
let component: AvvikelseReportFormComponent; let component: AvvikelseReportFormComponent;
@@ -17,13 +16,7 @@ describe('AvvikelseReportFormComponent', () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvvikelseReportFormComponent, LayoutComponent], declarations: [AvvikelseReportFormComponent, LayoutComponent],
imports: [ imports: [AvvikelseReportFormModule, RouterTestingModule, HttpClientTestingModule, ApmModule],
RouterTestingModule,
HttpClientTestingModule,
ReactiveFormsModule,
DigiNgFormDatepickerModule,
ApmModule,
],
providers: [AvvikelseReportFormService], providers: [AvvikelseReportFormService],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -12,8 +12,14 @@ import { RegexValidator } from '@msfa-utils/validators/regex.validator';
import { RequiredValidator } from '@msfa-validators/required.validator'; import { RequiredValidator } from '@msfa-validators/required.validator';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators'; import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { AvvikelseReportFormService } from './avvikelse-report-form.service'; import { AvvikelseReportFormService } from './avvikelse-report-form.service';
import { UiDialog } from '@ui/dialog/ui-dialog.service';
import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
import {
AvvikelseConfirmDialogComponent,
AvvikelseConfirmDialogData,
} from './avvikelse-confirm-dialog/avvikelse-confirm-dialog.component';
interface Params { interface Params {
genomforandeReferens: string; genomforandeReferens: string;
@@ -34,20 +40,22 @@ type AvvikelseFormKeys = keyof AvvikelseFormData;
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AvvikelseReportFormComponent implements OnInit, OnDestroy { export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
confirmDialogRef: UiDialogRef;
shouldValidate$ = new BehaviorSubject<boolean>(false); shouldValidate$ = new BehaviorSubject<boolean>(false);
reasonFormName: AvvikelseFormKeys = 'reason'; reasonFormName: AvvikelseFormKeys = 'reason';
questionsFormName: AvvikelseFormKeys = 'questions'; questionsFormName: AvvikelseFormKeys = 'questions';
reportingDateFormName: AvvikelseFormKeys = 'reportingDate'; reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
submitIsLoading$ = new BehaviorSubject<boolean>(false);
submitError$ = new BehaviorSubject<CustomError>(null); submitError$ = new BehaviorSubject<CustomError>(null);
genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe( genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
map((params: Params) => +params.genomforandeReferens) map((params: Params) => +params.genomforandeReferens)
); );
avrop: DeltagareAvrop;
avrop$: Observable<DeltagareAvrop> = this.genomforandeReferens$.pipe( avrop$: Observable<DeltagareAvrop> = this.genomforandeReferens$.pipe(
switchMap(genomforandeReferens => this.avvikelseReportFormService.fetchAvropInformation$(genomforandeReferens)), switchMap(genomforandeReferens => this.avvikelseReportFormService.fetchAvropInformation$(genomforandeReferens)),
tap(avrop => (this.avrop = avrop)),
shareReplay(1) shareReplay(1)
); );
@@ -60,8 +68,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
chosenReasonId$: Observable<string>; chosenReasonId$: Observable<string>;
chosenReason$: Observable<AvvikelseReason>; chosenReason$: Observable<AvvikelseReason>;
questionsForChosenReason$: Observable<AvvikelseQuestion[]>; questionsForChosenReason$: Observable<AvvikelseQuestion[]>;
avvikelseSubmitData$: Observable<AvvikelseReportRequest>;
confirmDialogIsOpen$ = new BehaviorSubject<boolean>(false);
submittedDate$ = new BehaviorSubject<Date | null>(null); submittedDate$ = new BehaviorSubject<Date | null>(null);
private subscriptions: Subscription[] = []; private subscriptions: Subscription[] = [];
private todayDateISO = new Date().toISOString().slice(0, 10); private todayDateISO = new Date().toISOString().slice(0, 10);
@@ -70,11 +76,19 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
[this.reportingDateFormName]: new FormControl(this.todayDateISO, [RequiredValidator('Datum är obligatoriskt')]), [this.reportingDateFormName]: new FormControl(this.todayDateISO, [RequiredValidator('Datum är obligatoriskt')]),
[this.questionsFormName]: new FormArray([]), [this.questionsFormName]: new FormArray([]),
}); });
private formData$: Observable<AvvikelseFormData> = this.avvikelseFormGroup
.valueChanges as Observable<AvvikelseFormData>;
private currentQuestions: AvvikelseQuestion[]; private currentQuestions: AvvikelseQuestion[];
constructor(private avvikelseReportFormService: AvvikelseReportFormService, private activatedRoute: ActivatedRoute) {} constructor(
private avvikelseReportFormService: AvvikelseReportFormService,
private activatedRoute: ActivatedRoute,
private uiDialog: UiDialog
) {
this.chosenReasonId$ = this.reasonFormControl.valueChanges as Observable<string>;
this.chosenReason$ = combineLatest([this.chosenReasonId$, this.reasons$]).pipe(
map(([chosenReasonId, reasons]) => reasons.find(reason => reason.id.toString() === chosenReasonId)),
shareReplay(1)
);
}
get reasonFormControl(): AbstractControl | undefined { get reasonFormControl(): AbstractControl | undefined {
return this.avvikelseFormGroup.get(this.reasonFormName); return this.avvikelseFormGroup.get(this.reasonFormName);
@@ -93,11 +107,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.chosenReasonId$ = this.reasonFormControl.valueChanges as Observable<string>;
this.chosenReason$ = combineLatest([this.chosenReasonId$, this.reasons$]).pipe(
map(([chosenReasonId, reasons]) => reasons.find(reason => reason.id.toString() === chosenReasonId))
);
this.questionsForChosenReason$ = combineLatest([this.chosenReasonId$, this.allAvvikelseQuestions$]).pipe( this.questionsForChosenReason$ = combineLatest([this.chosenReasonId$, this.allAvvikelseQuestions$]).pipe(
map(([chosenOrsak, allAvvikelseQuestions]) => { map(([chosenOrsak, allAvvikelseQuestions]) => {
return allAvvikelseQuestions.filter(question => question.id.startsWith(chosenOrsak.toString() + '_')); return allAvvikelseQuestions.filter(question => question.id.startsWith(chosenOrsak.toString() + '_'));
@@ -105,21 +114,15 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
); );
this.subscriptions.push( this.subscriptions.push(
this.chosenReason$.subscribe(() => { this.chosenReason$.subscribe(chosenReason => {
this.shouldValidate$.next(false); this.shouldValidate$.next(false);
}), }),
this.questionsForChosenReason$.subscribe(questions => { this.questionsForChosenReason$.subscribe(questions => {
this.clearQuestions(); this.clearQuestions();
questions.forEach(question => this.addQuestionToForm(question)); questions.forEach(question => this.addQuestionToForm(question));
}) })
); );
this.avvikelseSubmitData$ = combineLatest([this.genomforandeReferens$, this.chosenReasonId$, this.formData$]).pipe(
map(([genomforandeReferens, chosenReason, formData]) =>
this.makeAvvikelseSubmitData(genomforandeReferens, chosenReason, formData)
),
shareReplay(1)
);
} }
questionIsRequired(question: AvvikelseQuestion): boolean { questionIsRequired(question: AvvikelseQuestion): boolean {
@@ -138,6 +141,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
private _isAfterStartDate(startDate: Date): boolean { private _isAfterStartDate(startDate: Date): boolean {
return new Date() > startDate; return new Date() > startDate;
} }
private _isBeforeLastPossibleReportDay(endDate: Date): boolean { private _isBeforeLastPossibleReportDay(endDate: Date): boolean {
// Reporting is allowed at latest 5 days past avrop end date. // Reporting is allowed at latest 5 days past avrop end date.
// Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future // Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future
@@ -158,44 +162,41 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
} }
} }
openConfirmDialog(): void { openConfirmDialog(avrop: DeltagareAvrop): void {
this.shouldValidate$.next(true); this.shouldValidate$.next(true);
markControlsAsDirty(Object.values(this.avvikelseFormGroup.controls)); markControlsAsDirty(Object.values(this.avvikelseFormGroup.controls));
this.avvikelseFormGroup.markAllAsTouched(); this.avvikelseFormGroup.markAllAsTouched();
if (this.avvikelseFormGroup.valid) { if (this.avvikelseFormGroup.valid) {
this.confirmDialogIsOpen$.next(true); combineLatest([this.chosenReason$, this.genomforandeReferens$])
.pipe(
first(),
switchMap(([chosenReason, genomforandeReferens]) => {
const avvikelseSubmitData: AvvikelseReportRequest = this._makeAvvikelseSubmitData(
genomforandeReferens,
chosenReason.id.toString(),
this.avvikelseFormGroup.value as AvvikelseFormData
);
const data: AvvikelseConfirmDialogData = { chosenReason, avvikelseSubmitData, avrop };
return this.uiDialog.open<{ submitted: Date }>(AvvikelseConfirmDialogComponent, { data }).afterClosed$;
})
)
.subscribe(closedResult => {
this.submitError$.next(null);
if (closedResult.data?.submitted) {
this.submittedDate$.next(closedResult.data.submitted);
}
});
} }
} }
submitAndCloseConfirmDialog(): void {
this.submitIsLoading$.next(true);
this.avvikelseSubmitData$.pipe(take(1)).subscribe(avvikelseSubmitData =>
this.avvikelseReportFormService.createAvvikelse$(avvikelseSubmitData).subscribe({
next: () => {
this.submitIsLoading$.next(false);
this.submittedDate$.next(new Date());
this.confirmDialogIsOpen$.next(false);
},
error: (customError: CustomError) => {
this.submitError$.next({ ...customError, message: customError.error.message });
this.submitIsLoading$.next(false);
throw { ...customError, avoidToast: true };
},
})
);
}
cancelConfirmDialog(): void {
this.confirmDialogIsOpen$.next(false);
this.submitError$.next(null);
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscriptions.forEach(subscription => subscription.unsubscribe()); this.subscriptions.forEach(subscription => subscription.unsubscribe());
} }
private makeAvvikelseSubmitData( private _makeAvvikelseSubmitData(
genomforandeReferens: number, genomforandeReferens: number,
chosenReason: string, chosenReason: string,
formData: AvvikelseFormData formData: AvvikelseFormData
@@ -219,6 +220,8 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
private addQuestionToForm(question: AvvikelseQuestion): void { private addQuestionToForm(question: AvvikelseQuestion): void {
// FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit // FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit
// TODO we can actually just put id in the formgroup, as we do in slutredovisning-form-step1.component.ts. That would simplify this file.
this.currentQuestions.push(question); this.currentQuestions.push(question);
this.questionsFormArray.push( this.questionsFormArray.push(

View File

@@ -1,4 +1,3 @@
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker'; import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@@ -17,10 +16,12 @@ import { ReportDescriptionListModule } from '../../../components/report-descript
import { ReportLayoutModule } from '../../../components/report-layout/report-layout.module'; import { ReportLayoutModule } from '../../../components/report-layout/report-layout.module';
import { AvvikelseReportFormComponent } from './avvikelse-report-form.component'; import { AvvikelseReportFormComponent } from './avvikelse-report-form.component';
import { AvvikelseReportFormService } from './avvikelse-report-form.service'; import { AvvikelseReportFormService } from './avvikelse-report-form.service';
import { AvvikelseConfirmDialogComponent } from './avvikelse-confirm-dialog/avvikelse-confirm-dialog.component';
import { UiDialogModule } from '@ui/dialog/ui-dialog.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvvikelseReportFormComponent], declarations: [AvvikelseReportFormComponent, AvvikelseConfirmDialogComponent],
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: AvvikelseReportFormComponent }]), RouterModule.forChild([{ path: '', component: AvvikelseReportFormComponent }]),
@@ -34,10 +35,10 @@ import { AvvikelseReportFormService } from './avvikelse-report-form.service';
UiLoaderModule, UiLoaderModule,
UiSelectModule, UiSelectModule,
ReportDescriptionListModule, ReportDescriptionListModule,
DigiNgDialogModule,
UiTextareaModule, UiTextareaModule,
UiLinkButtonModule, UiLinkButtonModule,
PreventDoubleSubmitModule, PreventDoubleSubmitModule,
UiDialogModule,
], ],
providers: [AvvikelseReportFormService], providers: [AvvikelseReportFormService],
exports: [AvvikelseReportFormComponent], exports: [AvvikelseReportFormComponent],

View File

@@ -154,9 +154,9 @@ export class GemensamPlaneringFormComponent {
return new Date() > startDate; return new Date() > startDate;
} }
private _isBeforeLastPossibleReportDay(endDate: Date): boolean { private _isBeforeLastPossibleReportDay(endDate: Date): boolean {
// Reporting is allowed at latest 5 days past avrop end date. // Reporting is allowed at latest 60 working days past avrop end date. To calculate calendar days we remove weekends like this 60*7/5=84
// Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future // Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future
const lastPossibleReportDay = addDays(endDate, 15); const lastPossibleReportDay = addDays(endDate, 84);
return lastPossibleReportDay > new Date(); return lastPossibleReportDay > new Date();
} }

View File

@@ -21,35 +21,27 @@
</div> </div>
<ng-template #formRef> <ng-template #formRef>
<h2>Har den sökande fått arbete eller påbörjat studier?</h2>
<p>Ange sysselsättning (obligatoriskt)</p>
<form class="signal-form__form" [formGroup]="signalFormGroup" id="signal-form" (ngSubmit)="openConfirmDialog()"> <form class="signal-form__form" [formGroup]="signalFormGroup" id="signal-form" (ngSubmit)="openConfirmDialog()">
<ui-select <digi-form-fieldset af-name="type" af-form="signal-form">
[formControl]="typeFormControl"
uiLabel="Typ av sysselsättning"
uiPlaceholder="Välj typ av sysselsättning"
[uiOptions]="typeSelectItems"
[uiRequired]="true"
[uiAnnounceIfOptional]="true"
[uiInvalid]="formControlIsInvalid(typeFormName)"
[uiValidationMessage]="formErrors.type"
></ui-select>
<digi-form-fieldset af-legend="Omfattning" af-name="omfattning" af-form="signal-form">
<ui-radiobutton-group <ui-radiobutton-group
[formControl]="omfattningFormControl" [formControl]="typeFormControl"
[uiRequired]="true" [uiRequired]="true"
[uiAnnounceIfOptional]="true" [uiAnnounceIfOptional]="true"
[uiRadiobuttons]="omfattningRadioButtons" [uiRadiobuttons]="typeRadioButtons"
[uiInvalid]="formControlIsInvalid(omfattningFormName)" [uiInvalid]="formControlIsInvalid(typeFormName)"
[uiValidationMessage]="formErrors.omfattning" [uiValidationMessage]="formErrors.type"
></ui-radiobutton-group> ></ui-radiobutton-group>
</digi-form-fieldset> </digi-form-fieldset>
<ui-input <ui-input
*ngIf="showPercentFormControl" *ngIf="isDeltid"
class="signal-form__number-input" class="signal-form__number-input"
[formControl]="percentFormControl" [formControl]="percentFormControl"
uiType="number" uiType="number"
[uiMin]="1" [uiMin]="1"
[uiMax]="99" [uiMax]="99"
uiLabel="Antal procent vid deltid" uiLabel="Ange omfattning i procent"
[uiRequired]="true" [uiRequired]="true"
[uiAnnounceIfOptional]="true" [uiAnnounceIfOptional]="true"
[uiInvalid]="formControlIsInvalid(percentFormName)" [uiInvalid]="formControlIsInvalid(percentFormName)"
@@ -59,7 +51,7 @@
<div class="signal-form__form-item"> <div class="signal-form__form-item">
<digi-ng-form-datepicker <digi-ng-form-datepicker
[formControl]="startDateFormControl" [formControl]="startDateFormControl"
[afLabel]="'Startdatum för ' + (typeFormControl.value || 'arbete/utbildning')" [afLabel]="datePickerLabel"
[afDisableValidStyle]="true" [afDisableValidStyle]="true"
[afRequired]="true" [afRequired]="true"
[afInvalid]="formControlIsInvalid(startDateFormName)" [afInvalid]="formControlIsInvalid(startDateFormName)"
@@ -97,12 +89,10 @@
> >
<ui-loader *ngIf="submitLoading$ | async" uiType="absolute"></ui-loader> <ui-loader *ngIf="submitLoading$ | async" uiType="absolute"></ui-loader>
<msfa-report-description-list [avrop]="avrop"> <msfa-report-description-list [avrop]="avrop">
<dt>Typ av sysselsättning</dt> <dt>Sysselsättning</dt>
<dd>{{convertTypeValueToLabel(typeFormControl.value)}}</dd> <dd>{{convertTypeValueToLabel(typeFormControl.value)}}</dd>
<dt>Omfattning</dt> <ng-container *ngIf="isDeltid">
<dd>{{convertOmfattningValueToLabel(omfattningFormControl.value)}}</dd> <dt>Omfattning i procent</dt>
<ng-container *ngIf="showPercentFormControl">
<dt>Antal procent vid deltid</dt>
<dd>{{percentFormControl.value}}%</dd> <dd>{{percentFormControl.value}}%</dd>
</ng-container> </ng-container>
<dt>Startdatum</dt> <dt>Startdatum</dt>

View File

@@ -4,6 +4,10 @@
.signal-form { .signal-form {
max-width: var(--digi--typography--text--max-width); max-width: var(--digi--typography--text--max-width);
h2 {
margin-bottom: var(--digi--layout--gutter--s);
}
&__confirmation, &__confirmation,
&__warning, &__warning,
&__form { &__form {

View File

@@ -1,4 +1,3 @@
import { SelectOption } from '@ui/select/select-option.model';
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
@@ -10,7 +9,7 @@ import { Radiobutton } from '@ui/radiobutton-group/radiobutton.model';
import { add } from 'date-fns'; import { add } from 'date-fns';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators'; import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { SignalFormData, SignalFormKeys, SignalOmfattning, SignalType } from './signal-form.model'; import { SignalFormData, SignalFormKeys, SignalType } from './signal-form.model';
import { SignalFormService } from './signal-form.service'; import { SignalFormService } from './signal-form.service';
import { SignalFormValidator } from './signal-form.validator'; import { SignalFormValidator } from './signal-form.validator';
@@ -22,7 +21,6 @@ import { SignalFormValidator } from './signal-form.validator';
}) })
export class SignalFormComponent { export class SignalFormComponent {
readonly typeFormName: SignalFormKeys = 'type'; readonly typeFormName: SignalFormKeys = 'type';
readonly omfattningFormName: SignalFormKeys = 'omfattning';
readonly percentFormName: SignalFormKeys = 'percent'; readonly percentFormName: SignalFormKeys = 'percent';
readonly startDateFormName: SignalFormKeys = 'startDate'; readonly startDateFormName: SignalFormKeys = 'startDate';
@@ -31,7 +29,6 @@ export class SignalFormComponent {
signalFormGroup = new FormGroup( signalFormGroup = new FormGroup(
{ {
type: new FormControl(null), type: new FormControl(null),
omfattning: new FormControl(SignalOmfattning.Heltid),
percent: new FormControl(50), percent: new FormControl(50),
startDate: new FormControl(new Date()), startDate: new FormControl(new Date()),
}, },
@@ -49,23 +46,17 @@ export class SignalFormComponent {
shareReplay(1) shareReplay(1)
); );
typeSelectItems: SelectOption[] = [ typeRadioButtons: Radiobutton[] = [
{ name: 'Arbete', value: SignalType.Arbete }, { label: 'Arbete heltid', value: SignalType.Arbete_Heltid },
{ name: 'Utbildning', value: SignalType.Utbildning }, { label: 'Arbete deltid', value: SignalType.Arbete_Deltid },
{ label: 'Utbildning heltid', value: SignalType.Utbildning_Heltid },
{ label: 'Utbildning deltid', value: SignalType.Utbildning_Deltid },
]; ];
omfattningRadioButtons: Radiobutton[] = [ constructor(private signalFormService: SignalFormService, private activatedRoute: ActivatedRoute) { }
{ label: 'Heltid', value: SignalOmfattning.Heltid },
{ label: 'Deltid', value: SignalOmfattning.Deltid },
];
constructor(private signalFormService: SignalFormService, private activatedRoute: ActivatedRoute) {}
convertTypeValueToLabel(type: SignalType): string { convertTypeValueToLabel(type: SignalType): string {
return this.typeSelectItems?.find(selectItem => selectItem.value === type)?.name; return this.typeRadioButtons?.find(radiobuttons => radiobuttons.value === type)?.label;
}
convertOmfattningValueToLabel(type: SignalOmfattning): string {
return this.omfattningRadioButtons?.find(radiobuttons => radiobuttons.value === type)?.label;
} }
get formErrors(): { [key: string]: string } { get formErrors(): { [key: string]: string } {
@@ -75,9 +66,7 @@ export class SignalFormComponent {
get typeFormControl(): FormControl { get typeFormControl(): FormControl {
return this.signalFormGroup.get(this.typeFormName) as FormControl; return this.signalFormGroup.get(this.typeFormName) as FormControl;
} }
get omfattningFormControl(): FormControl {
return this.signalFormGroup.get(this.omfattningFormName) as FormControl;
}
get percentFormControl(): FormControl { get percentFormControl(): FormControl {
return this.signalFormGroup.get(this.percentFormName) as FormControl; return this.signalFormGroup.get(this.percentFormName) as FormControl;
} }
@@ -85,14 +74,27 @@ export class SignalFormComponent {
return this.signalFormGroup.get(this.startDateFormName) as FormControl; return this.signalFormGroup.get(this.startDateFormName) as FormControl;
} }
get showPercentFormControl(): boolean { get isDeltid(): boolean {
return this.omfattningFormControl.value === SignalOmfattning.Deltid; return this.typeFormControl.value === SignalType.Arbete_Deltid || this.typeFormControl.value === SignalType.Utbildning_Deltid;
} }
get startDateFormValueAsDate(): Date { get startDateFormValueAsDate(): Date {
return new Date(this.startDateFormControl.value); return new Date(this.startDateFormControl.value);
} }
get datePickerLabel(): string {
switch (this.typeFormControl.value) {
case SignalType.Arbete_Heltid:
case SignalType.Arbete_Deltid:
return 'Startdatum för arbete';
case SignalType.Utbildning_Heltid:
case SignalType.Utbildning_Deltid:
return 'Startdatum för utbildning';
default:
return 'Startdatum för arbete/utbildning';
}
}
formControlIsInvalid(formControlName: string): boolean { formControlIsInvalid(formControlName: string): boolean {
return this.formErrors[formControlName] && this.shouldValidate$.getValue(); return this.formErrors[formControlName] && this.shouldValidate$.getValue();
} }
@@ -117,36 +119,32 @@ export class SignalFormComponent {
private typeToRequest(type: string): SignalRequestType { private typeToRequest(type: string): SignalRequestType {
switch (type) { switch (type) {
case SignalType.Arbete: case SignalType.Arbete_Heltid:
return SignalRequestType.Work; return SignalRequestType.Arbete;
case SignalType.Utbildning: case SignalType.Arbete_Deltid:
return SignalRequestType.Education; return SignalRequestType.Arbete;
default: case SignalType.Utbildning_Heltid:
return; return SignalRequestType.Utbildning;
} case SignalType.Utbildning_Deltid:
} return SignalRequestType.Utbildning;
private omfattningToRequest(omfattning: string): SignalRequestOmfattning {
switch (omfattning) {
case 'heltid':
return SignalRequestOmfattning.Heltid;
case 'deltid':
return SignalRequestOmfattning.Deltid;
default: default:
return; return;
} }
} }
private _formDataToRequest(genomforandereferens: number, formData: SignalFormData): SignalRequest { private _formDataToRequest(genomforandereferens: number, formData: SignalFormData): SignalRequest {
const { type, omfattning, startDate, percent } = formData; const { type, startDate, percent } = formData;
const requestType: SignalRequestType = this.typeToRequest(type); const requestType: SignalRequestType = this.typeToRequest(type);
const requestOmfattning: SignalRequestOmfattning = this.omfattningToRequest(omfattning); const requestOmfattning: SignalRequestOmfattning = (this.typeFormControl.value as string).includes(SignalRequestOmfattning.Heltid)
? SignalRequestOmfattning.Heltid
: SignalRequestOmfattning.Deltid;
return { return {
genomforandereferens, genomforandereferens,
type: requestType, type: requestType,
omfattning: requestOmfattning, omfattning: requestOmfattning,
omfattningPercent: requestOmfattning === SignalRequestOmfattning.Deltid ? percent : null, omfattningPercent: requestOmfattning === SignalRequestOmfattning.Deltid ? percent : 100,
startDate: formatDate(startDate), startDate: formatDate(startDate),
}; };
} }

View File

@@ -3,8 +3,10 @@ export enum SignalOmfattning {
Deltid = 'deltid', Deltid = 'deltid',
} }
export enum SignalType { export enum SignalType {
Arbete = 'arbete', Arbete_Heltid = 'arbete_heltid',
Utbildning = 'utbildning', Arbete_Deltid = 'arbete_deltid',
Utbildning_Heltid = 'utbildning_heltid',
Utbildning_Deltid = 'utbildning_deltid',
} }
export interface SignalFormData { export interface SignalFormData {

View File

@@ -64,9 +64,9 @@ export class SlutredovisningFormComponent {
} }
private _isBeforeLastPossibleReportDay(date: Date): boolean { private _isBeforeLastPossibleReportDay(date: Date): boolean {
// Reporting is allowed at latest 60 days past avrop end date. // Reporting is allowed at latest 60 working days past avrop end date. To calculate calendar days we remove weekends like this 60*7/5=84
// Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future // Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future
const lastPossibleReportDay = addDays(date, 100); const lastPossibleReportDay = addDays(date, 84);
return lastPossibleReportDay > new Date(); return lastPossibleReportDay > new Date();
} }

View File

@@ -31,9 +31,9 @@ export class SignalViewComponent {
); );
typeToText(type: SignalResponseType): string { typeToText(type: SignalResponseType): string {
switch (type) { switch (type) {
case SignalResponseType.Work: case SignalResponseType.Arbete:
return 'Arbete'; return 'Arbete';
case SignalResponseType.Education: case SignalResponseType.Utbildning:
return 'Utbildning'; return 'Utbildning';
default: default:
return 'Okänd'; return 'Okänd';

View File

@@ -1,5 +1,3 @@
<h2>Händelser för {{deltagare.fullName}}</h2>
<div class="deltagare-list-handelser" *ngIf="deltagare"> <div class="deltagare-list-handelser" *ngIf="deltagare">
<h3 *ngIf="activeHandelseMotivation" class="deltagare-list-handelser__sub-heading">{{deltagare.fullName}}</h3> <h3 *ngIf="activeHandelseMotivation" class="deltagare-list-handelser__sub-heading">{{deltagare.fullName}}</h3>
<p>Genomförandereferens: <strong>{{deltagare.genomforandeReferens}}</strong></p> <p>Genomförandereferens: <strong>{{deltagare.genomforandeReferens}}</strong></p>

View File

@@ -3,15 +3,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DeltagareListHandelserDialogComponent } from './deltagare-list-handelser-dialog.component'; import { DeltagareListHandelserDialogComponent } from './deltagare-list-handelser-dialog.component';
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { DeltagareListModule } from '../../deltagare-list.module';
describe('DeltagareListHandelserDialogComponent', () => { xdescribe('DeltagareListHandelserDialogComponent', () => {
let component: DeltagareListHandelserDialogComponent; let component: DeltagareListHandelserDialogComponent;
let fixture: ComponentFixture<DeltagareListHandelserDialogComponent>; let fixture: ComponentFixture<DeltagareListHandelserDialogComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [HttpClientTestingModule], imports: [DeltagareListModule, HttpClientTestingModule],
declarations: [DeltagareListHandelserDialogComponent], declarations: [DeltagareListHandelserDialogComponent],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -11,13 +11,13 @@
<thead> <thead>
<tr> <tr>
<th scope="col" class="deltagare-list__column-head" *ngFor="let column of columnHeaders"> <th scope="col" class="deltagare-list__column-head" *ngFor="let column of columnHeaders">
{{column.label}}
<button <button
*ngIf="column.label !== 'Status'" *ngIf="column.key"
class="deltagare-list__sort-button" class="deltagare-list__sort-button"
[attr.id]="'sort-button-' + column.key" [attr.id]="'sort-button-' + column.key"
(click)="handleSort(column.key)" (click)="handleSort(column.key)"
> >
{{column.label}}
<ng-container *ngIf="sort.key === column.key"> <ng-container *ngIf="sort.key === column.key">
<digi-icon-caret-up <digi-icon-caret-up
class="deltagare-list__sort-icon" class="deltagare-list__sort-icon"
@@ -29,6 +29,7 @@
></digi-icon-caret-down> ></digi-icon-caret-down>
</ng-container> </ng-container>
</button> </button>
<span *ngIf="!column.key"> {{column.label}} </span>
</th> </th>
</tr> </tr>
</thead> </thead>

View File

@@ -2,6 +2,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { DeltagareListTableComponent } from './deltagare-list-table.component'; import { DeltagareListTableComponent } from './deltagare-list-table.component';
import { DeltagareListTableModule } from './deltagare-list-table.module';
describe('DeltagareListComponent', () => { describe('DeltagareListComponent', () => {
let component: DeltagareListTableComponent; let component: DeltagareListTableComponent;
@@ -11,7 +12,7 @@ describe('DeltagareListComponent', () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareListTableComponent], declarations: [DeltagareListTableComponent],
imports: [RouterTestingModule], imports: [DeltagareListTableModule, RouterTestingModule],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(DeltagareListTableComponent); fixture = TestBed.createComponent(DeltagareListTableComponent);

View File

@@ -55,7 +55,7 @@ export class DeltagareListTableComponent {
}, },
{ {
label: 'Status', label: 'Status',
key: 'genomforandeReferens', key: null,
}, },
]; ];
@@ -85,7 +85,9 @@ export class DeltagareListTableComponent {
openHandelser(singleDeltagare: DeltagareCompact): void { openHandelser(singleDeltagare: DeltagareCompact): void {
this.uiDialog.open(this.handelserDialogComponent, { this.uiDialog.open(this.handelserDialogComponent, {
data: singleDeltagare, data: singleDeltagare,
heading: 'Händelser för ' + singleDeltagare.fullName,
primaryButtonText: 'Stäng', primaryButtonText: 'Stäng',
includeBasicFooter: true,
}); });
} }

View File

@@ -1,6 +1,6 @@
export enum SignalRequestType { export enum SignalRequestType {
Work = 'work', Arbete = 'arbete',
Education = 'education', Utbildning = 'utbildning',
} }
export enum SignalRequestOmfattning { export enum SignalRequestOmfattning {

View File

@@ -1,6 +1,6 @@
export enum SignalResponseType { export enum SignalResponseType {
Work = 'work', Arbete = 'arbete',
Education = 'education', Utbildning = 'utbildning',
} }
export enum SignalResponseOmfattning { export enum SignalResponseOmfattning {

View File

@@ -0,0 +1,122 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '@msfa-environment';
import { EmployeeParams, Params } from '@msfa-models/api/params.model';
import { EmployeeResponse, EmployeesDataResponse } from '@msfa-models/api/employee.response.model';
import { catchError, map, mapTo, take } from 'rxjs/operators';
import {
Employee,
EmployeeCompact,
mapResponseToEmployee,
mapResponseToEmployeeCompact,
} from '@msfa-models/employee.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { Observable } from 'rxjs';
import { PaginationMeta } from '@msfa-models/pagination-meta.model';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
import { EmployeeEditRequest } from '@msfa-models/api/employee-edit.request.model';
@Injectable({
providedIn: 'root',
})
export class EmployeeApiService {
private _apiBaseUrl = `${environment.api.url}/users`;
constructor(private httpClient: HttpClient) {}
fetchEmployees$(employeeParams: EmployeeParams): Observable<{ data: EmployeeCompact[]; meta: PaginationMeta }> {
const { sort, order, limit, page, search, onlyEmployeesWithoutAuthorization } = employeeParams;
const params: Params = {
sort,
order,
limit: limit.toString(),
page: page.toString(),
};
if (search) {
params.search = search;
}
if (onlyEmployeesWithoutAuthorization) {
params.onlyEmployeesWithoutAuthorization = onlyEmployeesWithoutAuthorization.toString();
}
return this.httpClient
.get<EmployeesDataResponse>(this._apiBaseUrl, { params })
.pipe(
map(({ data, meta }) => {
return { data: data.map(employee => mapResponseToEmployeeCompact(employee)), meta };
}),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte hämta personal.\n\n${error.message}`,
name: `GET ${this._apiBaseUrl}`,
method: 'EmployeeApiService._fetchEmployees$',
});
})
);
}
fetchEmployee$(ciamUserId: string): Observable<Employee> {
const apiUrl = `${this._apiBaseUrl}/${ciamUserId}`;
return this.httpClient.get<{ data: EmployeeResponse }>(apiUrl).pipe(
map(({ data }) => mapResponseToEmployee(data)),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte hämta personal.\n\n${error.message}`,
name: `GET ${this._apiBaseUrl}/{ciamUserId}`,
data: { ciamUserId },
method: 'EmployeeApiService._fetchEmployee$',
});
})
);
}
deleteEmployee(employee: Employee): Observable<Employee> {
return this.httpClient.delete<void>(`${this._apiBaseUrl}/${employee.id}`).pipe(
map(() => employee),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte ta bort personal.\n\n${error.message}`,
name: `DELETE ${this._apiBaseUrl}/{ciamUserId}`,
method: 'EmployeeApiService.deleteEmployee',
});
})
);
}
postEmployeeInvitation(emails: string[]): Observable<EmployeeInviteResponse | null> {
const apiUrl = `${this._apiBaseUrl}/invite`;
return this.httpClient
.patch<{ data: EmployeeInviteResponse }>(apiUrl, { emails })
.pipe(
take(1),
map(({ data }) => data),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte bjuda in personal.\n\n${error.message}`,
name: `PATCH ${apiUrl}`,
data: { emails },
method: 'EmployeeApiService.postEmployeeInvitation',
});
})
);
}
updateEmployee$(ciamUserId: string, data: EmployeeEditRequest): Observable<boolean> {
return this.httpClient.put<boolean>(`${this._apiBaseUrl}/${ciamUserId}`, data).pipe(
mapTo(true),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte redigera personal.\n\n${error.message}`,
name: `PATCH ${this._apiBaseUrl}/{ciamUserId}`,
data: { ciamUserId },
method: 'EmployeeApiService.updateEmployee$',
});
})
);
}
}

View File

@@ -1,236 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { SortOrder } from '@msfa-enums/sort-order.enum';
import { environment } from '@msfa-environment';
import { EmployeeEditRequest } from '@msfa-models/api/employee-edit.request.model';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
import {
EmployeeCompactResponse,
EmployeeResponse,
EmployeesDataResponse,
} from '@msfa-models/api/employee.response.model';
import { EmployeeParams, Params } from '@msfa-models/api/params.model';
import {
Employee,
EmployeesData,
mapResponseToEmployee,
mapResponseToEmployeeCompact,
} from '@msfa-models/employee.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { Sort } from '@msfa-models/sort.model';
import { ErrorService } from '@msfa-services/error.service';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { catchError, distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
const DEFAULT_PARAMS: EmployeeParams = {
page: 1,
limit: 10,
sort: 'name',
order: SortOrder.ASC,
search: '',
onlyEmployeesWithoutAuthorization: false,
};
@Injectable({
providedIn: 'root',
})
export class EmployeeService extends UnsubscribeDirective {
private _apiBaseUrl = `${environment.api.url}/users`;
private _currentEmployeeId$ = new BehaviorSubject<string>(null);
private _params$ = new BehaviorSubject<EmployeeParams>(DEFAULT_PARAMS);
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);
public lastUpdatedEmployeeId$: Observable<string> = this._lastUpdatedEmployeeId$.asObservable();
private _lastDeletedEmployee$ = new BehaviorSubject<Employee>(null);
public lastDeletedEmployee$: Observable<Employee> = this._lastDeletedEmployee$.asObservable();
private _employeeToDelete$ = new BehaviorSubject<Employee>(null);
public employeeToDelete$: Observable<Employee> = this._employeeToDelete$.asObservable();
private _employeesLoading$ = new BehaviorSubject<boolean>(false);
public employeesLoading$: Observable<boolean> = this._employeesLoading$.asObservable();
constructor(private httpClient: HttpClient, private errorService: ErrorService) {
super();
super.unsubscribeOnDestroy(
combineLatest([this._currentEmployeeId$, this._lastUpdatedEmployeeId$])
.pipe(
distinctUntilChanged(
([prevEmployeeId], [currEmployeeId, currLastUpdatedEmployeeId]) =>
!currLastUpdatedEmployeeId && prevEmployeeId === currEmployeeId
),
filter(([currentEmployeeId]) => !!currentEmployeeId),
switchMap(([currentEmployeeId]) =>
this._fetchEmployee$(currentEmployeeId).pipe(filter(employee => !!employee))
)
)
.subscribe(employee => {
this._employee$.next(employee);
})
);
}
public employeesData$: Observable<EmployeesData> = combineLatest([this._params$, this._lastDeletedEmployee$]).pipe(
switchMap(([params]) => this._fetchEmployees$(params))
);
public resetParams(): void {
this._params$.next(DEFAULT_PARAMS);
}
public setCurrentEmployeeId(currentEmployeeId: string): void {
if (this._currentEmployeeId$.getValue() !== currentEmployeeId) {
this._employee$.next(null);
this._currentEmployeeId$.next(currentEmployeeId);
}
}
public resetLastUpdatedEmployeeId(): void {
this._lastUpdatedEmployeeId$.next(null);
}
private _fetchEmployees$(employeeParams: EmployeeParams): Observable<EmployeesData> {
const { sort, order, limit, page, search, onlyEmployeesWithoutAuthorization } = employeeParams;
const params: Params = {
sort,
order,
limit: limit.toString(),
page: page.toString(),
};
if (search) {
params.search = search;
}
if (onlyEmployeesWithoutAuthorization) {
params.onlyEmployeesWithoutAuthorization = onlyEmployeesWithoutAuthorization.toString();
}
this._employeesLoading$.next(true);
return this.httpClient
.get<EmployeesDataResponse>(this._apiBaseUrl, { params })
.pipe(
map(({ data, meta }) => {
this._employeesLoading$.next(false);
return { data: data.map(employee => mapResponseToEmployeeCompact(employee)), meta };
}),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte hämta personal.\n\n${error.message}`,
name: `GET ${this._apiBaseUrl}`,
method: 'EmployeeService._fetchEmployees$',
});
})
);
}
private _fetchEmployee$(ciamUserId: string): Observable<Employee> {
const apiUrl = `${this._apiBaseUrl}/${ciamUserId}`;
return this.httpClient.get<{ data: EmployeeResponse }>(apiUrl).pipe(
map(({ data }) => mapResponseToEmployee(data)),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte hämta personal.\n\n${error.message}`,
name: `GET ${this._apiBaseUrl}/{ciamUserId}`,
data: { ciamUserId },
method: 'EmployeeService._fetchEmployee$',
});
})
);
}
public setSearchString(value: string): void {
this._params$.next({
...this._params$.getValue(),
search: value,
page: 1,
});
}
public setOnlyEmployeesWithoutAuthorization(value: boolean): void {
this._params$.next({
...this._params$.getValue(),
onlyEmployeesWithoutAuthorization: value,
page: 1,
});
}
public setEmployeeToDelete(employee: Employee): void {
this._employeeToDelete$.next(employee);
}
public deleteEmployee(employee: Employee): Observable<Employee> {
return this.httpClient.delete<void>(`${this._apiBaseUrl}/${employee.id}`).pipe(
tap(() => {
this._lastDeletedEmployee$.next(employee);
}),
map(() => employee),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte ta bort personal.\n\n${error.message}`,
name: `DELETE ${this._apiBaseUrl}/{ciamUserId}`,
method: 'EmployeeService.deleteEmployee',
});
})
);
}
public setSort(sort: keyof EmployeeCompactResponse): void {
const params = this._params$.getValue();
const order = params.sort === sort && params.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
this._params$.next({ ...params, sort, order });
}
public setPage(page: number): void {
this._params$.next({ ...this._params$.getValue(), page });
}
public postEmployeeInvitation(emails: string[]): Observable<EmployeeInviteResponse | null> {
const apiUrl = `${this._apiBaseUrl}/invite`;
return this.httpClient
.patch<{ data: EmployeeInviteResponse }>(apiUrl, { emails })
.pipe(
take(1),
map(({ data }) => data),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte bjuda in personal.\n\n${error.message}`,
name: `PATCH ${apiUrl}`,
data: { emails },
method: 'EmployeeService.postEmployeeInvitation',
});
})
);
}
public updateEmployee$(ciamUserId: string, data: EmployeeEditRequest): Observable<boolean> {
return this.httpClient.put<boolean>(`${this._apiBaseUrl}/${ciamUserId}`, data).pipe(
take(1),
tap(() => {
this._employee$.next(null);
this._lastUpdatedEmployeeId$.next(ciamUserId);
}),
map(() => true),
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte redigera personal.\n\n${error.message}`,
name: `PATCH ${this._apiBaseUrl}/{ciamUserId}`,
data: { ciamUserId },
method: 'EmployeeService.updateEmployee$',
});
})
);
}
}

View File

@@ -1,3 +1,22 @@
### [2.5.1](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.5.0&sourceBranch=refs%2Ftags%2Fv2.5.1) (2022-01-11)
### Features
* **Avvikelserapporten:** Använd nya dialogen ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([8909f7c](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/8909f7c3d1e91833988c69d1f8485cd217654e32))
* **Nya deltagare:** Använd nya dialogen ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([b943474](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/b943474138442cec88f371e8477126f8dacc7e07))
* **automatisk utloggning:** Använder nu den nya dialog-komponenten ([TV-845](https://jira.arbetsformedlingen.se/browse/TV-845)) ([1de7624](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/1de762420054ca6782660dd950c938541f543291)), closes [feature/TV-845-ui-dialog-idle-timout-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-idle-timout-dialog-2) [feature/TV-845-ui-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-2) [feature/TV-845-ui-dialog-2](https://jira.arbetsformedlingen.se/browse/TV-845-ui-dialog-2)
* **Deltagarlista:** Nu går det att sortera när man klickar på kolumnnamnet ([TV-989](https://jira.arbetsformedlingen.se/browse/TV-989)) ([fb6239a](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/fb6239afbe2cfe2652f0f9b407f684ddc77e239a))
* **Signal om arbete eller studier:** Uppdaterad UX för val av sysselsättning (fyra radioknappar, ingen drop-down ([TV-440](https://jira.arbetsformedlingen.se/browse/TV-440)) ([65c29e0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/65c29e065d66cda8e6c6fa71af30558d303d9d39))
### Bug Fixes
* **informativ-rapport:** Added file-upload inside informativ-rapport to feature toggling ([92cf04e](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/92cf04e802cce05c1ed799cabfabf73b1c370c0c))
* **gemensam-planering:** Implemented new API response for GP ([a158b4a](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/a158b4ac21f9dbab84865f461010dab63c50a7fa))
* **login:** Removed login with username/password from prod builds ([a6f3792](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/a6f37923abc70627e8b0b98f18f5f3791321da3d))
* **texter:** Updated button text and changed aria texts for filter functionality inside utförande verksamheter ([TV-953](https://jira.arbetsformedlingen.se/browse/TV-953)) ([8fb58d6](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/commits/8fb58d698ce26e5031174040978f69ba1af1f57e))
## [2.5.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.4.0&sourceBranch=refs%2Ftags%2Fv2.5.0) (2021-12-14) ## [2.5.0](https://bitbucket.arbetsformedlingen.se/projects/tea/repos/mina-sidor-fa-web/compare/diff?targetBranch=refs%2Ftags%2Fv2.4.0&sourceBranch=refs%2Ftags%2Fv2.5.0) (2021-12-14)

View File

@@ -0,0 +1,14 @@
<div class="ui-dialog-layout">
<ui-loader *ngIf="isLoading" uiType="absolute"></ui-loader>
<div class="ui-dialog-layout__heading">
<ng-content select="[uiDialogHeading]"></ng-content>
</div>
<div class="ui-dialog-layout__scrollable-content">
<ng-content></ng-content>
</div>
<footer class="ui-dialog-layout__footer">
<ng-content select="[uiDialogFooter]"></ng-content>
</footer>
</div>

View File

@@ -0,0 +1,40 @@
@import 'variables/shadows';
@import 'variables/gutters';
.ui-dialog-layout {
padding-top: $digi--layout--gutter--s;
display: flex;
flex-direction: column;
gap: $digi--layout--gutter--s;
max-height: 90vh;
&__heading {
flex: 1 1 2rem;
}
&__scrollable-content {
overflow: auto;
flex-grow: 1;
}
&__close-button {
position: absolute;
top: var(--digi--layout--gutter);
right: var(--digi--layout--gutter--s);
background: transparent;
border: none;
display: flex;
justify-content: center;
align-items: center;
}
&__close-button-text {
font-size: var(--digi--typography--font-size--s);
}
&__footer {
flex: 1 0 4rem;
min-height: var(--digi--layout--gutter);
display: flex;
margin-top: $digi--layout--gutter--l;
gap: var(--digi--layout--gutter);
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UiDialogLayoutComponent } from './ui-dialog-layout.component';
describe('UiDialogLayoutComponent', () => {
let component: UiDialogLayoutComponent;
let fixture: ComponentFixture<UiDialogLayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UiDialogLayoutComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UiDialogLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,11 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'ui-dialog-layout',
templateUrl: './ui-dialog-layout.component.html',
styleUrls: ['./ui-dialog-layout.component.scss'],
})
export class UiDialogLayoutComponent {
@Input() uiHeading: string;
@Input() isLoading = false;
}

View File

@@ -21,12 +21,18 @@ export class UiDialogRef<CloseResponseData = unknown, InputDataType = unknown> {
overlay.backdropClick().subscribe(() => this._close('backdropClick', null)); overlay.backdropClick().subscribe(() => this._close('backdropClick', null));
} }
get includeBasicFooter(): boolean {
return this.config.includeBasicFooter;
}
get primaryButtonText(): string { get primaryButtonText(): string {
return this.config.primaryButtonText; return this.config.primaryButtonText;
} }
get secondaryButtonText(): string { get secondaryButtonText(): string {
return this.config.secondaryButtonText; return this.config.secondaryButtonText;
} }
get heading(): string {
return this.config.heading;
}
close(data?: CloseResponseData): void { close(data?: CloseResponseData): void {
this._close('close', data); this._close('close', data);

View File

@@ -1,21 +1,25 @@
<div class="ui-dialog"> <div class="ui-dialog">
<ng-container [ngSwitch]="contentType"> <h2 class="ui-dialog__heading" *ngIf="heading">{{heading}}</h2>
<ng-container *ngSwitchCase="'string'"> <div [ngClass]="{'ui-dialog__scrollable-content': includeBasicFooter}">
<div class="box"> <ng-container [ngSwitch]="contentType">
<ng-container *ngSwitchCase="'string'">
<div [innerHTML]="content"></div> <div [innerHTML]="content"></div>
</div> <footer class="ui-dialog__footer">
</ng-container> <digi-button af-type="button" (click)="close()">Stäng</digi-button>
</footer>
</ng-container>
<ng-container *ngSwitchCase="'template'"> <ng-container *ngSwitchCase="'template'">
<ng-container *ngTemplateOutlet="content; context: context"></ng-container> <ng-container *ngTemplateOutlet="content; context: context"></ng-container>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'component'"> <ng-container *ngSwitchCase="'component'">
<ng-container *ngComponentOutlet="content"></ng-container> <ng-container *ngComponentOutlet="content"></ng-container>
</ng-container>
</ng-container> </ng-container>
</ng-container> </div>
<footer class="ui-dialog__footer"> <footer class="ui-dialog__footer" *ngIf="includeBasicFooter">
<digi-button af-type="button" (click)="primaryAction()">{{primaryButtonText}}</digi-button> <digi-button af-type="button" (click)="primaryAction()">{{primaryButtonText}}</digi-button>
<digi-button af-type="button" af-variation="secondary" *ngIf="secondaryButtonText" (click)="secondaryAction()"> <digi-button af-type="button" af-variation="secondary" *ngIf="secondaryButtonText" (click)="secondaryAction()">
{{secondaryButtonText}} {{secondaryButtonText}}

View File

@@ -7,6 +7,18 @@
box-shadow: $msfa__shadow; box-shadow: $msfa__shadow;
padding: $digi--layout--gutter--s $digi--layout--gutter--xl $digi--layout--gutter--l; padding: $digi--layout--gutter--s $digi--layout--gutter--xl $digi--layout--gutter--l;
position: relative; position: relative;
max-height: 90vh;
display: flex;
flex-direction: column;
&__heading {
flex: 1 1 2rem;
}
&__scrollable-content {
overflow: auto;
flex-grow: 1;
}
&__close-button { &__close-button {
position: absolute; position: absolute;

View File

@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UiDialogComponent } from './ui-dialog.component'; import { UiDialogComponent } from './ui-dialog.component';
describe('DialogComponent', () => { xdescribe('DialogComponent', () => {
let component: UiDialogComponent; let component: UiDialogComponent;
let fixture: ComponentFixture<UiDialogComponent>; let fixture: ComponentFixture<UiDialogComponent>;

View File

@@ -28,11 +28,18 @@ export class UiDialogComponent implements OnInit {
get secondaryButtonText(): string { get secondaryButtonText(): string {
return this.uiDialogRef.secondaryButtonText; return this.uiDialogRef.secondaryButtonText;
} }
get heading(): string {
return this.uiDialogRef.heading;
}
primaryAction(): void { primaryAction(): void {
this.uiDialogRef.primaryAction(); this.uiDialogRef.primaryAction();
} }
get includeBasicFooter(): boolean {
return this.uiDialogRef.includeBasicFooter;
}
secondaryAction(): void { secondaryAction(): void {
this.uiDialogRef.secondaryAction(); this.uiDialogRef.secondaryAction();
} }

View File

@@ -4,10 +4,16 @@ export const UI_DIALOG_DATA = 'UI_DIALOG_DATA';
export interface UiDialogConfig<DialogInputData = unknown> { export interface UiDialogConfig<DialogInputData = unknown> {
data?: DialogInputData; data?: DialogInputData;
minWidth?: string;
maxWidth?: string;
minHeight?: string;
maxHeight?: string;
heading?: string;
/** /**
* primaryButtonText defaults to 'Stäng' * primaryButtonText defaults to 'Stäng'
*/ */
includeBasicFooter?: boolean;
primaryButtonText?: string; primaryButtonText?: string;
/** /**

View File

@@ -3,11 +3,15 @@ import { CommonModule } from '@angular/common';
import { UiDialogComponent } from './ui-dialog.component'; import { UiDialogComponent } from './ui-dialog.component';
import { UiIconModule } from '@ui/icon/icon.module'; import { UiIconModule } from '@ui/icon/icon.module';
import { UiDialog } from '@ui/dialog/ui-dialog.service'; import { UiDialog } from '@ui/dialog/ui-dialog.service';
import { UiDialogLayoutComponent } from './ui-dialog-layout/ui-dialog-layout.component';
import { UiLoaderModule } from '@ui/loader/loader.module';
import { OverlayModule } from '@angular/cdk/overlay';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [UiDialogComponent], declarations: [UiDialogComponent, UiDialogLayoutComponent],
imports: [CommonModule, UiIconModule], imports: [CommonModule, UiIconModule, UiLoaderModule, OverlayModule],
providers: [UiDialog], providers: [UiDialog],
exports: [UiDialogLayoutComponent],
}) })
export class UiDialogModule {} export class UiDialogModule {}

View File

@@ -5,7 +5,7 @@ import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
import { UiDialogComponent } from './ui-dialog.component'; import { UiDialogComponent } from './ui-dialog.component';
import { UI_DIALOG_DATA, UiDialogConfig } from '@ui/dialog/ui-dialog.model'; import { UI_DIALOG_DATA, UiDialogConfig } from '@ui/dialog/ui-dialog.model';
@Injectable() @Injectable({ providedIn: 'root' })
export class UiDialog { export class UiDialog {
constructor(private overlay: Overlay, private injector: Injector) {} constructor(private overlay: Overlay, private injector: Injector) {}
@@ -19,22 +19,24 @@ export class UiDialog {
}); });
} }
open<DialogContent = unknown, T = unknown>( open<CloseResponseData = unknown, InputDataType = unknown>(
content: string | TemplateRef<unknown> | Type<unknown>, content: string | TemplateRef<unknown> | Type<unknown>,
config: UiDialogConfig<T> = {} config: UiDialogConfig<InputDataType> = { includeBasicFooter: false }
): UiDialogRef<DialogContent> { ): UiDialogRef<CloseResponseData> {
const positionStrategy = this.overlay.position().global().centerHorizontally().centerVertically(); const positionStrategy = this.overlay.position().global().centerHorizontally().centerVertically();
const configs = new OverlayConfig({ const configs = new OverlayConfig({
positionStrategy, positionStrategy,
minWidth: '40rem', minWidth: config.minWidth ?? '20rem',
minHeight: '40rem', minHeight: config.minHeight ?? '10rem',
maxWidth: config.maxWidth ?? '60rem',
maxHeight: config.maxHeight ?? '60rem',
hasBackdrop: true, hasBackdrop: true,
scrollStrategy: this.overlay.scrollStrategies.close(), scrollStrategy: this.overlay.scrollStrategies.block(),
backdropClass: 'cdk-overlay-dark-backdrop', backdropClass: 'cdk-overlay-dark-backdrop',
}); });
const overlayRef = this.overlay.create(configs); const overlayRef = this.overlay.create(configs);
const uiDialogRef = new UiDialogRef<DialogContent, T>(overlayRef, content, config); const uiDialogRef = new UiDialogRef<CloseResponseData, InputDataType>(overlayRef, content, config);
const injector = UiDialog._createInjector(uiDialogRef, this.injector, config); const injector = UiDialog._createInjector(uiDialogRef, this.injector, config);
overlayRef.attach(new ComponentPortal(UiDialogComponent, null, injector)); overlayRef.attach(new ComponentPortal(UiDialogComponent, null, injector));

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "mina-sidor-fa-web", "name": "mina-sidor-fa-web",
"version": "2.5.0", "version": "2.5.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mina-sidor-fa-web", "name": "mina-sidor-fa-web",
"version": "2.5.0", "version": "2.5.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "mina-sidor-fa-web", "name": "mina-sidor-fa-web",
"version": "2.5.0", "version": "2.5.1",
"license": "MIT", "license": "MIT",
"repository": "https://bitbucket.arbetsformedlingen.se/projects/TEA/repos/mina-sidor-fa-web", "repository": "https://bitbucket.arbetsformedlingen.se/projects/TEA/repos/mina-sidor-fa-web",
"engines": { "engines": {

View File

@@ -48,14 +48,14 @@ trap exitMessage EXIT
# ---------------------------------- # ----------------------------------
# RUN ESLINT # RUN ESLINT
# ---------------------------------- # ----------------------------------
echo -e "${CYAN}Running e2e tests using Cypress${NOCOLOR}" echo -e "${CYAN}Running lint${NOCOLOR}"
npm run lint npm run lint
echo -e "${GREEN}Tests finished${NOCOLOR}" echo -e "${GREEN}Tests finished${NOCOLOR}"
# ---------------------------------- # ----------------------------------
# TESTS # TESTS
# ---------------------------------- # ----------------------------------
echo -e "${CYAN}Running e2e tests using Cypress${NOCOLOR}" echo -e "${CYAN}Running tests${NOCOLOR}"
npm run test npm run test
echo -e "${GREEN}Tests finished${NOCOLOR}" echo -e "${GREEN}Tests finished${NOCOLOR}"