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)

View File

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

View File

@@ -33,6 +33,7 @@ export class AppComponent extends UnsubscribeDirective implements OnInit {
private _dialogRef: UiDialogRef;
private _userIsIdle$: Observable<boolean> = this.idleService.isIdle$;
private _idleDialogConfig: UiDialogConfig = {
includeBasicFooter: true,
primaryButtonText: 'Fortsätt sessionen',
primaryAction: () => this.setUserAsActive(),
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 { NgModule } from '@angular/core';
import { AdministrationRoutingModule } from './administration-routing.module';
import { AdministrationComponent } from './administration.component';
@NgModule({
declarations: [AdministrationComponent],
declarations: [],
imports: [CommonModule, AdministrationRoutingModule],
})
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">
<digi-ng-dialog
*ngIf="deleteEmployeeData.toDelete"
[afActive]="deleteEmployeeData.toDelete"
(afOnPrimaryClick)="deleteEmployeeModelPrimaryClick(deleteEmployeeData)"
(afOnSecondaryClick)="closeDeleteEmployeeModal()"
(afOnInactive)="closeDeleteEmployeeModal()"
afHeading="Ta bort personalkonto"
afHeadingLevel="h2"
[afPrimaryButtonText]="getPrimaryButtonText(deleteEmployeeData.lastDeleted)"
[afSecondaryButtonText]="getSecondaryButtonText(deleteEmployeeData.lastDeleted)"
>
<ng-container *ngIf="deleteEmployeeData.lastDeleted; else deletionWarning">
<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>
</digi-notification-alert>
</ng-container>
<ng-template #deletionWarning>
<p>Är du säker på att du vill ta bort personalkontot för {{deleteEmployeeData.toDelete.fullName}}?</p>
</ng-template>
<!-- <digi-ng-dialog-->
<!-- *ngIf="deleteEmployeeData.toDelete"-->
<!-- [afActive]="deleteEmployeeData.toDelete"-->
<!-- (afOnPrimaryClick)="deleteEmployeeModelPrimaryClick(deleteEmployeeData)"-->
<!-- (afOnSecondaryClick)="closeDeleteEmployeeModal()"-->
<!-- (afOnInactive)="closeDeleteEmployeeModal()"-->
<!-- afHeading="Ta bort personalkonto"-->
<!-- afHeadingLevel="h2"-->
<!-- [afPrimaryButtonText]="getPrimaryButtonText(deleteEmployeeData.lastDeleted)"-->
<!-- [afSecondaryButtonText]="getSecondaryButtonText(deleteEmployeeData.lastDeleted)"-->
<!-- >-->
<!-- <ng-container *ngIf="deleteEmployeeData.lastDeleted; else deletionWarning">-->
<!-- <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>-->
<!-- </digi-notification-alert>-->
<!-- </ng-container>-->
<!-- <ng-template #deletionWarning>-->
<!-- <p>Är du säker på att du vill ta bort personalkontot för {{deleteEmployeeData.toDelete.fullName}}?</p>-->
<!-- </ng-template>-->
<ng-container *ngIf="errorDuringDeletion$ | async as error">
<digi-notification-alert af-variation="danger" af-heading="Någonting gick fel" af-heading-level="h3">
<p>
Vi kunde inte radera personalkontot för {{deleteEmployeeData.toDelete.fullName}}. 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>
</digi-ng-dialog>
<!-- <ng-container *ngIf="errorDuringDeletion$ | async as error">-->
<!-- <digi-notification-alert af-variation="danger" af-heading="Någonting gick fel" af-heading-level="h3">-->
<!-- <p>-->
<!-- Vi kunde inte radera personalkontot för {{deleteEmployeeData.toDelete.fullName}}. 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>-->
<!-- </digi-ng-dialog>-->
</ng-container>

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Router } from '@angular/router';
import { Employee } from '@msfa-models/employee.model';
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 { map } from 'rxjs/operators';
@@ -21,8 +21,8 @@ export class EmployeeDeleteComponent {
@Input() returnToEmployeeList = false;
deleteEmployeeData$: Observable<DeleteEmployeeData> = combineLatest([
this.employeeService.employeeToDelete$,
this.employeeService.lastDeletedEmployee$,
this.administrationService.employeeToDelete$,
this.administrationService.lastDeletedEmployee$,
]).pipe(
map(([employeeToDelete, lastDeletedEmployee]) => {
return {
@@ -42,7 +42,7 @@ export class EmployeeDeleteComponent {
return lastDeleted ? null : 'Avbryt';
}
constructor(private employeeService: EmployeeService, private router: Router) {}
constructor(private administrationService: AdministrationService, private router: Router) {}
deleteEmployeeModelPrimaryClick(deleteEmployeeData: DeleteEmployeeData): void {
const { lastDeleted, toDelete } = deleteEmployeeData;
@@ -54,7 +54,7 @@ export class EmployeeDeleteComponent {
return;
}
const deleteEmployeeSubscription = this.employeeService.deleteEmployee(toDelete).subscribe({
const deleteEmployeeSubscription = this.administrationService.deleteEmployee(toDelete).subscribe({
error: (error: CustomError) => {
this._errorDuringDeletion$.next(error);
},
@@ -65,7 +65,7 @@ export class EmployeeDeleteComponent {
}
closeDeleteEmployeeModal(): void {
this.employeeService.setEmployeeToDelete(null);
this.administrationService.setEmployeeToDelete(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 { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
@@ -7,7 +6,11 @@ import { EmployeeDeleteComponent } from './employee-delete.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [EmployeeDeleteComponent],
imports: [CommonModule, RouterModule, DigiNgDialogModule],
imports: [
CommonModule,
RouterModule,
// DigiNgDialogModule
],
exports: [EmployeeDeleteComponent],
})
export class EmployeeDeleteModule {}

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Employee } from '@msfa-models/employee.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 { BehaviorSubject, Observable } from 'rxjs';
@@ -14,22 +14,22 @@ import { BehaviorSubject, Observable } from 'rxjs';
})
export class EmployeeCardComponent implements OnDestroy {
private _employeeId$ = new BehaviorSubject<string>(this.activatedRoute.snapshot.params['employeeId']);
employee$: Observable<Employee> = this.employeeService.employee$;
lastUpdatedEmployeeId$: Observable<string> = this.employeeService.lastUpdatedEmployeeId$;
employee$: Observable<Employee> = this.administrationService.employee$;
lastUpdatedEmployeeId$: Observable<string> = this.administrationService.lastUpdatedEmployeeId$;
allRoles: Role[] = this.roleService.allRoles;
accordionsExpanded = [];
constructor(
private activatedRoute: ActivatedRoute,
private employeeService: EmployeeService,
private administrationService: AdministrationService,
private roleService: RoleService
) {
this.employeeService.setCurrentEmployeeId(this.employeeId);
this.administrationService.setCurrentEmployeeId(this.employeeId);
}
ngOnDestroy(): void {
this.employeeService.resetLastUpdatedEmployeeId();
this.employeeService.setCurrentEmployeeId(null);
this.administrationService.resetLastUpdatedEmployeeId();
this.administrationService.setCurrentEmployeeId(null);
}
get employeeId(): string {
@@ -41,7 +41,7 @@ export class EmployeeCardComponent implements OnDestroy {
}
closeUpdatedNotificationAlert(): void {
this.employeeService.resetLastUpdatedEmployeeId();
this.administrationService.resetLastUpdatedEmployeeId();
}
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);
}
}

View File

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

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
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 { RequiredValidator } from '@msfa-utils/validators/required.validator';
import { BehaviorSubject, Observable } from 'rxjs';
@@ -21,7 +21,7 @@ export class EmployeeInviteComponent {
lastInvites$: Observable<EmployeeInviteResponse> = this._lastInvites$.asObservable();
submitIsLoading$ = new BehaviorSubject<boolean>(false);
constructor(private employeeService: EmployeeService) {}
constructor(private administrationService: AdministrationService) {}
get emailsControl(): AbstractControl {
return this.formGroup.get('emails');
@@ -93,7 +93,7 @@ export class EmployeeInviteComponent {
return;
}
const post = this.employeeService.postEmployeeInvitation(this.emailsControlValueAsArray).subscribe({
const post = this.administrationService.postEmployeeInvitation$(this.emailsControlValueAsArray).subscribe({
next: data => {
this.submitIsLoading$.next(false);
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 { Employee, EmployeesData } from '@msfa-models/employee.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 { 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 {
UiLinkButtonType = UiLinkButtonType;
private _searchValue$ = new BehaviorSubject<string>('');
onlyEmployeesWithoutAuthorization$: Observable<boolean> = this.employeeService.onlyEmployeesWithoutAuthorization$;
employeesData$: Observable<EmployeesData> = this.employeeService.employeesData$;
employeesLoading$: Observable<boolean> = this.employeeService.employeesLoading$;
sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this.employeeService.sort$;
onlyEmployeesWithoutAuthorization$: Observable<boolean> = this.administrationService
.onlyEmployeesWithoutAuthorization$;
employeesData$: Observable<EmployeesData> = this.administrationService.employeesData$;
employeesLoading$: Observable<boolean> = this.administrationService.employeesLoading$;
sort$: Observable<Sort<keyof EmployeeCompactResponse>> = this.administrationService.sort$;
iconType = UiIconType;
constructor(private employeeService: EmployeeService) {}
constructor(private administrationService: AdministrationService) {}
ngOnDestroy(): void {
this.employeeService.resetParams();
this.administrationService.resetParams();
}
get searchValue(): string {
@@ -33,7 +34,7 @@ export class EmployeesComponent implements OnDestroy {
}
setSearchString(): void {
this.employeeService.setSearchString(this.searchValue);
this.administrationService.setSearchString(this.searchValue);
}
setSearchValue($event: CustomEvent<{ target: { value: string } }>): void {
@@ -41,18 +42,18 @@ export class EmployeesComponent implements OnDestroy {
}
handleEmployeesSort(key: keyof EmployeeCompactResponse): void {
this.employeeService.setSort(key);
this.administrationService.setSort(key);
}
setNewPage(page: number): void {
this.employeeService.setPage(page);
this.administrationService.setPage(page);
}
setOnlyEmployeesWithoutAuthorization(checked: boolean): void {
this.employeeService.setOnlyEmployeesWithoutAuthorization(checked);
this.administrationService.setOnlyEmployeesWithoutAuthorization(checked);
}
setEmployeeToDelete(employee: Employee): void {
this.employeeService.setEmployeeToDelete(employee);
this.administrationService.setEmployeeToDelete(employee);
}
}

View File

@@ -85,17 +85,7 @@
Läs mer information här
</digi-button>
<digi-ng-dialog
class="avrop-dialog"
[afActive]="displayAvropDialog$ | async"
(afOnInactive)="closeAvropDialog()"
(afOnPrimaryClick)="closeAvropDialog()"
afHeading="Information"
afHeadingLevel="h2"
afPrimaryButtonText="Stäng"
afSecondaryButtonText=""
>
<h3>Genomförandereferens</h3>
<ng-template #informationDialog>
<p>
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.
@@ -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 och tolk i förfrågningsunderlaget för specifik upphandling.
</p>
</digi-ng-dialog>
<!-- </ui-dialog-layout>-->
</ng-template>
</div>
<msfa-avrop-list
[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 { RouterTestingModule } from '@angular/router/testing';
import { AvropComponent } from './avrop.component';
import { AvropModule } from './avrop.module';
import { ApmModule } from '@elastic/apm-rum-angular';
describe('AvropComponent', () => {
let component: AvropComponent;
@@ -12,7 +14,7 @@ describe('AvropComponent', () => {
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [AvropComponent],
imports: [RouterTestingModule, HttpClientTestingModule],
imports: [AvropModule, RouterTestingModule, HttpClientTestingModule, ApmModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).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 { Handledare } from '@msfa-models/handledare.model';
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({
selector: 'msfa-avrop',
@@ -11,6 +13,8 @@ import { BehaviorSubject, Observable } from 'rxjs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvropComponent implements OnDestroy {
@ViewChild('informationDialog') informationDialog: TemplateRef<unknown>;
uiDialogRef: UiDialogRef;
readonly totalAmountOfSteps = 3;
currentStep$: Observable<number> = this.avropService.currentStep$;
error$: Observable<string> = this.avropService.error$;
@@ -23,9 +27,8 @@ export class AvropComponent implements OnDestroy {
avropIsSubmitted$: Observable<boolean> = this.avropService.avropIsSubmitted$;
avropLoading$: Observable<boolean> = this.avropService.avropLoading$;
showUnauthorizedError$: Observable<boolean> = this.avropService.showUnauthorizedError$;
displayAvropDialog$ = new BehaviorSubject(false);
constructor(private avropService: AvropService) {}
constructor(private avropService: AvropService, private uiDialog: UiDialog) {}
ngOnDestroy(): void {
this.returnToStep1();
@@ -76,10 +79,6 @@ export class AvropComponent implements OnDestroy {
}
openAvropDialog(): void {
this.displayAvropDialog$.next(true);
}
closeAvropDialog(): void {
this.displayAvropDialog$.next(false);
this.uiDialogRef = this.uiDialog.open(this.informationDialog, { heading: 'Information', includeBasicFooter: true });
}
}

View File

@@ -9,7 +9,7 @@ import { UiSkeletonModule } from '@ui/skeleton/skeleton.module';
import { AvropComponent } from './avrop.component';
import { AvropFiltersModule } from './components/avrop-filters/avrop-filters.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({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -24,7 +24,7 @@ import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
UiLoaderModule,
HandledarePickerFormModule,
UnauthorizedAlertModule,
DigiNgDialogModule,
UiDialogModule,
],
})
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>
<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">
<ui-select
*ngIf="reasonsAsUiSelectOptions$ | async as reason; else loadingRef"
@@ -90,40 +94,15 @@
</ui-link-button>
</div>
</form>
<digi-ng-dialog
[afActive]="confirmDialogIsOpen$ | async"
(afOnPrimaryClick)="submitAndCloseConfirmDialog()"
(afOnInactive)="cancelConfirmDialog()"
afHeadingLevel="h2"
afPrimaryButtonText="Skicka in"
afSecondaryButtonText="Avbryt"
(afOnSecondaryClick)="cancelConfirmDialog()"
afHeading="Vill du skicka in Avvikelserapport (avvikelse)"
afAriaLabel="Förhandsgranska och skicka in Avvikelserapport (avvikelse)"
id="confirmAvvikelserapport"
<digi-notification-alert
*ngIf="submitError$ | async as error"
af-variation="danger"
af-heading="Någonting gick fel"
>
<ui-loader *ngIf="submitIsLoading$ | async" uiType="absolute"></ui-loader>
<msfa-report-description-list [avrop]="avrop">
<dt>Orsak till avvikelse:</dt>
<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>
<p>Kunde inte spara Avvikelserapport (avvikelse). Försök igen om en stund.</p>
<p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
</digi-notification-alert>
</ng-template>
</ng-template>
</div>

View File

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

View File

@@ -12,8 +12,14 @@ import { RegexValidator } from '@msfa-utils/validators/regex.validator';
import { RequiredValidator } from '@msfa-validators/required.validator';
import { addDays } from 'date-fns';
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 { 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 {
genomforandeReferens: string;
@@ -34,20 +40,22 @@ type AvvikelseFormKeys = keyof AvvikelseFormData;
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
confirmDialogRef: UiDialogRef;
shouldValidate$ = new BehaviorSubject<boolean>(false);
reasonFormName: AvvikelseFormKeys = 'reason';
questionsFormName: AvvikelseFormKeys = 'questions';
reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
submitIsLoading$ = new BehaviorSubject<boolean>(false);
submitError$ = new BehaviorSubject<CustomError>(null);
genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
map((params: Params) => +params.genomforandeReferens)
);
avrop: DeltagareAvrop;
avrop$: Observable<DeltagareAvrop> = this.genomforandeReferens$.pipe(
switchMap(genomforandeReferens => this.avvikelseReportFormService.fetchAvropInformation$(genomforandeReferens)),
tap(avrop => (this.avrop = avrop)),
shareReplay(1)
);
@@ -60,8 +68,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
chosenReasonId$: Observable<string>;
chosenReason$: Observable<AvvikelseReason>;
questionsForChosenReason$: Observable<AvvikelseQuestion[]>;
avvikelseSubmitData$: Observable<AvvikelseReportRequest>;
confirmDialogIsOpen$ = new BehaviorSubject<boolean>(false);
submittedDate$ = new BehaviorSubject<Date | null>(null);
private subscriptions: Subscription[] = [];
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.questionsFormName]: new FormArray([]),
});
private formData$: Observable<AvvikelseFormData> = this.avvikelseFormGroup
.valueChanges as Observable<AvvikelseFormData>;
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 {
return this.avvikelseFormGroup.get(this.reasonFormName);
@@ -93,11 +107,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
}
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(
map(([chosenOrsak, allAvvikelseQuestions]) => {
return allAvvikelseQuestions.filter(question => question.id.startsWith(chosenOrsak.toString() + '_'));
@@ -105,21 +114,15 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
);
this.subscriptions.push(
this.chosenReason$.subscribe(() => {
this.chosenReason$.subscribe(chosenReason => {
this.shouldValidate$.next(false);
}),
this.questionsForChosenReason$.subscribe(questions => {
this.clearQuestions();
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 {
@@ -138,6 +141,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
private _isAfterStartDate(startDate: Date): boolean {
return new Date() > startDate;
}
private _isBeforeLastPossibleReportDay(endDate: Date): boolean {
// 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
@@ -158,44 +162,41 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
}
}
openConfirmDialog(): void {
openConfirmDialog(avrop: DeltagareAvrop): void {
this.shouldValidate$.next(true);
markControlsAsDirty(Object.values(this.avvikelseFormGroup.controls));
this.avvikelseFormGroup.markAllAsTouched();
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 {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
private makeAvvikelseSubmitData(
private _makeAvvikelseSubmitData(
genomforandeReferens: number,
chosenReason: string,
formData: AvvikelseFormData
@@ -219,6 +220,8 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
private addQuestionToForm(question: AvvikelseQuestion): void {
// 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.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 { CommonModule } from '@angular/common';
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 { AvvikelseReportFormComponent } from './avvikelse-report-form.component';
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({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvvikelseReportFormComponent],
declarations: [AvvikelseReportFormComponent, AvvikelseConfirmDialogComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: AvvikelseReportFormComponent }]),
@@ -34,10 +35,10 @@ import { AvvikelseReportFormService } from './avvikelse-report-form.service';
UiLoaderModule,
UiSelectModule,
ReportDescriptionListModule,
DigiNgDialogModule,
UiTextareaModule,
UiLinkButtonModule,
PreventDoubleSubmitModule,
UiDialogModule,
],
providers: [AvvikelseReportFormService],
exports: [AvvikelseReportFormComponent],

View File

@@ -154,9 +154,9 @@ export class GemensamPlaneringFormComponent {
return new Date() > startDate;
}
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
const lastPossibleReportDay = addDays(endDate, 15);
const lastPossibleReportDay = addDays(endDate, 84);
return lastPossibleReportDay > new Date();
}

View File

@@ -21,35 +21,27 @@
</div>
<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()">
<ui-select
[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">
<digi-form-fieldset af-name="type" af-form="signal-form">
<ui-radiobutton-group
[formControl]="omfattningFormControl"
[formControl]="typeFormControl"
[uiRequired]="true"
[uiAnnounceIfOptional]="true"
[uiRadiobuttons]="omfattningRadioButtons"
[uiInvalid]="formControlIsInvalid(omfattningFormName)"
[uiValidationMessage]="formErrors.omfattning"
[uiRadiobuttons]="typeRadioButtons"
[uiInvalid]="formControlIsInvalid(typeFormName)"
[uiValidationMessage]="formErrors.type"
></ui-radiobutton-group>
</digi-form-fieldset>
<ui-input
*ngIf="showPercentFormControl"
*ngIf="isDeltid"
class="signal-form__number-input"
[formControl]="percentFormControl"
uiType="number"
[uiMin]="1"
[uiMax]="99"
uiLabel="Antal procent vid deltid"
uiLabel="Ange omfattning i procent"
[uiRequired]="true"
[uiAnnounceIfOptional]="true"
[uiInvalid]="formControlIsInvalid(percentFormName)"
@@ -59,7 +51,7 @@
<div class="signal-form__form-item">
<digi-ng-form-datepicker
[formControl]="startDateFormControl"
[afLabel]="'Startdatum för ' + (typeFormControl.value || 'arbete/utbildning')"
[afLabel]="datePickerLabel"
[afDisableValidStyle]="true"
[afRequired]="true"
[afInvalid]="formControlIsInvalid(startDateFormName)"
@@ -97,12 +89,10 @@
>
<ui-loader *ngIf="submitLoading$ | async" uiType="absolute"></ui-loader>
<msfa-report-description-list [avrop]="avrop">
<dt>Typ av sysselsättning</dt>
<dt>Sysselsättning</dt>
<dd>{{convertTypeValueToLabel(typeFormControl.value)}}</dd>
<dt>Omfattning</dt>
<dd>{{convertOmfattningValueToLabel(omfattningFormControl.value)}}</dd>
<ng-container *ngIf="showPercentFormControl">
<dt>Antal procent vid deltid</dt>
<ng-container *ngIf="isDeltid">
<dt>Omfattning i procent</dt>
<dd>{{percentFormControl.value}}%</dd>
</ng-container>
<dt>Startdatum</dt>

View File

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

View File

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

View File

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

View File

@@ -64,9 +64,9 @@ export class SlutredovisningFormComponent {
}
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
const lastPossibleReportDay = addDays(date, 100);
const lastPossibleReportDay = addDays(date, 84);
return lastPossibleReportDay > new Date();
}

View File

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

View File

@@ -1,5 +1,3 @@
<h2>Händelser för {{deltagare.fullName}}</h2>
<div class="deltagare-list-handelser" *ngIf="deltagare">
<h3 *ngIf="activeHandelseMotivation" class="deltagare-list-handelser__sub-heading">{{deltagare.fullName}}</h3>
<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 { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { DeltagareListModule } from '../../deltagare-list.module';
describe('DeltagareListHandelserDialogComponent', () => {
xdescribe('DeltagareListHandelserDialogComponent', () => {
let component: DeltagareListHandelserDialogComponent;
let fixture: ComponentFixture<DeltagareListHandelserDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [HttpClientTestingModule],
imports: [DeltagareListModule, HttpClientTestingModule],
declarations: [DeltagareListHandelserDialogComponent],
}).compileComponents();
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
export enum SignalResponseType {
Work = 'work',
Education = 'education',
Arbete = 'arbete',
Utbildning = 'utbildning',
}
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)

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));
}
get includeBasicFooter(): boolean {
return this.config.includeBasicFooter;
}
get primaryButtonText(): string {
return this.config.primaryButtonText;
}
get secondaryButtonText(): string {
return this.config.secondaryButtonText;
}
get heading(): string {
return this.config.heading;
}
close(data?: CloseResponseData): void {
this._close('close', data);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,11 +3,15 @@ import { CommonModule } from '@angular/common';
import { UiDialogComponent } from './ui-dialog.component';
import { UiIconModule } from '@ui/icon/icon.module';
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({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [UiDialogComponent],
imports: [CommonModule, UiIconModule],
declarations: [UiDialogComponent, UiDialogLayoutComponent],
imports: [CommonModule, UiIconModule, UiLoaderModule, OverlayModule],
providers: [UiDialog],
exports: [UiDialogLayoutComponent],
})
export class UiDialogModule {}

View File

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

4
package-lock.json generated
View File

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

View File

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

View File

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