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
This commit is contained in:
Daniel Appelgren
2021-12-29 14:19:07 +01:00
parent b943474138
commit 7c48ec175e
13 changed files with 301 additions and 310 deletions

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

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

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

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