feat(deltagare): Added avrop-information to deltagare card. (TV-324)

Squashed commit of the following:

commit 7ed848709ff02565de4d7377e75dee804b2752ff
Merge: 1dd9593 35213d6
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Aug 12 13:24:28 2021 +0200

    Merged develop and resolved conflict

commit 1dd959342bd93110339db56035552c9560451b09
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Aug 12 08:39:52 2021 +0200

    Refactored custom error mapping

commit a456ddc6a3df6851b9bb894c6bf1af9884cc4870
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Aug 11 15:52:04 2021 +0200

    Updates regarding fetching deltagare

commit c320848ada6082ae06e8789d7b2569b269032a10
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Aug 11 07:00:30 2021 +0200

    Added some error-handling for deltagare service

commit b5591427114f080083026babd9c60ae7e8eec4a3
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Aug 10 11:30:40 2021 +0200

    Added mock-data and request inside service. Also changed view with updated properties
This commit is contained in:
Erik Tiekstra
2021-08-12 15:28:33 +02:00
parent 35213d6aba
commit b4b71efe2c
9 changed files with 170 additions and 82 deletions

View File

@@ -7,7 +7,12 @@
information om deltagarna.
</p>
<a routerLink="1">Klicka för att gå till en test-deltagare från API:et</a>
<ul>
<li><a routerLink="1">Klicka för att gå till sokandeId 1</a></li>
<li><a routerLink="2">Klicka för att gå till sokandeId 2</a></li>
<li><a routerLink="3">Klicka för att gå till sokandeId 3</a></li>
<li><a routerLink="1000">Klicka för att gå till sokandeId 1000</a></li>
</ul>
<ul *ngIf="allDeltagare$ | async as allDeltagare">
<li *ngFor="let deltagare of allDeltagare"><a [routerLink]="deltagare.id">{{deltagare.fullName}}</a></li>

View File

@@ -32,7 +32,7 @@
</dd>
</ng-container>
<dt>Telefon:</dt>
<ng-container *ngIf="deltagare.phoneNumbers.length; else emptyDD">
<ng-container *ngIf="deltagare.phoneNumbers?.length; else emptyDD">
<ng-container *ngFor="let phoneNumber of deltagare.phoneNumbers">
<dd>{{ phoneNumber.type }}: {{phoneNumber.number}}</dd>
</ng-container>
@@ -66,27 +66,33 @@
<h2>Om tjänsten</h2>
<dl>
<dt>Tillhörande tjänst:</dt>
<dd *ngIf="deltagare.service; else emptyDD">{{ deltagare.service }}</dd>
<dd *ngIf="deltagare.avropInformation.tjanst; else emptyDD">
{{ deltagare.avropInformation.tjanst }}
</dd>
<dt>Datum för tjänstens början:</dt>
<dd *ngIf="deltagare.startDate; else emptyDD">{{ deltagare.startDate | localDate }}</dd>
<dd *ngIf="deltagare.avropInformation.startDate; else emptyDD">
<digi-typography-time [afDateTime]="deltagare.avropInformation.startDate"></digi-typography-time>
</dd>
<dt>Datum för tjänstens slut:</dt>
<dd *ngIf="deltagare.endDate; else emptyDD">{{ deltagare.endDate | localDate }}</dd>
<dd *ngIf="deltagare.avropInformation.endDate; else emptyDD">
<digi-typography-time [afDateTime]="deltagare.avropInformation.endDate"></digi-typography-time>
</dd>
<dt>Deltagandefrekvens:</dt>
<dd *ngIf="deltagare.service?.frequency; else emptyDD">{{ deltagare.service.frequency }}</dd>
<dd *ngIf="deltagare.avropInformation.participationFrequency; else emptyDD">
{{ deltagare.avropInformation.participationFrequency }}
</dd>
<dt>Utförande verksamhet:</dt>
<dd *ngIf="deltagare.service?.organisation; else emptyDD">{{ deltagare.service.organisation }}</dd>
<dd *ngIf="deltagare.avropInformation.utforandeVerksamhet; else emptyDD">
{{ deltagare.avropInformation.utforandeVerksamhet }}
</dd>
<dt>Utförande adress:</dt>
<dd
*ngIf="
deltagare.service?.organisation &&
deltagare.service?.organisation.adress;
else emptyDD
"
>
{{ deltagare.service.organisation.adress }}
<dd *ngIf="deltagare.avropInformation.utforandeAdress; else emptyDD">
{{ deltagare.avropInformation.utforandeAdress }}
</dd>
<dt>Genomförandereferens:</dt>
<dd *ngIf="deltagare.service?.reference; else emptyDD">{{ deltagare.service.reference }}</dd>
<dd *ngIf="deltagare.avropInformation.genomforandeReferens; else emptyDD">
{{ deltagare.avropInformation.genomforandeReferens }}
</dd>
</dl>
</div>
</div>

