feat(deltagare): Implemented role-check and fetching data when needed. (TV-639)
Squashed commit of the following:
commit be46ec00569f3fa23a439d4fc40bfa8dd2f30ea7
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Fri Sep 24 10:18:33 2021 +0200
Fixed error-handling for deltagare
commit e18fe76f68f3894198887bf7fe8793dd34905674
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Fri Sep 24 08:35:40 2021 +0200
Updated tests
commit c8fa577236c1e3a797046d884d91e12e2c1f2c4a
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Fri Sep 24 08:20:18 2021 +0200
Fixed styling and some functionality
commit bfdcaef5c01edbee584ec0a1c1704983578ff6e5
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Thu Sep 23 16:00:10 2021 +0200
refactor
commit 5be380af3aaca3c158dcfb1d084e449558f4e720
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Thu Sep 23 15:59:41 2021 +0200
Update deltagare-tab-reports.component.ts
commit 96c4e36f0ce1a1f607e67ec3a99f18a85221f1d3
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Thu Sep 23 15:34:27 2021 +0200
break up into several components. remove activeTab-observable etc
commit ce2145f09438240d786e58f60e286e6e6f8e7a29
Merge: afc3989 14739fb
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Thu Sep 23 14:05:05 2021 +0200
Merged develop and resolved conflicts
commit afc39892ea33d2e1add92b2d5cb0d9f23b963666
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Thu Sep 23 13:39:01 2021 +0200
Added handledare information to avrop-data and removed id from deltagare-card
commit 1f7454a3cb4af09d3fdcb60c1298677d01a5a64a
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Thu Sep 23 12:59:47 2021 +0200
Implemented more logic inside component instead of service
commit 5af4a9a9f74707169892ce9fe02f7c93285f48cc
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Thu Sep 23 10:50:14 2021 +0200
Added part of role-check to be able to access deltagare-card
commit 7cf7c1d379583788e5fcbef5fff44b158d028f76
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Wed Sep 22 15:07:40 2021 +0200
WIP
commit 8466394d617fa573663f3d199414354394d22b31
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Wed Sep 22 11:42:32 2021 +0200
Moved around content for deltagare-card
This commit is contained in:
@@ -11,9 +11,7 @@
|
|||||||
{
|
{
|
||||||
"enforceBuildableLibDependency": true,
|
"enforceBuildableLibDependency": true,
|
||||||
"allow": [],
|
"allow": [],
|
||||||
"depConstraints": [
|
"depConstraints": [{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }]
|
||||||
{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
],
|
],
|
||||||
"parserOptions": { "project": ["apps/mina-sidor-fa/tsconfig.*?.json"] },
|
"parserOptions": { "project": ["apps/mina-sidor-fa/tsconfig.*?.json"] },
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"max-len": [
|
||||||
|
1,
|
||||||
|
140,
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"ignorePattern": "^import\\s.+\\sfrom\\s.+;$",
|
||||||
|
"ignoreUrls": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"@angular-eslint/directive-selector": [
|
"@angular-eslint/directive-selector": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ activeFeatures.forEach(feature => {
|
|||||||
case Feature.ADMINISTRATION:
|
case Feature.ADMINISTRATION:
|
||||||
routes.push({
|
routes.push({
|
||||||
path: 'administration',
|
path: 'administration',
|
||||||
data: { title: 'Administration', expectedRole: RoleEnum.MSFA_AuthAdmin },
|
data: { title: 'Administration', expectedRoles: [RoleEnum.MSFA_AuthAdmin] },
|
||||||
loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule),
|
loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule),
|
||||||
canActivate: [AuthGuard, OrganizationGuard, RoleGuard],
|
canActivate: [AuthGuard, OrganizationGuard, RoleGuard],
|
||||||
});
|
});
|
||||||
@@ -50,7 +50,7 @@ activeFeatures.forEach(feature => {
|
|||||||
case Feature.AVROP:
|
case Feature.AVROP:
|
||||||
routes.push({
|
routes.push({
|
||||||
path: 'nya-deltagare',
|
path: 'nya-deltagare',
|
||||||
data: { title: 'Nya deltagare', expectedRole: RoleEnum.MSFA_ReceiveDeltagare },
|
data: { title: 'Nya deltagare', expectedRoles: [RoleEnum.MSFA_ReceiveDeltagare] },
|
||||||
loadChildren: () => import('./pages/avrop/avrop.module').then(m => m.AvropModule),
|
loadChildren: () => import('./pages/avrop/avrop.module').then(m => m.AvropModule),
|
||||||
canActivate: [AuthGuard, OrganizationGuard, RoleGuard],
|
canActivate: [AuthGuard, OrganizationGuard, RoleGuard],
|
||||||
});
|
});
|
||||||
@@ -58,7 +58,7 @@ activeFeatures.forEach(feature => {
|
|||||||
case Feature.DELTAGARE:
|
case Feature.DELTAGARE:
|
||||||
routes.push({
|
routes.push({
|
||||||
path: 'deltagare',
|
path: 'deltagare',
|
||||||
data: { title: 'Deltagare', expectedRole: RoleEnum.MSFA_ReportAndPlanning },
|
data: { title: 'Deltagare', expectedRoles: [RoleEnum.MSFA_ReportAndPlanning, RoleEnum.MSFA_ReceiveDeltagare] },
|
||||||
loadChildren: () => import('./pages/deltagare/deltagare.module').then(m => m.DeltagareModule),
|
loadChildren: () => import('./pages/deltagare/deltagare.module').then(m => m.DeltagareModule),
|
||||||
canActivate: [AuthGuard, OrganizationGuard, RoleGuard],
|
canActivate: [AuthGuard, OrganizationGuard, RoleGuard],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { DOCUMENT } from '@angular/common';
|
import { DOCUMENT } from '@angular/common';
|
||||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||||
import { environment } from '@msfa-environment';
|
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
|
||||||
import { filter, map, mergeMap, switchMap } from 'rxjs/operators';
|
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { environment } from '@msfa-environment';
|
||||||
|
import { filter, map, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-root',
|
selector: 'msfa-root',
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
max-width: 400px !important;
|
max-width: 400px !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__close-button {
|
&__close-button {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { DeltagareCompact, DeltagareCompactData } from '@msfa-models/deltagare.model';
|
import { DeltagareCompact, DeltagareCompactData } from '@msfa-models/deltagare.model';
|
||||||
import { Sort } from '@msfa-models/sort.model';
|
import { Sort } from '@msfa-models/sort.model';
|
||||||
import { DeltagareService } from '@msfa-services/api/deltagare.service';
|
import { DeltagareService } from '@msfa-services/deltagare.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<div class="deltagare-tab-experiences">
|
||||||
|
<div class="deltagare-tab-experiences__tab-column">
|
||||||
|
<h2>Arbetslivserfarenhet</h2>
|
||||||
|
<ng-container *ngIf="firstVisibleWorkExperiences;">
|
||||||
|
<ul
|
||||||
|
class="deltagare-tab-experiences__experience-list"
|
||||||
|
*ngIf="firstVisibleWorkExperiences.length; else emptyText;"
|
||||||
|
>
|
||||||
|
<li *ngFor="let workExperience of firstVisibleWorkExperiences">
|
||||||
|
<h3 class="deltagare-tab-experiences__subheading">{{ workExperience.employer }}</h3>
|
||||||
|
<digi-typography-time [afDateTime]="workExperience.dateFrom"></digi-typography-time> -
|
||||||
|
<digi-typography-time [afDateTime]="workExperience.dateTo"></digi-typography-time><br />
|
||||||
|
{{ workExperience.profession }}
|
||||||
|
<p>{{ workExperience.description }}</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="hiddenWorkExperiences">
|
||||||
|
<digi-ng-layout-expansion-panel
|
||||||
|
class="deltagare-tab-experiences__accordion"
|
||||||
|
[afExpanded]="accordionExpanded"
|
||||||
|
(click)="toggleAccordionExpanded()"
|
||||||
|
*ngIf="hiddenWorkExperiences.length"
|
||||||
|
>
|
||||||
|
<span class="deltagare-tab-experiences__accordion-trigger" data-slot-trigger
|
||||||
|
>{{ accordionExpanded ? 'Dölj' : 'Visa' }} fler arbetsgivare</span
|
||||||
|
>
|
||||||
|
<ul class="deltagare-tab-experiences__experience-list">
|
||||||
|
<li *ngFor="let workExperience of hiddenWorkExperiences">
|
||||||
|
<h3 class="deltagare-tab-experiences__subheading">{{ workExperience.employer }}</h3>
|
||||||
|
<digi-typography-time [afDateTime]="workExperience.dateFrom"></digi-typography-time> -
|
||||||
|
<digi-typography-time [afDateTime]="workExperience.dateTo"></digi-typography-time><br />
|
||||||
|
{{ workExperience.profession }}
|
||||||
|
<p>{{ workExperience.description }}</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</digi-ng-layout-expansion-panel>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="deltagare-tab-experiences__tab-column">
|
||||||
|
<h2>Utbildning</h2>
|
||||||
|
<dl *ngIf="highestEducation">
|
||||||
|
<dt>Högsta utbildningsnivå:</dt>
|
||||||
|
<dd>
|
||||||
|
<ng-container *ngIf="highestEducation.level; else emptyText">
|
||||||
|
{{ highestEducation.level.description }}: {{ highestEducation.sunKod.description }}
|
||||||
|
</ng-container>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<ng-container *ngIf="educations">
|
||||||
|
<h3 class="deltagare-tab-experiences__subheading deltagare-tab-experiences__subheading--with-margin">
|
||||||
|
Utbildningar:
|
||||||
|
</h3>
|
||||||
|
<ul class="deltagare-tab-experiences__experience-list" *ngIf="educations.length; else emptyText">
|
||||||
|
<li *ngFor="let education of educations">
|
||||||
|
<h4 class="deltagare-tab-experiences__subheading">{{ education.organizer }}</h4>
|
||||||
|
<digi-typography-time [afDateTime]="education.dateFrom"></digi-typography-time> -
|
||||||
|
<digi-typography-time [afDateTime]="education.dateFrom"></digi-typography-time><br />
|
||||||
|
{{ education.education}}
|
||||||
|
<p>{{ education.description }}</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="deltagare-tab-experiences__tab-column" *ngIf="driversLicense">
|
||||||
|
<h2>Körkort</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Har körkort</dt>
|
||||||
|
<dd>{{driversLicense.licenses.length ? 'Ja' : 'Nej'}}</dd>
|
||||||
|
<ng-container *ngIf="driversLicense.licenses.length">
|
||||||
|
<dt>Körkortsklasser</dt>
|
||||||
|
<dd>{{driversLicense.licenses.join(', ')}}</dd>
|
||||||
|
<dt>Tillgång till bil</dt>
|
||||||
|
<dd>{{driversLicense.accessToCar ? 'Ja' : 'Nej'}}</dd>
|
||||||
|
</ng-container>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #emptyText>
|
||||||
|
<span>Info saknas</span>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@import 'mixins/list';
|
||||||
|
|
||||||
|
.deltagare-tab-experiences {
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
&__tab-column {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subheading {
|
||||||
|
font-size: var(--digi--typography--font-size--desktop);
|
||||||
|
font-weight: var(--digi--typography--font-weight--semibold);
|
||||||
|
margin: var(--digi--layout--gutter--s) 0 0;
|
||||||
|
|
||||||
|
&--with-margin {
|
||||||
|
font-size: var(--digi--typography--font-size--h3);
|
||||||
|
margin-bottom: var(--digi--layout--gutter--s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__experience-list {
|
||||||
|
@include msfa__reset-list;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accordion {
|
||||||
|
display: block;
|
||||||
|
margin-top: var(--digi--layout--gutter);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accordion-trigger {
|
||||||
|
font-weight: var(--digi--typography--font-weight--semibold);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { DeltagareTabExperiencesComponent } from './deltagare-tab-experiences.component';
|
||||||
|
|
||||||
|
describe('DeltagareTabExperiencesComponent', () => {
|
||||||
|
let component: DeltagareTabExperiencesComponent;
|
||||||
|
let fixture: ComponentFixture<DeltagareTabExperiencesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [DeltagareTabExperiencesComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DeltagareTabExperiencesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
import { DriversLicense } from '@msfa-models/drivers-license.model';
|
||||||
|
import { Education } from '@msfa-models/education.model';
|
||||||
|
import { HighestEducation } from '@msfa-models/highest-education.model';
|
||||||
|
import { WorkExperience } from '@msfa-models/work-experience.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'msfa-deltagare-tab-experiences',
|
||||||
|
templateUrl: './deltagare-tab-experiences.component.html',
|
||||||
|
styleUrls: ['./deltagare-tab-experiences.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DeltagareTabExperiencesComponent {
|
||||||
|
@Input() workExperiences: WorkExperience[];
|
||||||
|
@Input() highestEducation: HighestEducation;
|
||||||
|
@Input() educations: Education[];
|
||||||
|
@Input() driversLicense: DriversLicense;
|
||||||
|
|
||||||
|
accordionExpanded = false;
|
||||||
|
|
||||||
|
get firstVisibleWorkExperiences(): WorkExperience[] {
|
||||||
|
return this.workExperiences?.slice(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hiddenWorkExperiences(): WorkExperience[] {
|
||||||
|
return this.workExperiences?.slice(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAccordionExpanded(): void {
|
||||||
|
this.accordionExpanded = !this.accordionExpanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<div class="deltagare-tab-personal-information">
|
||||||
|
<div *ngIf="contactInformation" class="deltagare-tab-personal-information__tab-column">
|
||||||
|
<h2>Personuppgifter</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Namn:</dt>
|
||||||
|
<dd *ngIf="contactInformation.fullName; else emptyDD">{{ contactInformation.fullName }}</dd>
|
||||||
|
<dt>Personnummer:</dt>
|
||||||
|
<dd *ngIf="contactInformation.ssn; else emptyDD">
|
||||||
|
<msfa-hide-text
|
||||||
|
symbols="********-****"
|
||||||
|
[changingText]="contactInformation.ssn"
|
||||||
|
ariaLabelType="personnummer"
|
||||||
|
></msfa-hide-text>
|
||||||
|
</dd>
|
||||||
|
<ng-container *ngFor="let address of contactInformation.addresses">
|
||||||
|
<dt>{{address.type}}:</dt>
|
||||||
|
<dd>
|
||||||
|
<address>
|
||||||
|
{{ address.street }}<br />
|
||||||
|
{{ address.postalCode }} {{ address.city }}
|
||||||
|
</address>
|
||||||
|
</dd>
|
||||||
|
</ng-container>
|
||||||
|
<dt>Telefon:</dt>
|
||||||
|
<ng-container *ngIf="contactInformation.phoneNumbers?.length; else emptyDD">
|
||||||
|
<ng-container *ngFor="let phoneNumber of contactInformation.phoneNumbers">
|
||||||
|
<dd>{{ phoneNumber.type }}: {{phoneNumber.number}}</dd>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<dt>E-postadress:</dt>
|
||||||
|
<dd *ngIf="contactInformation.email; else emptyDD">
|
||||||
|
<a href="mailto:{{contactInformation.email}}">{{ contactInformation.email }}</a>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="avropInformation">
|
||||||
|
<div class="deltagare-tab-personal-information__tab-column">
|
||||||
|
<h2>Om tjänsten</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Tillhörande tjänst:</dt>
|
||||||
|
<dd *ngIf="avropInformation.tjanst; else emptyDD">{{ avropInformation.tjanst }}</dd>
|
||||||
|
<dt>Datum för tjänstens början:</dt>
|
||||||
|
<dd *ngIf="avropInformation.startDate; else emptyDD">
|
||||||
|
<digi-typography-time [afDateTime]="avropInformation.startDate"></digi-typography-time>
|
||||||
|
</dd>
|
||||||
|
<dt>Datum för tjänstens slut:</dt>
|
||||||
|
<dd *ngIf="avropInformation.endDate; else emptyDD">
|
||||||
|
<digi-typography-time [afDateTime]="avropInformation.endDate"></digi-typography-time>
|
||||||
|
</dd>
|
||||||
|
<dt>Deltagandefrekvens:</dt>
|
||||||
|
<dd *ngIf="avropInformation.participationFrequency; else emptyDD">
|
||||||
|
{{ avropInformation.participationFrequency }}
|
||||||
|
</dd>
|
||||||
|
<dt>Nivå:</dt>
|
||||||
|
<dd *ngIf="avropInformation.participationFrequency; else emptyDD">{{ avropInformation.trackName }}</dd>
|
||||||
|
<dt>Utförande verksamhet:</dt>
|
||||||
|
<dd *ngIf="avropInformation.utforandeVerksamhet; else emptyDD">{{ avropInformation.utforandeVerksamhet }}</dd>
|
||||||
|
<dt>Utförande adress:</dt>
|
||||||
|
<dd *ngIf="avropInformation.utforandeAdress; else emptyDD">{{ avropInformation.utforandeAdress }}</dd>
|
||||||
|
<dt>Genomförandereferens:</dt>
|
||||||
|
<dd *ngIf="avropInformation.genomforandeReferens; else emptyDD">{{ avropInformation.genomforandeReferens }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="deltagare-tab-personal-information__tab-column">
|
||||||
|
<h2>Handledare</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Tilldelad handledare:</dt>
|
||||||
|
<dd *ngIf="avropInformation.handledare; else emptyDD">{{ avropInformation.handledare }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #emptyDD>
|
||||||
|
<dd>Info saknas</dd>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.deltagare-tab-personal-information {
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
&__tab-column {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { DeltagareTabPersonalInformationComponent } from './deltagare-tab-personal-information.component';
|
||||||
|
|
||||||
|
describe('DeltagareTabPersonalInformationComponent', () => {
|
||||||
|
let component: DeltagareTabPersonalInformationComponent;
|
||||||
|
let fixture: ComponentFixture<DeltagareTabPersonalInformationComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [DeltagareTabPersonalInformationComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DeltagareTabPersonalInformationComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
|
import { ContactInformation } from '@msfa-models/contact-information.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'msfa-deltagare-tab-personal-information',
|
||||||
|
templateUrl: './deltagare-tab-personal-information.component.html',
|
||||||
|
styleUrls: ['./deltagare-tab-personal-information.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DeltagareTabPersonalInformationComponent {
|
||||||
|
@Input() contactInformation: ContactInformation;
|
||||||
|
@Input() avropInformation: Avrop;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<div *ngIf="reportsData$ | async as reportsData; else loadingRef" class="deltagare-tab-reports">
|
||||||
|
<form [formGroup]="reportPickerFormGroup" class="deltagare-tab-reports__form">
|
||||||
|
<h3>Skapa ny rapport</h3>
|
||||||
|
<p>Här kan du skicka rapporter om deltagaren till arbetsförmedlingen.</p>
|
||||||
|
<digi-ng-form-select
|
||||||
|
[formControlName]="reportsFormControlName"
|
||||||
|
afLabel="Välj rapporttyp"
|
||||||
|
afPlaceholder="Välj rapporttyp"
|
||||||
|
[afSelectItems]="selectableReportTypes"
|
||||||
|
[afDisableValidStyle]="true"
|
||||||
|
[afRequired]="true"
|
||||||
|
[afInvalid]="reportsFormControl.invalid && reportsFormControl.touched"
|
||||||
|
>
|
||||||
|
</digi-ng-form-select>
|
||||||
|
<digi-form-validation-message af-variation="error" *ngIf="reportsFormControl.invalid && reportsFormControl.touched">
|
||||||
|
Du måste välja en rapporttyp
|
||||||
|
</digi-form-validation-message>
|
||||||
|
</form>
|
||||||
|
<div class="deltagare-tab-reports__cta-wrapper" *ngIf="currentDeltagareId$ | async as currentDeltagareId">
|
||||||
|
<digi-ng-link-button
|
||||||
|
afText="Skapa ny rapport"
|
||||||
|
(click)="onFormSubmitted($event, reportsFormControl.value, currentDeltagareId)"
|
||||||
|
></digi-ng-link-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Inskickade rapporter</h3>
|
||||||
|
<msfa-reports
|
||||||
|
[reports]="reportsData.data"
|
||||||
|
[paginationMeta]="reportsData.meta"
|
||||||
|
(paginated)="setNewPage($event)"
|
||||||
|
></msfa-reports>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingRef>
|
||||||
|
<msfa-loader type="padded"></msfa-loader>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
@import 'variables/gutters';
|
||||||
|
|
||||||
|
.deltagare-tab-reports {
|
||||||
|
&__form {
|
||||||
|
max-width: var(--digi--typography--text--max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__cta-wrapper {
|
||||||
|
margin-top: $digi--layout--gutter--l;
|
||||||
|
margin-bottom: $digi--layout--gutter--xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { LoaderModule } from '@msfa-shared/components/loader/loader.module';
|
||||||
|
import { DeltagareTabReportsComponent } from './deltagare-tab-reports.component';
|
||||||
|
|
||||||
|
describe('DeltagareTabReportsComponent', () => {
|
||||||
|
let component: DeltagareTabReportsComponent;
|
||||||
|
let fixture: ComponentFixture<DeltagareTabReportsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [DeltagareTabReportsComponent],
|
||||||
|
imports: [RouterTestingModule, HttpClientTestingModule, ReactiveFormsModule, LoaderModule],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DeltagareTabReportsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { FormSelectItem } from '@af/digi-ng/_form/form-select';
|
||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ReportsData } from '@msfa-models/reports.model';
|
||||||
|
import { DeltagareCardService } from '@msfa-services/deltagare-card.service';
|
||||||
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'msfa-deltagare-tab-reports',
|
||||||
|
templateUrl: './deltagare-tab-reports.component.html',
|
||||||
|
styleUrls: ['./deltagare-tab-reports.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DeltagareTabReportsComponent {
|
||||||
|
private _limit$ = new BehaviorSubject<number>(20);
|
||||||
|
private _page$ = new BehaviorSubject<number>(1);
|
||||||
|
private _type$ = new BehaviorSubject<FormSelectItem>(null);
|
||||||
|
readonly reportsFormControlName = 'reports';
|
||||||
|
|
||||||
|
public currentDeltagareId$: Observable<string> = this.activatedRoute.params.pipe(
|
||||||
|
map(params => params.deltagareId as string),
|
||||||
|
distinctUntilChanged(([prevDeltagareId], [currDeltagareId]) => prevDeltagareId === currDeltagareId)
|
||||||
|
);
|
||||||
|
|
||||||
|
reportsData$: Observable<ReportsData> = combineLatest([this.currentDeltagareId$, this._limit$, this._page$]).pipe(
|
||||||
|
switchMap(([deltagareId, limit, page]) => this.deltagareCardService.fetchReports$(limit, page, deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
reportPickerFormGroup: FormGroup = this.formBuilder.group({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
reports: this.formBuilder.control('', [Validators.required]),
|
||||||
|
});
|
||||||
|
|
||||||
|
selectableReportTypes: Array<FormSelectItem> = [
|
||||||
|
{ name: 'Avvikelse', value: 'avvikelse' },
|
||||||
|
{ name: 'Gemensam Planering', value: 'planering' },
|
||||||
|
];
|
||||||
|
selectedReportType: FormSelectItem;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private deltagareCardService: DeltagareCardService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get reportsFormControl(): AbstractControl | null {
|
||||||
|
return this.reportPickerFormGroup?.get(this.reportsFormControlName);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmitted(event: Event, reportType: string, deltagareId: string): void {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
switch (reportType) {
|
||||||
|
case 'planering':
|
||||||
|
if (this.reportsFormControl.valid) {
|
||||||
|
this.router.navigate(['/deltagare/planering', deltagareId]).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'avvikelse':
|
||||||
|
if (this.reportsFormControl.valid) {
|
||||||
|
this.router.navigate(['/deltagare/rapportera', deltagareId]).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reportsFormControl.markAsTouched();
|
||||||
|
|
||||||
|
if (!this.selectableReportTypes || this.reportPickerFormGroup.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedReportType = this.selectableReportTypes.find(report => {
|
||||||
|
return report.value === this.reportsFormControl.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._type$.next(selectedReportType);
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewPage(page: number): void {
|
||||||
|
this._page$.next(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="deltagare-tab-sensitive-information">
|
||||||
|
<div class="deltagare-tab-sensitive-information__tab-column" *ngIf="disabilities">
|
||||||
|
<h2>Funktionsnedsättningar</h2>
|
||||||
|
|
||||||
|
<dl *ngIf="disabilities.length; else noDisabilities">
|
||||||
|
<ng-container *ngFor="let disability of disabilities; let index = index">
|
||||||
|
<dt>Funktionsnedsättning {{index + 1}}</dt>
|
||||||
|
<dd>
|
||||||
|
<span>{{ disability.title }}</span>
|
||||||
|
<digi-ng-popover
|
||||||
|
*ngIf="disability.description"
|
||||||
|
class="deltagare-tab-sensitive-information__popover"
|
||||||
|
[afRelativeIconSize]="true"
|
||||||
|
>{{ disability.description }}</digi-ng-popover
|
||||||
|
>
|
||||||
|
</dd>
|
||||||
|
</ng-container>
|
||||||
|
</dl>
|
||||||
|
<ng-template #noDisabilities>
|
||||||
|
<p>Deltagaren har inga funktionsnedsättningar registrerad.</p>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
<div class="deltagare-tab-sensitive-information__tab-column" *ngIf="avropInformation">
|
||||||
|
<h2>Språk</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Behov av tolk:</dt>
|
||||||
|
<dd>{{avropInformation.tolkbehov ? 'Ja (' + avropInformation.tolkbehov + ')' : 'Nej'}}</dd>
|
||||||
|
<dt>Behov av språkstöd:</dt>
|
||||||
|
<dd>{{avropInformation.sprakstod ? 'Ja (' + avropInformation.sprakstod + ')' : 'Nej'}}</dd>
|
||||||
|
<ng-container *ngIf="workLanguages">
|
||||||
|
<dt>Språk som kan användas på jobbet:</dt>
|
||||||
|
<dd>{{ workLanguages.length ? workLanguages.join(', ') : 'Info saknas'}}</dd>
|
||||||
|
</ng-container>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@import 'variables/z-index';
|
||||||
|
|
||||||
|
.deltagare-tab-sensitive-information {
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
&__tab-column {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__popover {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: var(--digi--layout--gutter--s);
|
||||||
|
|
||||||
|
::ng-deep .digi-ng-popover__container {
|
||||||
|
z-index: $msfa__z-index-popover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DeltagareTabSensitiveInformationComponent } from './deltagare-tab-sensitive-information.component';
|
||||||
|
|
||||||
|
describe('DeltagareTabSensitiveInformationComponent', () => {
|
||||||
|
let component: DeltagareTabSensitiveInformationComponent;
|
||||||
|
let fixture: ComponentFixture<DeltagareTabSensitiveInformationComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ DeltagareTabSensitiveInformationComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DeltagareTabSensitiveInformationComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
|
import { Disability } from '@msfa-models/disability.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'msfa-deltagare-tab-sensitive-information',
|
||||||
|
templateUrl: './deltagare-tab-sensitive-information.component.html',
|
||||||
|
styleUrls: ['./deltagare-tab-sensitive-information.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DeltagareTabSensitiveInformationComponent {
|
||||||
|
@Input() avropInformation: Avrop;
|
||||||
|
@Input() workLanguages: string[];
|
||||||
|
@Input() disabilities: Disability[];
|
||||||
|
}
|
||||||
@@ -1,240 +1,70 @@
|
|||||||
<msfa-layout>
|
<msfa-layout>
|
||||||
<digi-typography>
|
<digi-typography>
|
||||||
<section *ngIf="deltagare$ | async as deltagare; else loadingRef" class="deltagare-card">
|
<section class="deltagare-card" *ngIf="activeTab$ | async as activeTab">
|
||||||
<header class="deltagare-card__header">
|
<header class="deltagare-card__header">
|
||||||
<msfa-back-link [route]="['/deltagare']">Tillbaka till deltagarlistan</msfa-back-link>
|
<msfa-back-link [route]="['/deltagare']">Tillbaka till deltagarlistan</msfa-back-link>
|
||||||
<h1>Deltagarinformation</h1>
|
<h1>Deltagarinformation</h1>
|
||||||
</header>
|
</header>
|
||||||
<digi-navigation-tabs af-aria-label="Deltagarinformation">
|
<digi-navigation-tabs af-aria-label="Deltagarinformation">
|
||||||
<digi-navigation-tab af-aria-label="Deltagare & tjänst" af-id="deltagare-card-personuppgifter">
|
<digi-navigation-tab
|
||||||
<div class="deltagare-card__tab-contents">
|
(afOnToggle)="setActiveTab(0)"
|
||||||
<div class="deltagare-card__tab-column">
|
af-aria-label="Deltagare & tjänst"
|
||||||
<h2>Personuppgifter</h2>
|
af-id="deltagare-card-personal-information"
|
||||||
<dl>
|
*ngIf="deltagareTjanstVisible"
|
||||||
<dt>Namn:</dt>
|
>
|
||||||
<dd *ngIf="deltagare.fullName; else emptyDD">{{ deltagare.fullName }}</dd>
|
<ng-container *ngIf="activeTab === '0'">
|
||||||
<dt>Personnummer:</dt>
|
<msfa-deltagare-tab-personal-information
|
||||||
<dd *ngIf="deltagare.ssn; else emptyDD">
|
*ngIf="(tab0Loading$ | async) === false; else loadingRef"
|
||||||
<msfa-hide-text
|
class="deltagare-card__tab-contents"
|
||||||
symbols="********-****"
|
[contactInformation]="contactInformation$ | async"
|
||||||
[changingText]="deltagare.ssn"
|
[avropInformation]="avropInformation$ | async"
|
||||||
ariaLabelType="personnummer"
|
></msfa-deltagare-tab-personal-information>
|
||||||
></msfa-hide-text>
|
</ng-container>
|
||||||
</dd>
|
|
||||||
<ng-container *ngFor="let address of deltagare.addresses">
|
|
||||||
<dt>{{address.type}}:</dt>
|
|
||||||
<dd>
|
|
||||||
<address>
|
|
||||||
{{ address.street }}<br />
|
|
||||||
{{ address.postalCode }} {{ address.city }}
|
|
||||||
</address>
|
|
||||||
</dd>
|
|
||||||
</ng-container>
|
|
||||||
<dt>Telefon:</dt>
|
|
||||||
<ng-container *ngIf="deltagare.phoneNumbers?.length; else emptyDD">
|
|
||||||
<ng-container *ngFor="let phoneNumber of deltagare.phoneNumbers">
|
|
||||||
<dd>{{ phoneNumber.type }}: {{phoneNumber.number}}</dd>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
<dt>E-postadress:</dt>
|
|
||||||
<dd *ngIf="deltagare.email; else emptyDD">
|
|
||||||
<a href="mailto:{{deltagare.email}}">{{ deltagare.email }}</a>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div class="deltagare-card__tab-column">
|
|
||||||
<h2>Om tjänsten</h2>
|
|
||||||
<dl>
|
|
||||||
<dt>Tillhörande tjänst:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.tjanst; else emptyDD">{{ deltagare.avropInformation.tjanst }}</dd>
|
|
||||||
<dt>Datum för tjänstens början:</dt>
|
|
||||||
<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.avropInformation.endDate; else emptyDD">
|
|
||||||
<digi-typography-time [afDateTime]="deltagare.avropInformation.endDate"></digi-typography-time>
|
|
||||||
</dd>
|
|
||||||
<dt>Deltagandefrekvens:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.participationFrequency; else emptyDD">
|
|
||||||
{{ deltagare.avropInformation.participationFrequency }}
|
|
||||||
</dd>
|
|
||||||
<dt>Nivå:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.participationFrequency; else emptyDD">
|
|
||||||
{{ deltagare.avropInformation.trackName }}
|
|
||||||
</dd>
|
|
||||||
<dt>Utförande verksamhet:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.utforandeVerksamhet; else emptyDD">
|
|
||||||
{{ deltagare.avropInformation.utforandeVerksamhet }}
|
|
||||||
</dd>
|
|
||||||
<dt>Utförande adress:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.utforandeAdress; else emptyDD">
|
|
||||||
{{ deltagare.avropInformation.utforandeAdress }}
|
|
||||||
</dd>
|
|
||||||
<dt>Genomförandereferens:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.genomforandeReferens; else emptyDD">
|
|
||||||
{{ deltagare.avropInformation.genomforandeReferens }}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div class="deltagare-card__tab-column">
|
|
||||||
<h2>Handledare</h2>
|
|
||||||
<dl>
|
|
||||||
<dt>Tilldelad handledare:</dt>
|
|
||||||
<dd *ngIf="deltagare.avropInformation.handledare; else emptyDD">
|
|
||||||
{{ deltagare.avropInformation.handledare }}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</digi-navigation-tab>
|
</digi-navigation-tab>
|
||||||
<digi-navigation-tab af-aria-label="Rapportering" af-id="deltagare-card-rapportering">
|
|
||||||
<div class="deltagare-card__select-report">
|
|
||||||
<h3>Skapa ny rapport</h3>
|
|
||||||
<p>Här kan du skicka rapporter om deltagaren till arbetsförmedlingen.</p>
|
|
||||||
<form [formGroup]="reportPickerFormGroup">
|
|
||||||
<digi-ng-form-select
|
|
||||||
[formControlName]="reportsFormControlName"
|
|
||||||
afLabel="Välj rapporttyp"
|
|
||||||
afPlaceholder="Välj rapporttyp"
|
|
||||||
[afSelectItems]="selectableReportTypes"
|
|
||||||
[afDisableValidStyle]="true"
|
|
||||||
[afRequired]="true"
|
|
||||||
[afInvalid]="reportsFormControl.invalid && reportsFormControl.touched"
|
|
||||||
>
|
|
||||||
</digi-ng-form-select>
|
|
||||||
<digi-form-validation-message
|
|
||||||
af-variation="error"
|
|
||||||
*ngIf="reportsFormControl.invalid && reportsFormControl.touched"
|
|
||||||
>
|
|
||||||
Du måste välja en rapporttyp
|
|
||||||
</digi-form-validation-message>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="deltagare-card__cta-wrapper">
|
|
||||||
<digi-ng-link-button
|
|
||||||
afText="Skapa ny rapport"
|
|
||||||
(click)="onFormSubmitted($event, reportsFormControl.value, deltagare.id)"
|
|
||||||
></digi-ng-link-button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>Inskickade rapporter</h3>
|
|
||||||
<msfa-reports
|
|
||||||
*ngIf="reportsData$ | async as reportsData, else loadingRef"
|
|
||||||
[reports]="reportsData.data"
|
|
||||||
[paginationMeta]="reportsData?.meta"
|
|
||||||
(paginated)="setNewPage($event)"
|
|
||||||
></msfa-reports>
|
|
||||||
</div>
|
|
||||||
</digi-navigation-tab>
|
|
||||||
<digi-navigation-tab af-aria-label="Erfarenheter" af-id="deltagare-card-matchningsuppgifter">
|
|
||||||
<div class="deltagare-card__tab-contents">
|
|
||||||
<div class="deltagare-card__tab-column">
|
|
||||||
<h2>Arbetslivserfarenhet</h2>
|
|
||||||
<ng-container *ngIf="firstVisibleWorkExperiences$ | async as firstVisibleWorkExperiences;">
|
|
||||||
<ul class="deltagare-card__experience-list" *ngIf="firstVisibleWorkExperiences.length; else emptyText;">
|
|
||||||
<li *ngFor="let workExperience of firstVisibleWorkExperiences">
|
|
||||||
<h3 class="deltagare-card__subheading">{{ workExperience.employer }}</h3>
|
|
||||||
<digi-typography-time [afDateTime]="workExperience.dateFrom"></digi-typography-time> -
|
|
||||||
<digi-typography-time [afDateTime]="workExperience.dateTo"></digi-typography-time><br />
|
|
||||||
{{ workExperience.profession }}
|
|
||||||
<p>{{ workExperience.description }}</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="hiddenWorkExperiences$ | async as hiddenWorkExperiences">
|
|
||||||
<digi-ng-layout-expansion-panel
|
|
||||||
class="deltagare-card__accordion"
|
|
||||||
[afExpanded]="accordionExpanded"
|
|
||||||
(click)="toggleAccordionExpanded()"
|
|
||||||
*ngIf="hiddenWorkExperiences.length"
|
|
||||||
>
|
|
||||||
<span class="deltagare-card__accordion-trigger" data-slot-trigger
|
|
||||||
>{{ accordionExpanded ? 'Dölj' : 'Visa' }} fler arbetsgivare</span
|
|
||||||
>
|
|
||||||
<ul class="deltagare-card__experience-list">
|
|
||||||
<li *ngFor="let workExperience of hiddenWorkExperiences">
|
|
||||||
<h3 class="deltagare-card__subheading">{{ workExperience.employer }}</h3>
|
|
||||||
<digi-typography-time [afDateTime]="workExperience.dateFrom"></digi-typography-time> -
|
|
||||||
<digi-typography-time [afDateTime]="workExperience.dateTo"></digi-typography-time><br />
|
|
||||||
{{ workExperience.profession }}
|
|
||||||
<p>{{ workExperience.description }}</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</digi-ng-layout-expansion-panel>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="deltagare-card__tab-column">
|
|
||||||
<h2>Utbildning</h2>
|
|
||||||
<dl>
|
|
||||||
<dt>Högsta utbildningsnivå:</dt>
|
|
||||||
<dd *ngIf="deltagare.highestEducation.level; else emptyDD">
|
|
||||||
{{ deltagare.highestEducation.level.description }}: {{ deltagare.highestEducation.sunKod.description
|
|
||||||
}}
|
|
||||||
</dd>
|
|
||||||
<h3 class="deltagare-card__subheading deltagare-card__subheading--with-margin">Utbildningar:</h3>
|
|
||||||
<ul class="deltagare-card__experience-list" *ngIf="deltagare.educations.length; else emptyText">
|
|
||||||
<li *ngFor="let education of deltagare.educations">
|
|
||||||
<h4 class="deltagare-card__subheading">{{ education.organizer }}</h4>
|
|
||||||
<digi-typography-time [afDateTime]="education.dateFrom"></digi-typography-time> -
|
|
||||||
<digi-typography-time [afDateTime]="education.dateFrom"></digi-typography-time><br />
|
|
||||||
{{ education.education}}
|
|
||||||
<p>{{ education.description }}</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div class="deltagare-card__tab-column">
|
|
||||||
<h2>Körkort</h2>
|
|
||||||
<dl>
|
|
||||||
<dt>Har körkort</dt>
|
|
||||||
<dd>{{deltagare.driversLicense.licenses.length ? 'Ja' : 'Nej'}}</dd>
|
|
||||||
<ng-container *ngIf="deltagare.driversLicense.licenses.length">
|
|
||||||
<dt>Körkortsklasser</dt>
|
|
||||||
<dd>{{deltagare.driversLicense.licenses.join(', ')}}</dd>
|
|
||||||
<dt>Tillgång till bil</dt>
|
|
||||||
<dd>{{deltagare.driversLicense.accessToCar ? 'Ja' : 'Nej'}}</dd>
|
|
||||||
</ng-container>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</digi-navigation-tab>
|
|
||||||
<digi-navigation-tab af-aria-label="Känsliga uppgifter">
|
|
||||||
<div class="deltagare-card__tab-contents">
|
|
||||||
<div class="deltagare-card__tab-column">
|
|
||||||
<h2>Funktionsnedsättningar</h2>
|
|
||||||
|
|
||||||
<dl *ngIf="deltagare.disabilities.length; else emptyText">
|
<digi-navigation-tab
|
||||||
<ng-container *ngFor="let disability of deltagare.disabilities; let index = index">
|
(afOnToggle)="setActiveTab(1)"
|
||||||
<dt>Funktionsnedsättning {{index + 1}}</dt>
|
af-aria-label="Rapportering"
|
||||||
<dd>
|
af-id="deltagare-card-reports"
|
||||||
<span>{{ disability.title }}</span>
|
*ngIf="reportingTabVisible"
|
||||||
<digi-ng-popover
|
>
|
||||||
*ngIf="disability.description"
|
<ng-container *ngIf="activeTab === '1'">
|
||||||
class="deltagare-card__popover"
|
<msfa-deltagare-tab-reports></msfa-deltagare-tab-reports>
|
||||||
[afRelativeIconSize]="true"
|
</ng-container>
|
||||||
>{{ disability.description }}</digi-ng-popover
|
</digi-navigation-tab>
|
||||||
>
|
|
||||||
</dd>
|
<digi-navigation-tab
|
||||||
</ng-container>
|
(afOnToggle)="setActiveTab(2)"
|
||||||
</dl>
|
af-aria-label="Erfarenheter"
|
||||||
</div>
|
af-id="deltagare-card-experiences"
|
||||||
<div class="deltagare-card__tab-column">
|
*ngIf="experiencesVisible"
|
||||||
<h2>Språk</h2>
|
>
|
||||||
<dl>
|
<ng-container *ngIf="activeTab === '2'">
|
||||||
<dt>Behov av tolk:</dt>
|
<msfa-deltagare-tab-experiences
|
||||||
<dd>
|
*ngIf="(tab2Loading$ | async) === false; else loadingRef"
|
||||||
{{deltagare.avropInformation.tolkbehov ? 'Ja (' + deltagare.avropInformation.tolkbehov + ')' : 'Nej'}}
|
class="deltagare-card__tab-contents"
|
||||||
</dd>
|
[workExperiences]="workExperiences$ | async"
|
||||||
<dt>Behov av språkstöd:</dt>
|
[highestEducation]="highestEducation$ | async"
|
||||||
<dd>
|
[driversLicense]="driversLicense$ | async"
|
||||||
{{deltagare.avropInformation.sprakstod ? 'Ja (' + deltagare.avropInformation.sprakstod + ')' : 'Nej'}}
|
></msfa-deltagare-tab-experiences>
|
||||||
</dd>
|
</ng-container>
|
||||||
<dt>Språk som kan användas på jobbet:</dt>
|
</digi-navigation-tab>
|
||||||
<dd *ngIf="deltagare.workLanguages.length else emptyDD">{{ deltagare.workLanguages.join(', ')}}</dd>
|
|
||||||
</dl>
|
<digi-navigation-tab
|
||||||
</div>
|
(afOnToggle)="setActiveTab(3)"
|
||||||
</div>
|
af-aria-label="Känsliga uppgifter"
|
||||||
|
af-id="deltagare-card-sensitive-information"
|
||||||
|
*ngIf="sensitiveDataVisible"
|
||||||
|
>
|
||||||
|
<ng-container *ngIf="activeTab === '3'">
|
||||||
|
<msfa-deltagare-tab-sensitive-information
|
||||||
|
*ngIf="(tab3Loading$ | async) === false; else loadingRef"
|
||||||
|
class="deltagare-card__tab-contents"
|
||||||
|
[avropInformation]="avropInformation$ | async"
|
||||||
|
[workLanguages]="workLanguages$ | async"
|
||||||
|
[disabilities]="disabilities$ | async"
|
||||||
|
></msfa-deltagare-tab-sensitive-information>
|
||||||
|
</ng-container>
|
||||||
</digi-navigation-tab>
|
</digi-navigation-tab>
|
||||||
</digi-navigation-tabs>
|
</digi-navigation-tabs>
|
||||||
</section>
|
</section>
|
||||||
@@ -242,15 +72,5 @@
|
|||||||
</msfa-layout>
|
</msfa-layout>
|
||||||
|
|
||||||
<ng-template #loadingRef>
|
<ng-template #loadingRef>
|
||||||
<digi-ng-skeleton-base [afCount]="3" afText="Laddar deltagarinformation"></digi-ng-skeleton-base>
|
<msfa-loader type="padded"></msfa-loader>
|
||||||
</ng-template>
|
|
||||||
<ng-template #emptyDD>
|
|
||||||
<dd>
|
|
||||||
<span aria-hidden="true">-</span>
|
|
||||||
<span class="msfa__a11y-sr-only">Info saknas</span>
|
|
||||||
</dd>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #emptyText>
|
|
||||||
<span aria-hidden="true">-</span>
|
|
||||||
<span class="msfa__a11y-sr-only">Info saknas</span>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
@import 'mixins/list';
|
|
||||||
@import 'variables/gutters';
|
@import 'variables/gutters';
|
||||||
@import 'variables/z-index';
|
|
||||||
|
|
||||||
.deltagare-card {
|
.deltagare-card {
|
||||||
&__tab-contents {
|
&__tab-contents {
|
||||||
@@ -9,68 +7,10 @@
|
|||||||
margin: 0 $digi--layout--gutter--l;
|
margin: 0 $digi--layout--gutter--l;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tab-column {
|
&__header {
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
flex-basis: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__select-report {
|
|
||||||
max-width: var(--digi--typography--text--max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__cta-wrapper {
|
|
||||||
margin-top: $digi--layout--gutter--l;
|
|
||||||
margin-bottom: $digi--layout--gutter--xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt,
|
|
||||||
&__subheading {
|
|
||||||
font-size: var(--digi--typography--font-size--desktop);
|
|
||||||
font-weight: var(--digi--typography--font-weight--semibold);
|
|
||||||
margin: var(--digi--layout--gutter--s) 0 0;
|
|
||||||
|
|
||||||
&--with-margin {
|
|
||||||
font-size: var(--digi--typography--font-size--h3);
|
|
||||||
margin-bottom: var(--digi--layout--gutter--s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__experience-list {
|
|
||||||
@include msfa__reset-list;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accordion {
|
|
||||||
display: block;
|
|
||||||
margin-top: var(--digi--layout--gutter);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accordion-trigger {
|
|
||||||
font-weight: var(--digi--typography--font-weight--semibold);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__popover {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: var(--digi--layout--gutter--s);
|
|
||||||
|
|
||||||
::ng-deep .digi-ng-popover__container {
|
|
||||||
z-index: $msfa__z-index-popover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header,
|
|
||||||
&__footer {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
|
||||||
margin-top: $digi--layout--gutter--l;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
|
|
||||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
|
import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
|
||||||
import { DeltagareCardComponent } from './deltagare-card.component';
|
import { DeltagareCardComponent } from './deltagare-card.component';
|
||||||
@@ -14,7 +13,8 @@ describe('DeltagareCardComponent', () => {
|
|||||||
waitForAsync(() => {
|
waitForAsync(() => {
|
||||||
void TestBed.configureTestingModule({
|
void TestBed.configureTestingModule({
|
||||||
declarations: [DeltagareCardComponent, LayoutComponent],
|
declarations: [DeltagareCardComponent, LayoutComponent],
|
||||||
imports: [RouterTestingModule, HttpClientTestingModule, DigiNgSkeletonBaseModule, ReactiveFormsModule],
|
imports: [RouterTestingModule, HttpClientTestingModule],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { FormSelectItem } from '@af/digi-ng/_form/form-select';
|
|
||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { Feature } from '@msfa-enums/feature.enum';
|
||||||
import { IconType } from '@msfa-enums/icon-type.enum';
|
import { IconType } from '@msfa-enums/icon-type.enum';
|
||||||
import { Deltagare } from '@msfa-models/deltagare.model';
|
import { RoleEnum } from '@msfa-enums/role.enum';
|
||||||
import { ReportsData } from '@msfa-models/reports.model';
|
import { environment } from '@msfa-environment';
|
||||||
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
|
import { ContactInformation } from '@msfa-models/contact-information.model';
|
||||||
|
import { Disability } from '@msfa-models/disability.model';
|
||||||
|
import { DriversLicense } from '@msfa-models/drivers-license.model';
|
||||||
|
import { Education } from '@msfa-models/education.model';
|
||||||
|
import { HighestEducation } from '@msfa-models/highest-education.model';
|
||||||
|
import { Role } from '@msfa-models/role.model';
|
||||||
import { WorkExperience } from '@msfa-models/work-experience.model';
|
import { WorkExperience } from '@msfa-models/work-experience.model';
|
||||||
import { DeltagareService } from '@msfa-services/api/deltagare.service';
|
import { UserService } from '@msfa-services/api/user.service';
|
||||||
import { Observable } from 'rxjs';
|
import { DeltagareCardService } from '@msfa-services/deltagare-card.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-deltagare-card',
|
selector: 'msfa-deltagare-card',
|
||||||
@@ -17,85 +24,110 @@ import { map } from 'rxjs/operators';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DeltagareCardComponent {
|
export class DeltagareCardComponent {
|
||||||
deltagare$: Observable<Deltagare> = this.deltagareService.deltagare$;
|
private _activeFeatures: Feature[] = environment.activeFeatures;
|
||||||
reportsData$: Observable<ReportsData> = this.deltagareService.reportsData$;
|
private _activeTab$ = new BehaviorSubject<string>('0');
|
||||||
|
private _userRoles: Role[] = this.userService.userRolesSnapshot;
|
||||||
|
|
||||||
readonly reportsFormControlName = 'reports';
|
public activeTab$: Observable<string> = this._activeTab$.asObservable();
|
||||||
|
public currentDeltagareId$: Observable<string> = this.activatedRoute.params.pipe(
|
||||||
reportPickerFormGroup: FormGroup = this.formBuilder.group({
|
map(params => params.deltagareId as string),
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
distinctUntilChanged(([prevDeltagareId], [currDeltagareId]) => prevDeltagareId === currDeltagareId)
|
||||||
reports: this.formBuilder.control('', [Validators.required]),
|
|
||||||
});
|
|
||||||
|
|
||||||
selectableReportTypes: Array<FormSelectItem> = [
|
|
||||||
{ name: 'Avvikelse', value: 'avvikelse' },
|
|
||||||
{ name: 'Gemensam Planering', value: 'planering' },
|
|
||||||
];
|
|
||||||
selectedReportType: FormSelectItem;
|
|
||||||
|
|
||||||
firstVisibleWorkExperiences$: Observable<WorkExperience[]> = this.deltagare$.pipe(
|
|
||||||
map(deltagare => deltagare.workExperiences.slice(0, 2))
|
|
||||||
);
|
);
|
||||||
hiddenWorkExperiences$: Observable<WorkExperience[]> = this.deltagare$.pipe(
|
contactInformation$: Observable<ContactInformation> = combineLatest([this.currentDeltagareId$]).pipe(
|
||||||
map(deltagare => deltagare.workExperiences.slice(2))
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchContactInformation$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
avropInformation$: Observable<Avrop> = combineLatest([this.currentDeltagareId$, this._activeTab$]).pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchAvropInformation$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
workExperiences$: Observable<WorkExperience[]> = this.currentDeltagareId$.pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchWorkExperiences$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
highestEducation$: Observable<HighestEducation> = this.currentDeltagareId$.pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchHighestEducation$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
educations$: Observable<Education[]> = this.currentDeltagareId$.pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchEducations$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
driversLicense$: Observable<DriversLicense> = this.currentDeltagareId$.pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchDriversLicense$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
workLanguages$: Observable<string[]> = this.currentDeltagareId$.pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchWorkLanguages$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
disabilities$: Observable<Disability[]> = this.currentDeltagareId$.pipe(
|
||||||
|
switchMap(([deltagareId]) => this.deltagareCardService.fetchDisabilities$(deltagareId)),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
tab0Loading$: Observable<boolean> = combineLatest([this.contactInformation$, this.avropInformation$]).pipe(
|
||||||
|
map(([contactInformation, avropInformation]) => !(contactInformation && avropInformation)),
|
||||||
|
startWith(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
tab2Loading$: Observable<boolean> = combineLatest([
|
||||||
|
this.workExperiences$,
|
||||||
|
this.highestEducation$,
|
||||||
|
this.educations$,
|
||||||
|
this.driversLicense$,
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([workExperiences, highestEducation, educations, driversLicense]) =>
|
||||||
|
!(workExperiences && highestEducation && educations && driversLicense)
|
||||||
|
),
|
||||||
|
startWith(true)
|
||||||
|
);
|
||||||
|
tab3Loading$: Observable<boolean> = combineLatest([
|
||||||
|
this.disabilities$,
|
||||||
|
this.avropInformation$,
|
||||||
|
this.workLanguages$,
|
||||||
|
]).pipe(
|
||||||
|
map(([disabilities, avropInformation, workLanguages]) => !(disabilities && avropInformation && workLanguages)),
|
||||||
|
startWith(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
get deltagareTjanstVisible(): boolean {
|
||||||
|
return this._userRoles?.some(
|
||||||
|
role => role.type === RoleEnum.MSFA_ReportAndPlanning || role.type === RoleEnum.MSFA_ReceiveDeltagare
|
||||||
|
);
|
||||||
|
}
|
||||||
|
get reportingTabVisible(): boolean {
|
||||||
|
return (
|
||||||
|
this._activeFeatures.includes(Feature.REPORTING) &&
|
||||||
|
this._userRoles?.some(role => role.type === RoleEnum.MSFA_ReportAndPlanning)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
get experiencesVisible(): boolean {
|
||||||
|
return this._userRoles?.some(role => role.type === RoleEnum.MSFA_ReportAndPlanning);
|
||||||
|
}
|
||||||
|
get sensitiveDataVisible(): boolean {
|
||||||
|
return this._userRoles?.some(role => role.type === RoleEnum.MSFA_ReportAndPlanning);
|
||||||
|
}
|
||||||
|
|
||||||
|
firstVisibleWorkExperiences$: Observable<WorkExperience[]> = this.workExperiences$.pipe(
|
||||||
|
filter(workExperiences => !!workExperiences),
|
||||||
|
map(workExperiences => workExperiences.slice(0, 2))
|
||||||
|
);
|
||||||
|
hiddenWorkExperiences$: Observable<WorkExperience[]> = this.workExperiences$.pipe(
|
||||||
|
filter(workExperiences => !!workExperiences),
|
||||||
|
map(workExperiences => workExperiences.slice(2))
|
||||||
);
|
);
|
||||||
|
|
||||||
iconType = IconType;
|
iconType = IconType;
|
||||||
accordionExpanded = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private deltagareService: DeltagareService,
|
private deltagareCardService: DeltagareCardService,
|
||||||
private formBuilder: FormBuilder,
|
private userService: UserService
|
||||||
private router: Router
|
) {}
|
||||||
) {
|
|
||||||
this.deltagareService.setCurrentDeltagareId(this.activatedRoute.snapshot.params.deltagareId);
|
|
||||||
}
|
|
||||||
|
|
||||||
get reportsFormControl(): AbstractControl | null {
|
setActiveTab(tab: number): void {
|
||||||
return this.reportPickerFormGroup?.get(this.reportsFormControlName);
|
this._activeTab$.next(tab.toString());
|
||||||
}
|
|
||||||
|
|
||||||
toggleAccordionExpanded(): void {
|
|
||||||
this.accordionExpanded = !this.accordionExpanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewPage(page: number): void {
|
|
||||||
this.deltagareService.setPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
onFormSubmitted(event: Event, reportType: string, deltagareId: string): void {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
switch (reportType) {
|
|
||||||
case 'planering':
|
|
||||||
if (this.reportsFormControl.valid) {
|
|
||||||
this.router.navigate(['/deltagare/planering', deltagareId]).catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'avvikelse':
|
|
||||||
if (this.reportsFormControl.valid) {
|
|
||||||
this.router.navigate(['/deltagare/rapportera', deltagareId]).catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reportsFormControl.markAsTouched();
|
|
||||||
|
|
||||||
if (!this.selectableReportTypes || this.reportPickerFormGroup.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedReportType = this.selectableReportTypes.find(report => {
|
|
||||||
return report.value === this.reportsFormControl.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.deltagareService.setReportType(selectedReportType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,45 @@
|
|||||||
import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
|
import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
|
||||||
import { DigiNgLayoutExpansionPanelModule } from '@af/digi-ng/_layout/layout-expansion-panel';
|
import { DigiNgLayoutExpansionPanelModule } from '@af/digi-ng/_layout/layout-expansion-panel';
|
||||||
import { DigiNgLinkButtonModule } from '@af/digi-ng/_link/link-button';
|
import { DigiNgLinkButtonModule } from '@af/digi-ng/_link/link-button';
|
||||||
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
|
|
||||||
import { DigiNgPopoverModule } from '@af/digi-ng/_popover/popover';
|
import { DigiNgPopoverModule } from '@af/digi-ng/_popover/popover';
|
||||||
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
|
import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
|
||||||
import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
|
import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
|
||||||
import { IconModule } from '@msfa-shared/components/icon/icon.module';
|
|
||||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||||
|
import { LoaderModule } from '@msfa-shared/components/loader/loader.module';
|
||||||
|
import { DeltagareTabExperiencesComponent } from './components/deltagare-tab-experiences/deltagare-tab-experiences.component';
|
||||||
|
import { DeltagareTabPersonalInformationComponent } from './components/deltagare-tab-personal-information/deltagare-tab-personal-information.component';
|
||||||
|
import { DeltagareTabReportsComponent } from './components/deltagare-tab-reports/deltagare-tab-reports.component';
|
||||||
|
import { DeltagareTabSensitiveInformationComponent } from './components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component';
|
||||||
import { ReportsModule } from './components/reports/reports.module';
|
import { ReportsModule } from './components/reports/reports.module';
|
||||||
import { DeltagareCardComponent } from './deltagare-card.component';
|
import { DeltagareCardComponent } from './deltagare-card.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
declarations: [DeltagareCardComponent],
|
declarations: [
|
||||||
|
DeltagareCardComponent,
|
||||||
|
DeltagareTabExperiencesComponent,
|
||||||
|
DeltagareTabReportsComponent,
|
||||||
|
DeltagareTabPersonalInformationComponent,
|
||||||
|
DeltagareTabSensitiveInformationComponent,
|
||||||
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
RouterModule.forChild([{ path: '', component: DeltagareCardComponent }]),
|
RouterModule.forChild([{ path: '', component: DeltagareCardComponent }]),
|
||||||
|
ReactiveFormsModule,
|
||||||
ReportsModule,
|
ReportsModule,
|
||||||
LayoutModule,
|
LayoutModule,
|
||||||
DigiNgLinkInternalModule,
|
|
||||||
IconModule,
|
|
||||||
BackLinkModule,
|
BackLinkModule,
|
||||||
DigiNgLayoutExpansionPanelModule,
|
|
||||||
HideTextModule,
|
HideTextModule,
|
||||||
DigiNgSkeletonBaseModule,
|
LoaderModule,
|
||||||
|
DigiNgLayoutExpansionPanelModule,
|
||||||
DigiNgPopoverModule,
|
DigiNgPopoverModule,
|
||||||
DigiNgLinkButtonModule,
|
DigiNgLinkButtonModule,
|
||||||
DigiNgFormSelectModule,
|
DigiNgFormSelectModule,
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
],
|
||||||
exports: [DeltagareCardComponent],
|
exports: [DeltagareCardComponent],
|
||||||
})
|
})
|
||||||
export class DeltagareCardModule { }
|
export class DeltagareCardModule {}
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export class SidebarComponent {
|
|||||||
get deltagareVisible(): boolean {
|
get deltagareVisible(): boolean {
|
||||||
return (
|
return (
|
||||||
this.activeFeatures.includes(Feature.DELTAGARE) &&
|
this.activeFeatures.includes(Feature.DELTAGARE) &&
|
||||||
this.userRoles?.some(role => role.type === RoleEnum.MSFA_ReportAndPlanning)
|
this.userRoles?.some(
|
||||||
|
role => role.type === RoleEnum.MSFA_ReportAndPlanning || role.type === RoleEnum.MSFA_ReceiveDeltagare
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
|
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
|
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
|
||||||
import { Employee } from '@msfa-models/employee.model';
|
import { Employee } from '@msfa-models/employee.model';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import 'mixins/backdrop';
|
@import 'mixins/backdrop';
|
||||||
|
@import 'variables/gutters';
|
||||||
@import 'variables/z-index';
|
@import 'variables/z-index';
|
||||||
|
|
||||||
@keyframes spinning {
|
@keyframes spinning {
|
||||||
@@ -12,6 +13,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
&--padded {
|
||||||
|
padding: $digi--layout--gutter--l;
|
||||||
|
}
|
||||||
|
|
||||||
&--absolute {
|
&--absolute {
|
||||||
@include msfa__backdrop($msfa__z-index-backdrop, false);
|
@include msfa__backdrop($msfa__z-index-backdrop, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ export enum Feature {
|
|||||||
MOCK_LOGIN,
|
MOCK_LOGIN,
|
||||||
VERSION_INFO,
|
VERSION_INFO,
|
||||||
ACCESSIBILITY_REPORT,
|
ACCESSIBILITY_REPORT,
|
||||||
|
REPORTING,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum LoaderType {
|
export enum LoaderType {
|
||||||
FULL_SCREEN = 'fullscreen',
|
FULL_SCREEN = 'fullscreen',
|
||||||
ABSOLUTE = 'absolute',
|
ABSOLUTE = 'absolute',
|
||||||
|
PADDED = 'padded',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ export class RoleGuard implements CanActivate {
|
|||||||
constructor(private router: Router, private userService: UserService) {}
|
constructor(private router: Router, private userService: UserService) {}
|
||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
|
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
|
||||||
const expectedRole: RoleEnum = route.data.expectedRole as RoleEnum;
|
const expectedRoles: RoleEnum[] = route.data.expectedRoles as RoleEnum[];
|
||||||
|
|
||||||
return this.userService.userRoles$.pipe(
|
return this.userService.userRoles$.pipe(
|
||||||
filter(roles => !!roles),
|
filter(roles => !!roles),
|
||||||
map(roles => {
|
map(roles => {
|
||||||
const userHasRole = roles.some(role => role.type === expectedRole);
|
const userHasRole = roles.some(role => expectedRoles.includes(role.type));
|
||||||
|
|
||||||
if (userHasRole) {
|
if (userHasRole) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export interface AvropResponse {
|
|||||||
sprakstod: string;
|
sprakstod: string;
|
||||||
sparkod: string;
|
sparkod: string;
|
||||||
sparNamn: string;
|
sparNamn: string;
|
||||||
supervisorId: number;
|
handledareCiamUserId: string;
|
||||||
|
handledare: string;
|
||||||
recievedTimestamp: string;
|
recievedTimestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export interface Avrop extends AvropCompact {
|
|||||||
genomforandeReferens: number; // genomforandeReferens
|
genomforandeReferens: number; // genomforandeReferens
|
||||||
participationFrequency: number; // deltagandeGrad
|
participationFrequency: number; // deltagandeGrad
|
||||||
utforandeVerksamhet: string; // utforandeverksamhet
|
utforandeVerksamhet: string; // utforandeverksamhet
|
||||||
|
handledareCiamUserId: string;
|
||||||
|
handledare: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvropCompactData {
|
export interface AvropCompactData {
|
||||||
@@ -42,6 +44,8 @@ export function mapAvropResponseToAvrop(data: AvropResponse): Avrop {
|
|||||||
genomforandeReferens,
|
genomforandeReferens,
|
||||||
deltagandeGrad,
|
deltagandeGrad,
|
||||||
utforandeverksamhet,
|
utforandeverksamhet,
|
||||||
|
handledareCiamUserId,
|
||||||
|
handledare,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -59,5 +63,7 @@ export function mapAvropResponseToAvrop(data: AvropResponse): Avrop {
|
|||||||
genomforandeReferens,
|
genomforandeReferens,
|
||||||
participationFrequency: deltagandeGrad,
|
participationFrequency: deltagandeGrad,
|
||||||
utforandeVerksamhet: utforandeverksamhet,
|
utforandeVerksamhet: utforandeverksamhet,
|
||||||
|
handledareCiamUserId,
|
||||||
|
handledare,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class CustomError implements Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function errorToCustomError(error: Error & { ngDebugContext: unknown }): CustomError {
|
export function errorToCustomError(error: Error & { ngDebugContext?: unknown }): CustomError {
|
||||||
const type = CustomError.getErrorType(error);
|
const type = CustomError.getErrorType(error);
|
||||||
const message = error.message || error;
|
const message = error.message || error;
|
||||||
const severity = ErrorSeverity.HIGH;
|
const severity = ErrorSeverity.HIGH;
|
||||||
|
|||||||
@@ -1,267 +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 { AvropResponse } from '@msfa-models/api/avrop.response.model';
|
|
||||||
import { ContactInformationResponse } from '@msfa-models/api/contact-information.response.model';
|
|
||||||
import { DeltagareCompactApiResponse } from '@msfa-models/api/deltagare.response.model';
|
|
||||||
import { DisabilityResponse } from '@msfa-models/api/disability.response.model';
|
|
||||||
import { DriversLicenseResponse } from '@msfa-models/api/drivers-license.response.model';
|
|
||||||
import { EducationsResponse } from '@msfa-models/api/educations.response.model';
|
|
||||||
import { HighestEducationResponse } from '@msfa-models/api/highest-education.response.model';
|
|
||||||
import { Params } from '@msfa-models/api/params.model';
|
|
||||||
import { TranslatorResponse } from '@msfa-models/api/translator.response.model';
|
|
||||||
import { WorkExperiencesResponse } from '@msfa-models/api/work-experiences.response.model';
|
|
||||||
import { WorkLanguagesResponse } from '@msfa-models/api/work-languages.response.model';
|
|
||||||
import { Avrop, mapAvropResponseToAvrop } from '@msfa-models/avrop.model';
|
|
||||||
import { ContactInformation, mapResponseToContactInformation } from '@msfa-models/contact-information.model';
|
|
||||||
import {
|
|
||||||
Deltagare,
|
|
||||||
DeltagareCompact,
|
|
||||||
DeltagareCompactData,
|
|
||||||
mapResponseToDeltagareCompact
|
|
||||||
} from '@msfa-models/deltagare.model';
|
|
||||||
import { Disability, mapResponseToDisability } from '@msfa-models/disability.model';
|
|
||||||
import { DriversLicense, mapResponseToDriversLicense } from '@msfa-models/drivers-license.model';
|
|
||||||
import { Education, mapResponseToEducation } from '@msfa-models/education.model';
|
|
||||||
import { errorToCustomError } from '@msfa-models/error/custom-error';
|
|
||||||
import { HighestEducation, mapResponseToHighestEducation } from '@msfa-models/highest-education.model';
|
|
||||||
import { Sort } from '@msfa-models/sort.model';
|
|
||||||
import { mapResponseToWorkExperience, WorkExperience } from '@msfa-models/work-experience.model';
|
|
||||||
import { ErrorService } from '@msfa-services/error.service';
|
|
||||||
import { sortFromToDates } from '@msfa-utils/sort.util';
|
|
||||||
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
|
||||||
import { catchError, filter, map, switchMap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class DeltagareApiService extends UnsubscribeDirective {
|
|
||||||
private _apiBaseUrl = `${environment.api.url}/deltagare`;
|
|
||||||
private _currentDeltagareId$ = new BehaviorSubject<string>(null);
|
|
||||||
private _limit$ = new BehaviorSubject<number>(20);
|
|
||||||
private _page$ = new BehaviorSubject<number>(1);
|
|
||||||
private _sort$ = new BehaviorSubject<Sort<keyof DeltagareCompact>>({ key: 'fullName', order: SortOrder.ASC });
|
|
||||||
public sort$: Observable<Sort<keyof DeltagareCompact>> = this._sort$.asObservable();
|
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private errorService: ErrorService) {
|
|
||||||
super();
|
|
||||||
super.unsubscribeOnDestroy(
|
|
||||||
this._currentDeltagareId$
|
|
||||||
.pipe(
|
|
||||||
filter(currentDeltagareId => !!currentDeltagareId),
|
|
||||||
switchMap(currentDeltagareId => this._fetchDeltagare$(currentDeltagareId))
|
|
||||||
)
|
|
||||||
.subscribe(deltagare => {
|
|
||||||
this._deltagare$.next(deltagare);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _deltagare$ = new BehaviorSubject<Deltagare>(null);
|
|
||||||
public deltagare$: Observable<Deltagare> = this._deltagare$.asObservable();
|
|
||||||
|
|
||||||
public allDeltagareData$: Observable<DeltagareCompactData> = combineLatest([
|
|
||||||
this._limit$,
|
|
||||||
this._page$,
|
|
||||||
this._sort$,
|
|
||||||
]).pipe(switchMap(([limit, page, sort]) => this._fetchAllDeltagare$(limit, page, sort)));
|
|
||||||
|
|
||||||
public setSort(newSortKey: keyof DeltagareCompact): void {
|
|
||||||
const currentSort = this._sort$.getValue();
|
|
||||||
const order =
|
|
||||||
currentSort.key === newSortKey && currentSort.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
|
||||||
|
|
||||||
this._sort$.next({ key: newSortKey, order });
|
|
||||||
}
|
|
||||||
|
|
||||||
public setPage(page: number): void {
|
|
||||||
this._page$.next(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchAllDeltagare$(
|
|
||||||
limit: number,
|
|
||||||
page: number,
|
|
||||||
sort: Sort<keyof DeltagareCompact>
|
|
||||||
): Observable<DeltagareCompactData> {
|
|
||||||
const params: Params = {
|
|
||||||
sort: sort.key as string,
|
|
||||||
order: sort.order as string,
|
|
||||||
limit: limit.toString(),
|
|
||||||
page: page.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.httpClient
|
|
||||||
.get<DeltagareCompactApiResponse>(this._apiBaseUrl, {
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
map(({ data, meta }) => {
|
|
||||||
return { data: data.map(deltagare => mapResponseToDeltagareCompact(deltagare)), meta };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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}/${id}/contact`).pipe(
|
|
||||||
map(({ data }) => mapResponseToContactInformation(data)),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of({});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchDriversLicense$(id: string): Observable<DriversLicense | Partial<DriversLicense>> {
|
|
||||||
return this.httpClient.get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/${id}/driverlicense`).pipe(
|
|
||||||
map(({ data }) => mapResponseToDriversLicense(data)),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of({});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchHighestEducation$(id: string): Observable<HighestEducation | Partial<HighestEducation>> {
|
|
||||||
return this.httpClient
|
|
||||||
.get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/${id}/educationlevels/highest`)
|
|
||||||
.pipe(
|
|
||||||
map(({ data }) => mapResponseToHighestEducation(data)),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of({});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchEducations$(id: string): Observable<Education[]> {
|
|
||||||
return this.httpClient.get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/${id}/educations`).pipe(
|
|
||||||
map(({ data }) =>
|
|
||||||
data.utbildningar
|
|
||||||
? data.utbildningar.sort((a, b) =>
|
|
||||||
sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
),
|
|
||||||
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}/${id}/translator`).pipe(
|
|
||||||
map(({ data }) => 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}/${id}/work/languages`).pipe(
|
|
||||||
map(({ data }) => data?.sprak?.map(sprak => sprak.beskrivning) || []),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of([]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchDisabilities$(id: string): Observable<Disability[]> {
|
|
||||||
return this.httpClient.get<{ data: DisabilityResponse[] }>(`${this._apiBaseUrl}/${id}/work/disabilities`).pipe(
|
|
||||||
map(({ data }) => data?.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning)) || []),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of([]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchWorkExperiences$(id: string): Observable<WorkExperience[]> {
|
|
||||||
return this.httpClient.get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/${id}/work/experiences`).pipe(
|
|
||||||
map(
|
|
||||||
({ data }) =>
|
|
||||||
data?.arbetslivserfarenheter?.sort((a, b) =>
|
|
||||||
sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
|
|
||||||
) || []
|
|
||||||
),
|
|
||||||
map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of([]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchAvropInformation$(id: string): Observable<Avrop | Partial<Avrop>> {
|
|
||||||
return this.httpClient.get<{ data: AvropResponse }>(`${this._apiBaseUrl}/${id}/avrop`).pipe(
|
|
||||||
map(({ data }) => (data ? mapAvropResponseToAvrop(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
|
|
||||||
private _fetchDeltagare$(id: string): Observable<Deltagare> {
|
|
||||||
return combineLatest([
|
|
||||||
this._fetchContactInformation$(id),
|
|
||||||
this._fetchDriversLicense$(id),
|
|
||||||
this._fetchHighestEducation$(id),
|
|
||||||
this._fetchEducations$(id),
|
|
||||||
this._fetchTranslator$(id),
|
|
||||||
this._fetchWorkLanguages$(id),
|
|
||||||
this._fetchDisabilities$(id),
|
|
||||||
this._fetchWorkExperiences$(id),
|
|
||||||
this._fetchAvropInformation$(id),
|
|
||||||
]).pipe(
|
|
||||||
map(
|
|
||||||
([
|
|
||||||
contactInformation,
|
|
||||||
driversLicense,
|
|
||||||
highestEducation,
|
|
||||||
educations,
|
|
||||||
translator,
|
|
||||||
workLanguages,
|
|
||||||
disabilities,
|
|
||||||
workExperiences,
|
|
||||||
avropInformation,
|
|
||||||
]: [
|
|
||||||
ContactInformation,
|
|
||||||
DriversLicense,
|
|
||||||
HighestEducation,
|
|
||||||
Education[],
|
|
||||||
string,
|
|
||||||
string[],
|
|
||||||
Disability[],
|
|
||||||
WorkExperience[],
|
|
||||||
Avrop
|
|
||||||
]) => ({
|
|
||||||
id,
|
|
||||||
...contactInformation,
|
|
||||||
driversLicense,
|
|
||||||
highestEducation,
|
|
||||||
educations,
|
|
||||||
translator,
|
|
||||||
workLanguages,
|
|
||||||
disabilities,
|
|
||||||
workExperiences,
|
|
||||||
avropInformation,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { environment } from '@msfa-environment';
|
||||||
|
import { AvropResponse } from '@msfa-models/api/avrop.response.model';
|
||||||
|
import { ContactInformationResponse } from '@msfa-models/api/contact-information.response.model';
|
||||||
|
import { DeltagareCompactApiResponse } from '@msfa-models/api/deltagare.response.model';
|
||||||
|
import { DisabilityResponse } from '@msfa-models/api/disability.response.model';
|
||||||
|
import { DriversLicenseResponse } from '@msfa-models/api/drivers-license.response.model';
|
||||||
|
import { EducationsResponse } from '@msfa-models/api/educations.response.model';
|
||||||
|
import { HighestEducationResponse } from '@msfa-models/api/highest-education.response.model';
|
||||||
|
import { Params } from '@msfa-models/api/params.model';
|
||||||
|
import { TranslatorResponse } from '@msfa-models/api/translator.response.model';
|
||||||
|
import { WorkExperiencesResponse } from '@msfa-models/api/work-experiences.response.model';
|
||||||
|
import { WorkLanguagesResponse } from '@msfa-models/api/work-languages.response.model';
|
||||||
|
import { Avrop, mapAvropResponseToAvrop } from '@msfa-models/avrop.model';
|
||||||
|
import { ContactInformation, mapResponseToContactInformation } from '@msfa-models/contact-information.model';
|
||||||
|
import { DeltagareCompact, DeltagareCompactData, mapResponseToDeltagareCompact } from '@msfa-models/deltagare.model';
|
||||||
|
import { Disability, mapResponseToDisability } from '@msfa-models/disability.model';
|
||||||
|
import { DriversLicense, mapResponseToDriversLicense } from '@msfa-models/drivers-license.model';
|
||||||
|
import { Education, mapResponseToEducation } from '@msfa-models/education.model';
|
||||||
|
import { CustomError, errorToCustomError } from '@msfa-models/error/custom-error';
|
||||||
|
import { HighestEducation, mapResponseToHighestEducation } from '@msfa-models/highest-education.model';
|
||||||
|
import { ReportsData } from '@msfa-models/reports.model';
|
||||||
|
import { Sort } from '@msfa-models/sort.model';
|
||||||
|
import { mapResponseToWorkExperience, WorkExperience } from '@msfa-models/work-experience.model';
|
||||||
|
import { sortFromToDates } from '@msfa-utils/sort.util';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DeltagareApiService {
|
||||||
|
private _apiBaseUrl = `${environment.api.url}/deltagare`;
|
||||||
|
|
||||||
|
constructor(private httpClient: HttpClient) {}
|
||||||
|
|
||||||
|
public fetchAllDeltagare$(
|
||||||
|
limit: number,
|
||||||
|
page: number,
|
||||||
|
sort: Sort<keyof DeltagareCompact>,
|
||||||
|
onlyMyDeltagare?: boolean
|
||||||
|
): Observable<DeltagareCompactData> {
|
||||||
|
const params: Params = {
|
||||||
|
sort: sort.key as string,
|
||||||
|
order: sort.order as string,
|
||||||
|
limit: limit.toString(),
|
||||||
|
page: page.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onlyMyDeltagare) {
|
||||||
|
params.onlyMyDeltagare = onlyMyDeltagare.toString();
|
||||||
|
}
|
||||||
|
return this.httpClient
|
||||||
|
.get<DeltagareCompactApiResponse>(this._apiBaseUrl, {
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
map(({ data, meta }) => {
|
||||||
|
return { data: data.map(deltagare => mapResponseToDeltagareCompact(deltagare)), meta };
|
||||||
|
}),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta deltagare.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchReports$(limit: number, page: number, deltagareId: string): Observable<ReportsData> {
|
||||||
|
return of({ data: [], meta: null });
|
||||||
|
|
||||||
|
// TODO: When the API/Mock-API has implemented the endpoint, we can remove use following code
|
||||||
|
// to make API-requests.
|
||||||
|
|
||||||
|
// const params: { [param: string]: string | string[] } = {
|
||||||
|
// id: deltagareId.toString(),
|
||||||
|
// limit: limit.toString(),
|
||||||
|
// page: page.toString(),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return this.httpClient
|
||||||
|
// .get<ReportResponse>(`${this._apiBaseUrl}/report`, {
|
||||||
|
// params,
|
||||||
|
// })
|
||||||
|
// .pipe(
|
||||||
|
// map(({ data, meta }) => {
|
||||||
|
// data.sort((reportA, reportB) => (+reportA.sendDate < +reportB.sendDate ? 1 : -1));
|
||||||
|
// return { data: data.map(report => mapReportsResponseToReport(report)), meta };
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchContactInformation$(id: string): Observable<ContactInformation> {
|
||||||
|
return this.httpClient.get<{ data: ContactInformationResponse }>(`${this._apiBaseUrl}/${id}/contact`).pipe(
|
||||||
|
map(({ data }) => mapResponseToContactInformation(data)),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta kontaktinformation.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchDriversLicense$(id: string): Observable<DriversLicense> {
|
||||||
|
return this.httpClient.get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/${id}/driverlicense`).pipe(
|
||||||
|
map(({ data }) => mapResponseToDriversLicense(data)),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta körkortsinformation.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchHighestEducation$(id: string): Observable<HighestEducation> {
|
||||||
|
return this.httpClient
|
||||||
|
.get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/${id}/educationlevels/highest`)
|
||||||
|
.pipe(
|
||||||
|
map(({ data }) => mapResponseToHighestEducation(data)),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta högsta utbildning.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchEducations$(id: string): Observable<Education[]> {
|
||||||
|
return this.httpClient.get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/${id}/educations`).pipe(
|
||||||
|
map(({ data }) =>
|
||||||
|
data.utbildningar
|
||||||
|
? data.utbildningar.sort((a, b) =>
|
||||||
|
sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
map(educations => educations.map(utbildning => mapResponseToEducation(utbildning))),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta utbildningar.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchTranslator$(id: string): Observable<string> {
|
||||||
|
return this.httpClient.get<{ data: TranslatorResponse }>(`${this._apiBaseUrl}/${id}/translator`).pipe(
|
||||||
|
map(({ data }) => data.sprak?.beskrivning || null),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta tolkinformation.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchWorkLanguages$(id: string): Observable<string[]> {
|
||||||
|
return this.httpClient.get<{ data: WorkLanguagesResponse }>(`${this._apiBaseUrl}/${id}/work/languages`).pipe(
|
||||||
|
map(({ data }) => data?.sprak?.map(sprak => sprak.beskrivning) || []),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({
|
||||||
|
...error,
|
||||||
|
message: `Kunde inte hämta språk som kan användas på jobbet.\n\n${error.message}`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchDisabilities$(id: string): Observable<Disability[]> {
|
||||||
|
return this.httpClient.get<{ data: DisabilityResponse[] }>(`${this._apiBaseUrl}/${id}/work/disabilities`).pipe(
|
||||||
|
map(({ data }) => data?.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning)) || []),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta funktionsnedsättningar.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchWorkExperiences$(id: string): Observable<WorkExperience[]> {
|
||||||
|
return this.httpClient.get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/${id}/work/experiences`).pipe(
|
||||||
|
map(
|
||||||
|
({ data }) =>
|
||||||
|
data?.arbetslivserfarenheter?.sort((a, b) =>
|
||||||
|
sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
|
||||||
|
) || []
|
||||||
|
),
|
||||||
|
map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta arbetslivserfarenheter.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchAvropInformation$(id: string): Observable<Avrop | Partial<Avrop>> {
|
||||||
|
return this.httpClient.get<{ data: AvropResponse }>(`${this._apiBaseUrl}/${id}/avrop`).pipe(
|
||||||
|
map(({ data }) => (data ? mapAvropResponseToAvrop(data) : {})),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
throw new CustomError(
|
||||||
|
errorToCustomError({ ...error, message: `Kunde inte hämta avropsinformation.\n\n${error.message}` })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
import { FormSelectItem } from '@af/digi-ng/_form/form-select';
|
|
||||||
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 { AvropResponse } from '@msfa-models/api/avrop.response.model';
|
|
||||||
import { ContactInformationResponse } from '@msfa-models/api/contact-information.response.model';
|
|
||||||
import { DeltagareCompactApiResponse } from '@msfa-models/api/deltagare.response.model';
|
|
||||||
import { DisabilityResponse } from '@msfa-models/api/disability.response.model';
|
|
||||||
import { DriversLicenseResponse } from '@msfa-models/api/drivers-license.response.model';
|
|
||||||
import { EducationsResponse } from '@msfa-models/api/educations.response.model';
|
|
||||||
import { HighestEducationResponse } from '@msfa-models/api/highest-education.response.model';
|
|
||||||
import { ReportResponse } from '@msfa-models/api/report.response.model';
|
|
||||||
import { Params } from '@msfa-models/api/params.model';
|
|
||||||
import { TranslatorResponse } from '@msfa-models/api/translator.response.model';
|
|
||||||
import { WorkExperiencesResponse } from '@msfa-models/api/work-experiences.response.model';
|
|
||||||
import { WorkLanguagesResponse } from '@msfa-models/api/work-languages.response.model';
|
|
||||||
import { Avrop, mapAvropResponseToAvrop } from '@msfa-models/avrop.model';
|
|
||||||
import { ContactInformation, mapResponseToContactInformation } from '@msfa-models/contact-information.model';
|
|
||||||
import {
|
|
||||||
Deltagare,
|
|
||||||
DeltagareCompact,
|
|
||||||
DeltagareCompactData,
|
|
||||||
mapResponseToDeltagareCompact
|
|
||||||
} from '@msfa-models/deltagare.model';
|
|
||||||
import { Disability, mapResponseToDisability } from '@msfa-models/disability.model';
|
|
||||||
import { DriversLicense, mapResponseToDriversLicense } from '@msfa-models/drivers-license.model';
|
|
||||||
import { Education, mapResponseToEducation } from '@msfa-models/education.model';
|
|
||||||
import { errorToCustomError } from '@msfa-models/error/custom-error';
|
|
||||||
import { HighestEducation, mapResponseToHighestEducation } from '@msfa-models/highest-education.model';
|
|
||||||
import { mapReportsResponseToReport, ReportsData } from '@msfa-models/reports.model';
|
|
||||||
import { Sort } from '@msfa-models/sort.model';
|
|
||||||
import { mapResponseToWorkExperience, WorkExperience } from '@msfa-models/work-experience.model';
|
|
||||||
import { ErrorService } from '@msfa-services/error.service';
|
|
||||||
import { sortFromToDates } from '@msfa-utils/sort.util';
|
|
||||||
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
|
||||||
import { catchError, filter, map, switchMap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class DeltagareService extends UnsubscribeDirective {
|
|
||||||
private _apiBaseUrl = `${environment.api.url}/deltagare`;
|
|
||||||
private _currentDeltagareId$ = new BehaviorSubject<string>(null);
|
|
||||||
private _limit$ = new BehaviorSubject<number>(20);
|
|
||||||
private _page$ = new BehaviorSubject<number>(1);
|
|
||||||
private _sort$ = new BehaviorSubject<Sort<keyof DeltagareCompact>>({ key: 'fullName', order: SortOrder.ASC });
|
|
||||||
public sort$: Observable<Sort<keyof DeltagareCompact>> = this._sort$.asObservable();
|
|
||||||
private _reportType$ = new BehaviorSubject<FormSelectItem>(null);
|
|
||||||
reportType$: Observable<FormSelectItem> = this._reportType$.asObservable();
|
|
||||||
private _onlyMyDeltagare$ = new BehaviorSubject<boolean>(false);
|
|
||||||
public onlyMyDeltagare$: Observable<boolean> = this._onlyMyDeltagare$.asObservable();
|
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private errorService: ErrorService) {
|
|
||||||
super();
|
|
||||||
super.unsubscribeOnDestroy(
|
|
||||||
this._currentDeltagareId$
|
|
||||||
.pipe(
|
|
||||||
filter(currentDeltagareId => !!currentDeltagareId),
|
|
||||||
switchMap(currentDeltagareId => this._fetchDeltagare$(currentDeltagareId))
|
|
||||||
)
|
|
||||||
.subscribe(deltagare => {
|
|
||||||
this._deltagare$.next(deltagare);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _deltagare$ = new BehaviorSubject<Deltagare>(null);
|
|
||||||
public deltagare$: Observable<Deltagare> = this._deltagare$.asObservable();
|
|
||||||
|
|
||||||
public allDeltagareData$: Observable<DeltagareCompactData> = combineLatest([
|
|
||||||
this._limit$,
|
|
||||||
this._page$,
|
|
||||||
this._sort$,
|
|
||||||
this._onlyMyDeltagare$,
|
|
||||||
]).pipe(
|
|
||||||
switchMap(([limit, page, sort, onlyMyDeltagare]) => this._fetchAllDeltagare$(limit, page, sort, onlyMyDeltagare))
|
|
||||||
);
|
|
||||||
|
|
||||||
public reportsData$: Observable<ReportsData> = combineLatest([
|
|
||||||
this._limit$,
|
|
||||||
this._page$
|
|
||||||
]).pipe(switchMap(([limit, page]) => this._fetchReports$(limit, page)));
|
|
||||||
|
|
||||||
public setSort(newSortKey: keyof DeltagareCompact): void {
|
|
||||||
const currentSort = this._sort$.getValue();
|
|
||||||
const order =
|
|
||||||
currentSort.key === newSortKey && currentSort.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
|
||||||
|
|
||||||
this._sort$.next({ key: newSortKey, order });
|
|
||||||
}
|
|
||||||
|
|
||||||
public setPage(page: number): void {
|
|
||||||
this._page$.next(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchAllDeltagare$(
|
|
||||||
limit: number,
|
|
||||||
page: number,
|
|
||||||
sort: Sort<keyof DeltagareCompact>,
|
|
||||||
onlyMyDeltagare?: boolean
|
|
||||||
): Observable<DeltagareCompactData> {
|
|
||||||
const params: Params = {
|
|
||||||
sort: sort.key as string,
|
|
||||||
order: sort.order as string,
|
|
||||||
limit: limit.toString(),
|
|
||||||
page: page.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (onlyMyDeltagare) {
|
|
||||||
params.onlyMyDeltagare = onlyMyDeltagare.toString();
|
|
||||||
}
|
|
||||||
return this.httpClient
|
|
||||||
.get<DeltagareCompactApiResponse>(this._apiBaseUrl, {
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
map(({ data, meta }) => {
|
|
||||||
return { data: data.map(deltagare => mapResponseToDeltagareCompact(deltagare)), meta };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchReports$(
|
|
||||||
limit: number,
|
|
||||||
page: number
|
|
||||||
): Observable<ReportsData> {
|
|
||||||
const params: { [param: string]: string | string[] } = {
|
|
||||||
limit: limit.toString(),
|
|
||||||
page: page.toString()
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.httpClient
|
|
||||||
.get<ReportResponse>(`${this._apiBaseUrl}/report`, {
|
|
||||||
params
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
map(({ data, meta }) => {
|
|
||||||
data.sort((reportA, reportB) =>
|
|
||||||
+reportA.sendDate < +reportB.sendDate ? 1 : -1)
|
|
||||||
return { data: data.map(report => mapReportsResponseToReport(report)), meta };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setReportType(reportType: FormSelectItem): void {
|
|
||||||
this._reportType$.next(reportType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setCurrentDeltagareId(currentDeltagareId: string): void {
|
|
||||||
this._deltagare$.next(null);
|
|
||||||
this._currentDeltagareId$.next(currentDeltagareId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setOnlyMyDeltagare(value: boolean): void {
|
|
||||||
this._onlyMyDeltagare$.next(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchContactInformation$(id: string): Observable<ContactInformation | Partial<ContactInformation>> {
|
|
||||||
return this.httpClient.get<{ data: ContactInformationResponse }>(`${this._apiBaseUrl}/${id}/contact`).pipe(
|
|
||||||
map(({ data }) => mapResponseToContactInformation(data)),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of({});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchDriversLicense$(id: string): Observable<DriversLicense | Partial<DriversLicense>> {
|
|
||||||
return this.httpClient.get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/${id}/driverlicense`).pipe(
|
|
||||||
map(({ data }) => mapResponseToDriversLicense(data)),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of({});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchHighestEducation$(id: string): Observable<HighestEducation | Partial<HighestEducation>> {
|
|
||||||
return this.httpClient
|
|
||||||
.get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/${id}/educationlevels/highest`)
|
|
||||||
.pipe(
|
|
||||||
map(({ data }) => mapResponseToHighestEducation(data)),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of({});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchEducations$(id: string): Observable<Education[]> {
|
|
||||||
return this.httpClient.get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/${id}/educations`).pipe(
|
|
||||||
map(({ data }) =>
|
|
||||||
data.utbildningar
|
|
||||||
? data.utbildningar.sort((a, b) =>
|
|
||||||
sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
),
|
|
||||||
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}/${id}/translator`).pipe(
|
|
||||||
map(({ data }) => 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}/${id}/work/languages`).pipe(
|
|
||||||
map(({ data }) => data?.sprak?.map(sprak => sprak.beskrivning) || []),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of([]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchDisabilities$(id: string): Observable<Disability[]> {
|
|
||||||
return this.httpClient.get<{ data: DisabilityResponse[] }>(`${this._apiBaseUrl}/${id}/work/disabilities`).pipe(
|
|
||||||
map(({ data }) => data?.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning)) || []),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of([]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchWorkExperiences$(id: string): Observable<WorkExperience[]> {
|
|
||||||
return this.httpClient.get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/${id}/work/experiences`).pipe(
|
|
||||||
map(
|
|
||||||
({ data }) =>
|
|
||||||
data?.arbetslivserfarenheter?.sort((a, b) =>
|
|
||||||
sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
|
|
||||||
) || []
|
|
||||||
),
|
|
||||||
map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))),
|
|
||||||
catchError(error => {
|
|
||||||
this.errorService.add(errorToCustomError(error));
|
|
||||||
return of([]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _fetchAvropInformation$(id: string): Observable<Avrop | Partial<Avrop>> {
|
|
||||||
return this.httpClient.get<{ data: AvropResponse }>(`${this._apiBaseUrl}/${id}/avrop`).pipe(
|
|
||||||
map(({ data }) => (data ? mapAvropResponseToAvrop(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
|
|
||||||
private _fetchDeltagare$(id: string): Observable<Deltagare> {
|
|
||||||
return combineLatest([
|
|
||||||
this._fetchContactInformation$(id),
|
|
||||||
this._fetchDriversLicense$(id),
|
|
||||||
this._fetchHighestEducation$(id),
|
|
||||||
this._fetchEducations$(id),
|
|
||||||
this._fetchTranslator$(id),
|
|
||||||
this._fetchWorkLanguages$(id),
|
|
||||||
this._fetchDisabilities$(id),
|
|
||||||
this._fetchWorkExperiences$(id),
|
|
||||||
this._fetchAvropInformation$(id),
|
|
||||||
]).pipe(
|
|
||||||
map(
|
|
||||||
([
|
|
||||||
contactInformation,
|
|
||||||
driversLicense,
|
|
||||||
highestEducation,
|
|
||||||
educations,
|
|
||||||
translator,
|
|
||||||
workLanguages,
|
|
||||||
disabilities,
|
|
||||||
workExperiences,
|
|
||||||
avropInformation,
|
|
||||||
]: [
|
|
||||||
ContactInformation,
|
|
||||||
DriversLicense,
|
|
||||||
HighestEducation,
|
|
||||||
Education[],
|
|
||||||
string,
|
|
||||||
string[],
|
|
||||||
Disability[],
|
|
||||||
WorkExperience[],
|
|
||||||
Avrop
|
|
||||||
]) => ({
|
|
||||||
id,
|
|
||||||
...contactInformation,
|
|
||||||
driversLicense,
|
|
||||||
highestEducation,
|
|
||||||
educations,
|
|
||||||
translator,
|
|
||||||
workLanguages,
|
|
||||||
disabilities,
|
|
||||||
workExperiences,
|
|
||||||
avropInformation,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,6 +34,10 @@ export class UserService extends UnsubscribeDirective {
|
|||||||
public userRoles$: Observable<Role[]> = this._userRoles$.asObservable();
|
public userRoles$: Observable<Role[]> = this._userRoles$.asObservable();
|
||||||
private _selectedOrganizationNumber$ = new BehaviorSubject<string>(null);
|
private _selectedOrganizationNumber$ = new BehaviorSubject<string>(null);
|
||||||
|
|
||||||
|
public get userRolesSnapshot(): Role[] {
|
||||||
|
return this._userRoles$.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) {
|
constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) {
|
||||||
super();
|
super();
|
||||||
this._selectedOrganizationNumber$.next(this._selectedOrganizationNumber);
|
this._selectedOrganizationNumber$.next(this._selectedOrganizationNumber);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
|
import { ContactInformation } from '@msfa-models/contact-information.model';
|
||||||
|
import { Disability } from '@msfa-models/disability.model';
|
||||||
|
import { DriversLicense } from '@msfa-models/drivers-license.model';
|
||||||
|
import { Education } from '@msfa-models/education.model';
|
||||||
|
import { HighestEducation } from '@msfa-models/highest-education.model';
|
||||||
|
import { ReportsData } from '@msfa-models/reports.model';
|
||||||
|
import { WorkExperience } from '@msfa-models/work-experience.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DeltagareApiService } from './api/deltagare.api.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DeltagareCardService {
|
||||||
|
constructor(private deltagareApiService: DeltagareApiService) {}
|
||||||
|
|
||||||
|
public fetchContactInformation$(deltagareId: string): Observable<ContactInformation> {
|
||||||
|
return this.deltagareApiService.fetchContactInformation$(deltagareId);
|
||||||
|
}
|
||||||
|
public fetchAvropInformation$(deltagareId: string): Observable<Avrop> {
|
||||||
|
return this.deltagareApiService.fetchAvropInformation$(deltagareId) as Observable<Avrop>;
|
||||||
|
}
|
||||||
|
public fetchWorkExperiences$(deltagareId: string): Observable<WorkExperience[]> {
|
||||||
|
return this.deltagareApiService.fetchWorkExperiences$(deltagareId);
|
||||||
|
}
|
||||||
|
public fetchHighestEducation$(deltagareId: string): Observable<HighestEducation> {
|
||||||
|
return this.deltagareApiService.fetchHighestEducation$(deltagareId) as Observable<HighestEducation>;
|
||||||
|
}
|
||||||
|
public fetchEducations$(deltagareId: string): Observable<Education[]> {
|
||||||
|
return this.deltagareApiService.fetchEducations$(deltagareId);
|
||||||
|
}
|
||||||
|
public fetchDriversLicense$(deltagareId: string): Observable<DriversLicense> {
|
||||||
|
return this.deltagareApiService.fetchDriversLicense$(deltagareId) as Observable<DriversLicense>;
|
||||||
|
}
|
||||||
|
public fetchWorkLanguages$(deltagareId: string): Observable<string[]> {
|
||||||
|
return this.deltagareApiService.fetchWorkLanguages$(deltagareId);
|
||||||
|
}
|
||||||
|
public fetchDisabilities$(deltagareId: string): Observable<Disability[]> {
|
||||||
|
return this.deltagareApiService.fetchDisabilities$(deltagareId);
|
||||||
|
}
|
||||||
|
public fetchReports$(limit: number, page: number, deltagareId: string): Observable<ReportsData> {
|
||||||
|
return this.deltagareApiService.fetchReports$(limit, page, deltagareId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { SortOrder } from '@msfa-enums/sort-order.enum';
|
||||||
|
import { DeltagareCompact, DeltagareCompactData } from '@msfa-models/deltagare.model';
|
||||||
|
import { Sort } from '@msfa-models/sort.model';
|
||||||
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { DeltagareApiService } from './api/deltagare.api.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DeltagareService {
|
||||||
|
private _limit$ = new BehaviorSubject<number>(20);
|
||||||
|
private _page$ = new BehaviorSubject<number>(1);
|
||||||
|
private _sort$ = new BehaviorSubject<Sort<keyof DeltagareCompact>>({ key: 'fullName', order: SortOrder.ASC });
|
||||||
|
public sort$: Observable<Sort<keyof DeltagareCompact>> = this._sort$.asObservable();
|
||||||
|
private _onlyMyDeltagare$ = new BehaviorSubject<boolean>(false);
|
||||||
|
public onlyMyDeltagare$: Observable<boolean> = this._onlyMyDeltagare$.asObservable();
|
||||||
|
public allDeltagareData$: Observable<DeltagareCompactData> = combineLatest([
|
||||||
|
this._limit$,
|
||||||
|
this._page$,
|
||||||
|
this._sort$,
|
||||||
|
this._onlyMyDeltagare$,
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([limit, page, sort, onlyMyDeltagare]) =>
|
||||||
|
this.deltagareApiService.fetchAllDeltagare$(limit, page, sort, onlyMyDeltagare)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(private deltagareApiService: DeltagareApiService) {}
|
||||||
|
|
||||||
|
public setSort(newSortKey: keyof DeltagareCompact): void {
|
||||||
|
const currentSort = this._sort$.getValue();
|
||||||
|
const order =
|
||||||
|
currentSort.key === newSortKey && currentSort.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||||
|
|
||||||
|
this._sort$.next({ key: newSortKey, order });
|
||||||
|
}
|
||||||
|
|
||||||
|
public setPage(page: number): void {
|
||||||
|
this._page$.next(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setOnlyMyDeltagare(value: boolean): void {
|
||||||
|
this._onlyMyDeltagare$.next(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ export const ACTIVE_FEATURES_PROD: Feature[] = [
|
|||||||
Feature.MY_ACCOUNT,
|
Feature.MY_ACCOUNT,
|
||||||
Feature.MY_ORGANIZATION,
|
Feature.MY_ORGANIZATION,
|
||||||
Feature.ACCESSIBILITY_REPORT,
|
Feature.ACCESSIBILITY_REPORT,
|
||||||
|
Feature.DELTAGARE,
|
||||||
|
Feature.AVROP,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ACTIVE_FEATURES_TEST: Feature[] = [
|
export const ACTIVE_FEATURES_TEST: Feature[] = [
|
||||||
@@ -16,4 +18,5 @@ export const ACTIVE_FEATURES_TEST: Feature[] = [
|
|||||||
Feature.RELEASES,
|
Feature.RELEASES,
|
||||||
Feature.VERSION_INFO,
|
Feature.VERSION_INFO,
|
||||||
Feature.ACCESSIBILITY_REPORT,
|
Feature.ACCESSIBILITY_REPORT,
|
||||||
|
Feature.REPORTING,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ function generateAvrop(amount = 10, deltagare, handledare) {
|
|||||||
sparkod: track.kod,
|
sparkod: track.kod,
|
||||||
sparNamn: track.name,
|
sparNamn: track.name,
|
||||||
handledareCiamUserId: null,
|
handledareCiamUserId: null,
|
||||||
|
handledare: null,
|
||||||
recievedTimestamp: faker.date.recent(),
|
recievedTimestamp: faker.date.recent(),
|
||||||
hasAvbrott: currentDeltagare.hasAvbrott,
|
hasAvbrott: currentDeltagare.hasAvbrott,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user