View File

@@ -5,7 +5,7 @@ import { Deltagare } from '@dafa-models/deltagare.model';
import { WorkExperience } from '@dafa-models/work-experience.model';
import { DeltagareService } from '@dafa-services/api/deltagare.service';
import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { map } from 'rxjs/operators';
@Component({
selector: 'dafa-deltagare-card',
@@ -14,12 +14,7 @@ import { map, switchMap } from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeltagareCardComponent {
private _deltagareId$: Observable<string> = this.activatedRoute.params.pipe(
map(({ deltagareId }) => deltagareId as string)
);
deltagare$: Observable<Deltagare> = this._deltagareId$.pipe(
switchMap(deltagareId => this.deltagaresService.deltagare$(deltagareId))
);
deltagare$: Observable<Deltagare> = this.deltagareService.deltagare$;
firstVisibleWorkExperiences$: Observable<WorkExperience[]> = this.deltagare$.pipe(
map(deltagare => deltagare.workExperiences.slice(0, 2))
);
@@ -30,7 +25,9 @@ export class DeltagareCardComponent {
iconType = IconType;
accordionExpanded = false;
constructor(private activatedRoute: ActivatedRoute, private deltagaresService: DeltagareService) {}
constructor(private activatedRoute: ActivatedRoute, private deltagareService: DeltagareService) {
this.deltagareService.setCurrentDeltagareId(this.activatedRoute.snapshot.params.deltagareId);
}
toggleAccordionExpanded(): void {
this.accordionExpanded = !this.accordionExpanded;

View File

@@ -1,3 +1,4 @@
import { AvropResponse } from './avrop.response.model';
import { ContactInformationResponse } from './contact-information.response.model';
import { DisabilitiesResponse } from './disabilities.response.model';
import { DriversLicenseResponse } from './drivers-license.response.model';
@@ -23,4 +24,5 @@ export interface DeltagareResponse {
workLanguages: WorkLanguagesResponse;
disabilities: DisabilitiesResponse;
workExperiences: WorkExperiencesResponse;
avropInformation: AvropResponse;
}

View File

@@ -1,5 +1,7 @@
import { Address } from './address.model';
import { DeltagareCompactResponse, DeltagareResponse } from './api/deltagare.response.model';
import { AvropResponse } from './api/avrop.response.model';
import { DeltagareResponse } from './api/deltagare.response.model';
import { Avrop, mapAvropResponseToAvrop } from './avrop.model';
import { mapResponseToContactInformation } from './contact-information.model';
import { Disability, mapResponseToDisability } from './disability.model';
import { DriversLicense, mapResponseToDriversLicense } from './drivers-license.model';
@@ -11,10 +13,13 @@ import { mapResponseToWorkExperience, WorkExperience } from './work-experience.m
export interface DeltagareCompact {
id: string;
fullName: string;
kommun?: string;
utforandeVerksamhet: string;
utforandeAdress: string;
}
export interface Deltagare extends DeltagareCompact {
export interface Deltagare {
id: string;
fullName: string;
firstName: string;
lastName: string;
ssn: string;
@@ -28,14 +33,16 @@ export interface Deltagare extends DeltagareCompact {
disabilities: Disability[];
workLanguages: string[];
workExperiences: WorkExperience[];
avropInformation: Avrop;
}
export function mapResponseToDeltagareCompact(data: DeltagareCompactResponse): DeltagareCompact {
const { sokandeId, deltagare, kommun } = data;
export function mapResponseToDeltagareCompact(data: AvropResponse): DeltagareCompact {
const { sokandeId, deltagare, adress, utforandeverksamhet } = data;
return {
id: sokandeId,
id: sokandeId.toString(),
fullName: deltagare,
kommun,
utforandeVerksamhet: utforandeverksamhet,
utforandeAdress: adress,
};
}
@@ -50,6 +57,7 @@ export function mapResponseToDeltagare(data: DeltagareResponse): Deltagare {
workLanguages,
disabilities,
workExperiences,
avropInformation,
} = data;
return {
@@ -65,5 +73,6 @@ export function mapResponseToDeltagare(data: DeltagareResponse): Deltagare {
workExperiences:
workExperiences &&
workExperiences.arbetslivserfarenheter.map(workExperience => mapResponseToWorkExperience(workExperience)),
avropInformation: avropInformation && mapAvropResponseToAvrop(avropInformation),
};
}

View File

@@ -28,6 +28,9 @@ export class CustomError implements Error {
if (!error) {
return '';
}
if (typeof error === 'string') {
return error;
}
if ('stack' in error) {
return error.stack;
@@ -39,18 +42,13 @@ export class CustomError implements Error {
}
static getErrorType(error: Error | (Error & { type: ErrorType })): ErrorType {
let type: ErrorType;
if ('type' in error) {
type = error.type;
} else {
type = ErrorType.UNKNOWN;
if (typeof error === 'object' && 'type' in error) {
return error.type;
} else if (error.name === 'HttpErrorResponse') {
return ErrorType.API;
}
if (error.name === 'HttpErrorResponse') {
type = ErrorType.API;
}
return type;
return ErrorType.UNKNOWN;
}
}

View File

@@ -1,8 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { UnsubscribeDirective } from '@dafa-directives/unsubscribe.directive';
import { environment } from '@dafa-environment';
import { AvropResponse } from '@dafa-models/api/avrop.response.model';
import { ContactInformationResponse } from '@dafa-models/api/contact-information.response.model';
import { DeltagareCompactResponse } from '@dafa-models/api/deltagare.response.model';
import { DisabilityResponse } from '@dafa-models/api/disability.response.model';
import { DriversLicenseResponse } from '@dafa-models/api/drivers-license.response.model';
import { EducationsResponse } from '@dafa-models/api/educations.response.model';
@@ -10,55 +11,90 @@ import { HighestEducationResponse } from '@dafa-models/api/highest-education.res
import { TranslatorResponse } from '@dafa-models/api/translator.response.model';
import { WorkExperiencesResponse } from '@dafa-models/api/work-experiences.response.model';
import { WorkLanguagesResponse } from '@dafa-models/api/work-languages.response.model';
import { Avrop, mapAvropResponseToAvrop } from '@dafa-models/avrop.model';
import { ContactInformation, mapResponseToContactInformation } from '@dafa-models/contact-information.model';
import { Deltagare, DeltagareCompact, mapResponseToDeltagareCompact } from '@dafa-models/deltagare.model';
import { Disability, mapResponseToDisability } from '@dafa-models/disability.model';
import { DriversLicense, mapResponseToDriversLicense } from '@dafa-models/drivers-license.model';
import { Education, mapResponseToEducation } from '@dafa-models/education.model';
import { errorToCustomError } from '@dafa-models/error/custom-error';
import { HighestEducation, mapResponseToHighestEducation } from '@dafa-models/highest-education.model';
import { mapResponseToWorkExperience, WorkExperience } from '@dafa-models/work-experience.model';
import { ErrorService } from '@dafa-services/error.service';
import { sortFromToDates } from '@dafa-utils/sort.util';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';
const API_HEADERS = { headers: environment.api.headers };
@Injectable({
providedIn: 'root',
})
export class DeltagareService {
export class DeltagareService extends UnsubscribeDirective {
private _apiBaseUrl = `${environment.api.url}/customerinfo`;
private _apiAvropUrl = `${environment.api.url}/avrop`;
private _currentDeltagareId$ = new BehaviorSubject<string>(null);
// private _fetchAllDeltagare: Observable<DeltagareCompactResponse[]> = this.httpClient
// .get<{ data: DeltagareCompactResponse[] }>(`${this._apiAvropUrl}`, { ...API_HEADERS })
// .pipe(map(response => response.data));
constructor(private httpClient: HttpClient, private errorService: ErrorService) {
super();
this.unsubscribeOnDestroy(
this._currentDeltagareId$
.pipe(
filter(currentDeltagareId => !!currentDeltagareId),
switchMap(currentDeltagareId => this.fetchDeltagare$(currentDeltagareId))
)
.subscribe(deltagare => {
this._deltagare$.next(deltagare);
})
);
}
private _fetchAllDeltagare: Observable<DeltagareCompactResponse[]> = this.httpClient.get<DeltagareCompactResponse[]>(
`${this._apiAvropUrl}`,
{ ...API_HEADERS }
);
private _deltagare$ = new BehaviorSubject<Deltagare>(null);
public deltagare$: Observable<Deltagare> = this._deltagare$.asObservable();
public allDeltagare$: Observable<DeltagareCompact[]> = this._fetchAllDeltagare.pipe(
map(data => data.map(deltagare => mapResponseToDeltagareCompact(deltagare)))
);
public allDeltagare$: Observable<DeltagareCompact[]> = this.httpClient
.get<{ data: AvropResponse[] }>(`${this._apiAvropUrl}`, { ...API_HEADERS })
.pipe(map(response => response.data.map(deltagare => mapResponseToDeltagareCompact(deltagare))));
private _fetchContactInformation$(id: string): Observable<ContactInformation> {
public setCurrentDeltagareId(currentDeltagareId: string): void {
this._deltagare$.next(null);
this._currentDeltagareId$.next(currentDeltagareId);
}
private _fetchContactInformation$(id: string): Observable<ContactInformation | Partial<ContactInformation>> {
return this.httpClient
.get<{ data: ContactInformationResponse }>(`${this._apiBaseUrl}/contact/${id}`, { ...API_HEADERS })
.pipe(map(response => mapResponseToContactInformation(response.data)));
.pipe(
map(response => mapResponseToContactInformation(response.data)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of({});
})
);
}
private _fetchDriversLicense$(id: string): Observable<DriversLicense> {
private _fetchDriversLicense$(id: string): Observable<DriversLicense | Partial<DriversLicense>> {
return this.httpClient
.get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/driverlicense/${id}`, { ...API_HEADERS })
.pipe(map(response => mapResponseToDriversLicense(response.data)));
.pipe(
map(response => mapResponseToDriversLicense(response.data)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of({});
})
);
}
private _fetchHighestEducation$(id: string): Observable<HighestEducation> {
private _fetchHighestEducation$(id: string): Observable<HighestEducation | Partial<HighestEducation>> {
return this.httpClient
.get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/education/highest/${id}`, { ...API_HEADERS })
.pipe(map(response => mapResponseToHighestEducation(response.data)));
.pipe(
map(response => mapResponseToHighestEducation(response.data)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of({});
})
);
}
private _fetchEducations$(id: string): Observable<Education[]> {
@@ -73,20 +109,38 @@ export class DeltagareService {
}
return [];
}),
map(educations => educations.map(utbildning => mapResponseToEducation(utbildning)))
map(
educations => educations.map(utbildning => mapResponseToEducation(utbildning)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of([]);
})
)
);
}
private _fetchTranslator$(id: string): Observable<string> {
return this.httpClient
.get<{ data: TranslatorResponse }>(`${this._apiBaseUrl}/translator/${id}`, { ...API_HEADERS })
.pipe(map(response => (response.data.sprak ? response.data.sprak.beskrivning : null)));
.pipe(
map(response => (response.data.sprak ? response.data.sprak.beskrivning : null)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of('');
})
);
}
private _fetchWorkLanguages$(id: string): Observable<string[]> {
return this.httpClient
.get<{ data: WorkLanguagesResponse }>(`${this._apiBaseUrl}/work/languages/${id}`, { ...API_HEADERS })
.pipe(map(response => (response.data.sprak ? response.data.sprak.map(sprak => sprak.beskrivning) : [])));
.pipe(
map(response => (response.data.sprak ? response.data.sprak.map(sprak => sprak.beskrivning) : [])),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of([]);
})
);
}
private _fetchDisabilities$(id: string): Observable<Disability[]> {
@@ -97,7 +151,11 @@ export class DeltagareService {
response.data.length
? response.data.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning))
: []
)
),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of([]);
})
);
}
@@ -113,23 +171,31 @@ export class DeltagareService {
}
return [];
}),
map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet)))
map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of([]);
})
);
}
public deltagareCompact$(id: string): Observable<DeltagareCompact> {
return this._fetchContactInformation$(id).pipe(
map(contactInformation => ({
id,
fullName: contactInformation.fullName,
}))
);
private _fetchAvropInformation$(id: string): Observable<Avrop | Partial<Avrop>> {
return this.httpClient
.get<{ data: AvropResponse }>(`${this._apiAvropUrl}/${id}`, { ...API_HEADERS })
.pipe(
filter(response => !!response.data),
map(response => mapAvropResponseToAvrop(response.data)),
catchError(error => {
this.errorService.add(errorToCustomError(error));
return of({});
})
);
}
// As TypeScript has some limitations regarding combining Observables this way,
// we need to type it manually when exceeding 6 Observables inside a combineLatest.
// Read: https://github.com/ReactiveX/rxjs/issues/3601#issuecomment-384711601
public deltagare$(id: string): Observable<Deltagare> {
public fetchDeltagare$(id: string): Observable<Deltagare> {
return combineLatest([
this._fetchContactInformation$(id),
this._fetchDriversLicense$(id),
@@ -139,6 +205,7 @@ export class DeltagareService {
this._fetchWorkLanguages$(id),
this._fetchDisabilities$(id),
this._fetchWorkExperiences$(id),
this._fetchAvropInformation$(id),
]).pipe(
map(
([
@@ -150,6 +217,7 @@ export class DeltagareService {
workLanguages,
disabilities,
workExperiences,
avropInformation,
]: [
ContactInformation,
DriversLicense,
@@ -158,7 +226,8 @@ export class DeltagareService {
string,
string[],
Disability[],
WorkExperience[]
WorkExperience[],
Avrop
]) => ({
id,
...contactInformation,
@@ -169,10 +238,9 @@ export class DeltagareService {
workLanguages,
disabilities,
workExperiences,
avropInformation,
})
)
);
}
constructor(private httpClient: HttpClient) {}
}

View File

@@ -25,7 +25,7 @@ function generateAvrop(amount = 10, deltagare) {
avrop.push({
id: faker.datatype.uuid(),
deltagare: currentDeltagare.fullName,
deltagare: `${currentDeltagare.contact.fornamn} ${currentDeltagare.contact.efternamn}`,
genomforandeReferens: faker.datatype.number({ min: 100000000, max: 999999999 }),
orgId: faker.datatype.uuid(),
leverantorId: faker.datatype.number({ min: 1000, max: 99999 }),

View File

@@ -21,6 +21,7 @@ server.use(
'/avrop/tjanster*': '/tjanster$1',
'/avrop/utforandeverksamheter*': '/organizations$1',
'/avrop/kommuner*': '/kommuner$1',
'/avrop/:sokandeId': '/avrop?sokandeId=:sokandeId',
'*page=*': '$1_page=$2',
'*limit=*': '$1_limit=$2',
'*sort=*': '$1_sort=$2',
@@ -57,7 +58,7 @@ router.render = (req, res) => {
let data = res.locals.data;
const deltagareRegex = /(?:\/customerinfo\/)(contact|driverlicense|education\/highest|education|translator|work\/disability|work\/languages|work\/experience)/g;
const isDeltagarePath = deltagareRegex.exec(pathname);
const avropRegex = /(?:\/avrop\/)(tjanster|utforandeverksamheter|kommuner)/g;
const avropRegex = /(?:\/avrop\/)(tjanster|utforandeverksamheter|kommuner|\d)/g;
const isAvropPath = avropRegex.exec(pathname);
if (isDeltagarePath) {
@@ -75,6 +76,8 @@ router.render = (req, res) => {
});
data = newData.filter((value, index, arr) => arr.findIndex(item => item.code === value.code) === index);
} else if (isAvropPath[1]) {
data = data[0];
}
}
@@ -91,9 +94,9 @@ server.listen(8000, () => {
});
function appendMetaData(params, res) {
if (params && params.has('_page')) {
const limit = +params.get('_limit');
const page = +params.get('_page');
if (params && params.has('page')) {
const limit = +params.get('limit');
const page = +params.get('page');
const count = res.get('X-Total-Count');
const totalPages = Math.ceil(count / limit);
return {