diff --git a/.eslintrc.json b/.eslintrc.json index d7e458c..98e0e4c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,9 +11,7 @@ { "enforceBuildableLibDependency": true, "allow": [], - "depConstraints": [ - { "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] } - ] + "depConstraints": [{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }] } ] } diff --git a/apps/mina-sidor-fa/.eslintrc.json b/apps/mina-sidor-fa/.eslintrc.json index f602bc2..9695e63 100644 --- a/apps/mina-sidor-fa/.eslintrc.json +++ b/apps/mina-sidor-fa/.eslintrc.json @@ -14,6 +14,15 @@ ], "parserOptions": { "project": ["apps/mina-sidor-fa/tsconfig.*?.json"] }, "rules": { + "max-len": [ + 1, + 140, + 2, + { + "ignorePattern": "^import\\s.+\\sfrom\\s.+;$", + "ignoreUrls": true + } + ], "@angular-eslint/directive-selector": [ "error", { diff --git a/apps/mina-sidor-fa/src/app/app-routing.module.ts b/apps/mina-sidor-fa/src/app/app-routing.module.ts index 66dd96a..5795781 100644 --- a/apps/mina-sidor-fa/src/app/app-routing.module.ts +++ b/apps/mina-sidor-fa/src/app/app-routing.module.ts @@ -42,7 +42,7 @@ activeFeatures.forEach(feature => { case Feature.ADMINISTRATION: routes.push({ 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), canActivate: [AuthGuard, OrganizationGuard, RoleGuard], }); @@ -50,7 +50,7 @@ activeFeatures.forEach(feature => { case Feature.AVROP: routes.push({ 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), canActivate: [AuthGuard, OrganizationGuard, RoleGuard], }); @@ -58,7 +58,7 @@ activeFeatures.forEach(feature => { case Feature.DELTAGARE: routes.push({ 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), canActivate: [AuthGuard, OrganizationGuard, RoleGuard], }); diff --git a/apps/mina-sidor-fa/src/app/app.component.ts b/apps/mina-sidor-fa/src/app/app.component.ts index 80927f4..4ea91e8 100644 --- a/apps/mina-sidor-fa/src/app/app.component.ts +++ b/apps/mina-sidor-fa/src/app/app.component.ts @@ -1,9 +1,9 @@ import { DOCUMENT } from '@angular/common'; 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 { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { environment } from '@msfa-environment'; +import { filter, map, switchMap } from 'rxjs/operators'; @Component({ selector: 'msfa-root', diff --git a/apps/mina-sidor-fa/src/app/components/toast-list/toast/toast.component.scss b/apps/mina-sidor-fa/src/app/components/toast-list/toast/toast.component.scss index 7fde7dc..0e35d60 100644 --- a/apps/mina-sidor-fa/src/app/components/toast-list/toast/toast.component.scss +++ b/apps/mina-sidor-fa/src/app/components/toast-list/toast/toast.component.scss @@ -50,6 +50,7 @@ max-width: 400px !important; margin: 0; overflow-wrap: break-word; + white-space: pre-wrap; } &__close-button { diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/deltagare.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/deltagare.component.ts index e1cf5dd..cdd18c2 100644 --- a/apps/mina-sidor-fa/src/app/pages/deltagare/deltagare.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/deltagare.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { DeltagareCompact, DeltagareCompactData } from '@msfa-models/deltagare.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'; @Component({ diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.html new file mode 100644 index 0000000..91be9e1 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.html @@ -0,0 +1,83 @@ +
+
+

Arbetslivserfarenhet

+ +
    +
  • +

    {{ workExperience.employer }}

    + - +
    + {{ workExperience.profession }} +

    {{ workExperience.description }}

    +
  • +
+
+ + + {{ accordionExpanded ? 'Dölj' : 'Visa' }} fler arbetsgivare +
    +
  • +

    {{ workExperience.employer }}

    + - +
    + {{ workExperience.profession }} +

    {{ workExperience.description }}

    +
  • +
+
+
+
+
+

Utbildning

+
+
Högsta utbildningsnivå:
+
+ + {{ highestEducation.level.description }}: {{ highestEducation.sunKod.description }} + +
+ + +

+ Utbildningar: +

+
    +
  • +

    {{ education.organizer }}

    + - +
    + {{ education.education}} +

    {{ education.description }}

    +
  • +
+
+
+
+
+

Körkort

+
+
Har körkort
+
{{driversLicense.licenses.length ? 'Ja' : 'Nej'}}
+ +
Körkortsklasser
+
{{driversLicense.licenses.join(', ')}}
+
Tillgång till bil
+
{{driversLicense.accessToCar ? 'Ja' : 'Nej'}}
+
+
+
+
+ + + Info saknas + diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.scss b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.scss new file mode 100644 index 0000000..fb6b80c --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.scss @@ -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); + } +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.spec.ts new file mode 100644 index 0000000..42d612f --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DeltagareTabExperiencesComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DeltagareTabExperiencesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.ts new file mode 100644 index 0000000..771858b --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-experiences/deltagare-tab-experiences.component.ts @@ -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; + } +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.html new file mode 100644 index 0000000..0407d47 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.html @@ -0,0 +1,76 @@ +
+
+

Personuppgifter

+
+
Namn:
+
{{ contactInformation.fullName }}
+
Personnummer:
+
+ +
+ +
{{address.type}}:
+
+
+ {{ address.street }}
+ {{ address.postalCode }} {{ address.city }} +
+
+
+
Telefon:
+ + +
{{ phoneNumber.type }}: {{phoneNumber.number}}
+
+
+
E-postadress:
+
+ {{ contactInformation.email }} +
+
+
+ +
+

Om tjänsten

+
+
Tillhörande tjänst:
+
{{ avropInformation.tjanst }}
+
Datum för tjänstens början:
+
+ +
+
Datum för tjänstens slut:
+
+ +
+
Deltagandefrekvens:
+
+ {{ avropInformation.participationFrequency }} +
+
Nivå:
+
{{ avropInformation.trackName }}
+
Utförande verksamhet:
+
{{ avropInformation.utforandeVerksamhet }}
+
Utförande adress:
+
{{ avropInformation.utforandeAdress }}
+
Genomförandereferens:
+
{{ avropInformation.genomforandeReferens }}
+
+
+
+

Handledare

+
+
Tilldelad handledare:
+
{{ avropInformation.handledare }}
+
+
+
+
+ + +
Info saknas
+
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.scss b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.scss new file mode 100644 index 0000000..1d96ee1 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.scss @@ -0,0 +1,8 @@ +.deltagare-tab-personal-information { + display: contents; + + &__tab-column { + flex-grow: 1; + flex-basis: 0; + } +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.spec.ts new file mode 100644 index 0000000..060e099 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DeltagareTabPersonalInformationComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DeltagareTabPersonalInformationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.ts new file mode 100644 index 0000000..ba5b9b9 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-personal-information/deltagare-tab-personal-information.component.ts @@ -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; +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html new file mode 100644 index 0000000..5de91c8 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html @@ -0,0 +1,37 @@ +
+
+

Skapa ny rapport

+

Här kan du skicka rapporter om deltagaren till arbetsförmedlingen.

+ + + + Du måste välja en rapporttyp + +
+
+ +
+
+

Inskickade rapporter

+ +
+
+ + + + diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.scss b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.scss new file mode 100644 index 0000000..51ed0bc --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.scss @@ -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; + } +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.spec.ts new file mode 100644 index 0000000..7f10ba0 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.ts new file mode 100644 index 0000000..6a8142b --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.ts @@ -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(20); + private _page$ = new BehaviorSubject(1); + private _type$ = new BehaviorSubject(null); + readonly reportsFormControlName = 'reports'; + + public currentDeltagareId$: Observable = this.activatedRoute.params.pipe( + map(params => params.deltagareId as string), + distinctUntilChanged(([prevDeltagareId], [currDeltagareId]) => prevDeltagareId === currDeltagareId) + ); + + reportsData$: Observable = 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 = [ + { 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); + } +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.html new file mode 100644 index 0000000..1835596 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.html @@ -0,0 +1,36 @@ +
+
+

Funktionsnedsättningar

+ +
+ +
Funktionsnedsättning {{index + 1}}
+
+ {{ disability.title }} + {{ disability.description }} +
+
+
+ +

Deltagaren har inga funktionsnedsättningar registrerad.

+
+
+
+

Språk

+
+
Behov av tolk:
+
{{avropInformation.tolkbehov ? 'Ja (' + avropInformation.tolkbehov + ')' : 'Nej'}}
+
Behov av språkstöd:
+
{{avropInformation.sprakstod ? 'Ja (' + avropInformation.sprakstod + ')' : 'Nej'}}
+ +
Språk som kan användas på jobbet:
+
{{ workLanguages.length ? workLanguages.join(', ') : 'Info saknas'}}
+
+
+
+
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.scss b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.scss new file mode 100644 index 0000000..ed7bcc5 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.scss @@ -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; + } + } +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.spec.ts new file mode 100644 index 0000000..2b7dd07 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DeltagareTabSensitiveInformationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DeltagareTabSensitiveInformationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.ts new file mode 100644 index 0000000..d48e3ef --- /dev/null +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component.ts @@ -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[]; +} diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.html index a78f01b..3781135 100644 --- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.html +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.html @@ -1,240 +1,70 @@ -
+
Tillbaka till deltagarlistan

Deltagarinformation

- -
-
-

Personuppgifter

-
-
Namn:
-
{{ deltagare.fullName }}
-
Personnummer:
-
- -
- -
{{address.type}}:
-
-
- {{ address.street }}
- {{ address.postalCode }} {{ address.city }} -
-
-
-
Telefon:
- - -
{{ phoneNumber.type }}: {{phoneNumber.number}}
-
-
-
E-postadress:
-
- {{ deltagare.email }} -
-
-
-
-

Om tjänsten

-
-
Tillhörande tjänst:
-
{{ deltagare.avropInformation.tjanst }}
-
Datum för tjänstens början:
-
- -
-
Datum för tjänstens slut:
-
- -
-
Deltagandefrekvens:
-
- {{ deltagare.avropInformation.participationFrequency }} -
-
Nivå:
-
- {{ deltagare.avropInformation.trackName }} -
-
Utförande verksamhet:
-
- {{ deltagare.avropInformation.utforandeVerksamhet }} -
-
Utförande adress:
-
- {{ deltagare.avropInformation.utforandeAdress }} -
-
Genomförandereferens:
-
- {{ deltagare.avropInformation.genomforandeReferens }} -
-
-
-
-

Handledare

-
-
Tilldelad handledare:
-
- {{ deltagare.avropInformation.handledare }} -
-
-
-
+ + + + - -
-

Skapa ny rapport

-

Här kan du skicka rapporter om deltagaren till arbetsförmedlingen.

-
- - - - Du måste välja en rapporttyp - -
-
-
- -
-
-

Inskickade rapporter

- -
-
- -
-
-

Arbetslivserfarenhet

- -
    -
  • -

    {{ workExperience.employer }}

    - - -
    - {{ workExperience.profession }} -

    {{ workExperience.description }}

    -
  • -
-
- - - {{ accordionExpanded ? 'Dölj' : 'Visa' }} fler arbetsgivare -
    -
  • -

    {{ workExperience.employer }}

    - - -
    - {{ workExperience.profession }} -

    {{ workExperience.description }}

    -
  • -
-
-
-
-
-

Utbildning

-
-
Högsta utbildningsnivå:
-
- {{ deltagare.highestEducation.level.description }}: {{ deltagare.highestEducation.sunKod.description - }} -
-

Utbildningar:

-
    -
  • -

    {{ education.organizer }}

    - - -
    - {{ education.education}} -

    {{ education.description }}

    -
  • -
-
-
-
-

Körkort

-
-
Har körkort
-
{{deltagare.driversLicense.licenses.length ? 'Ja' : 'Nej'}}
- -
Körkortsklasser
-
{{deltagare.driversLicense.licenses.join(', ')}}
-
Tillgång till bil
-
{{deltagare.driversLicense.accessToCar ? 'Ja' : 'Nej'}}
-
-
-
-
-
- -
-
-

Funktionsnedsättningar

-
- -
Funktionsnedsättning {{index + 1}}
-
- {{ disability.title }} - {{ disability.description }} -
-
-
-
-
-

Språk

-
-
Behov av tolk:
-
- {{deltagare.avropInformation.tolkbehov ? 'Ja (' + deltagare.avropInformation.tolkbehov + ')' : 'Nej'}} -
-
Behov av språkstöd:
-
- {{deltagare.avropInformation.sprakstod ? 'Ja (' + deltagare.avropInformation.sprakstod + ')' : 'Nej'}} -
-
Språk som kan användas på jobbet:
-
{{ deltagare.workLanguages.join(', ')}}
-
-
-
+ + + + + + + + + + + + + + + +
@@ -242,15 +72,5 @@ - - - -
- - Info saknas -
-
- - - Info saknas + diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.scss b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.scss index dddea31..e69cf87 100644 --- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.scss +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.scss @@ -1,6 +1,4 @@ -@import 'mixins/list'; @import 'variables/gutters'; -@import 'variables/z-index'; .deltagare-card { &__tab-contents { @@ -9,68 +7,10 @@ margin: 0 $digi--layout--gutter--l; } - &__tab-column { - 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 { + &__header { display: flex; flex-direction: row-reverse; align-items: center; justify-content: space-between; } - - &__footer { - margin-top: $digi--layout--gutter--l; - } } diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.spec.ts index 8308801..e12814f 100644 --- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.spec.ts +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.spec.ts @@ -1,7 +1,6 @@ -import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { LayoutComponent } from '@msfa-shared/components/layout/layout.component'; import { DeltagareCardComponent } from './deltagare-card.component'; @@ -14,7 +13,8 @@ describe('DeltagareCardComponent', () => { waitForAsync(() => { void TestBed.configureTestingModule({ declarations: [DeltagareCardComponent, LayoutComponent], - imports: [RouterTestingModule, HttpClientTestingModule, DigiNgSkeletonBaseModule, ReactiveFormsModule], + imports: [RouterTestingModule, HttpClientTestingModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); }) ); diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.ts index b590b6a..b108100 100644 --- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.component.ts @@ -1,14 +1,21 @@ -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 { ActivatedRoute } from '@angular/router'; +import { Feature } from '@msfa-enums/feature.enum'; import { IconType } from '@msfa-enums/icon-type.enum'; -import { Deltagare } from '@msfa-models/deltagare.model'; -import { ReportsData } from '@msfa-models/reports.model'; +import { RoleEnum } from '@msfa-enums/role.enum'; +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 { DeltagareService } from '@msfa-services/api/deltagare.service'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { UserService } from '@msfa-services/api/user.service'; +import { DeltagareCardService } from '@msfa-services/deltagare-card.service'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { distinctUntilChanged, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators'; @Component({ selector: 'msfa-deltagare-card', @@ -17,85 +24,110 @@ import { map } from 'rxjs/operators'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class DeltagareCardComponent { - deltagare$: Observable = this.deltagareService.deltagare$; - reportsData$: Observable = this.deltagareService.reportsData$; + private _activeFeatures: Feature[] = environment.activeFeatures; + private _activeTab$ = new BehaviorSubject('0'); + private _userRoles: Role[] = this.userService.userRolesSnapshot; - readonly reportsFormControlName = 'reports'; - - reportPickerFormGroup: FormGroup = this.formBuilder.group({ - // eslint-disable-next-line @typescript-eslint/unbound-method - reports: this.formBuilder.control('', [Validators.required]), - }); - - selectableReportTypes: Array = [ - { name: 'Avvikelse', value: 'avvikelse' }, - { name: 'Gemensam Planering', value: 'planering' }, - ]; - selectedReportType: FormSelectItem; - - firstVisibleWorkExperiences$: Observable = this.deltagare$.pipe( - map(deltagare => deltagare.workExperiences.slice(0, 2)) + public activeTab$: Observable = this._activeTab$.asObservable(); + public currentDeltagareId$: Observable = this.activatedRoute.params.pipe( + map(params => params.deltagareId as string), + distinctUntilChanged(([prevDeltagareId], [currDeltagareId]) => prevDeltagareId === currDeltagareId) ); - hiddenWorkExperiences$: Observable = this.deltagare$.pipe( - map(deltagare => deltagare.workExperiences.slice(2)) + contactInformation$: Observable = combineLatest([this.currentDeltagareId$]).pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchContactInformation$(deltagareId)), + shareReplay(1) + ); + avropInformation$: Observable = combineLatest([this.currentDeltagareId$, this._activeTab$]).pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchAvropInformation$(deltagareId)), + shareReplay(1) + ); + workExperiences$: Observable = this.currentDeltagareId$.pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchWorkExperiences$(deltagareId)), + shareReplay(1) + ); + highestEducation$: Observable = this.currentDeltagareId$.pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchHighestEducation$(deltagareId)), + shareReplay(1) + ); + educations$: Observable = this.currentDeltagareId$.pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchEducations$(deltagareId)), + shareReplay(1) + ); + driversLicense$: Observable = this.currentDeltagareId$.pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchDriversLicense$(deltagareId)), + shareReplay(1) + ); + workLanguages$: Observable = this.currentDeltagareId$.pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchWorkLanguages$(deltagareId)), + shareReplay(1) + ); + disabilities$: Observable = this.currentDeltagareId$.pipe( + switchMap(([deltagareId]) => this.deltagareCardService.fetchDisabilities$(deltagareId)), + shareReplay(1) + ); + + tab0Loading$: Observable = combineLatest([this.contactInformation$, this.avropInformation$]).pipe( + map(([contactInformation, avropInformation]) => !(contactInformation && avropInformation)), + startWith(true) + ); + + tab2Loading$: Observable = combineLatest([ + this.workExperiences$, + this.highestEducation$, + this.educations$, + this.driversLicense$, + ]).pipe( + map( + ([workExperiences, highestEducation, educations, driversLicense]) => + !(workExperiences && highestEducation && educations && driversLicense) + ), + startWith(true) + ); + tab3Loading$: Observable = 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 = this.workExperiences$.pipe( + filter(workExperiences => !!workExperiences), + map(workExperiences => workExperiences.slice(0, 2)) + ); + hiddenWorkExperiences$: Observable = this.workExperiences$.pipe( + filter(workExperiences => !!workExperiences), + map(workExperiences => workExperiences.slice(2)) ); iconType = IconType; - accordionExpanded = false; constructor( private activatedRoute: ActivatedRoute, - private deltagareService: DeltagareService, - private formBuilder: FormBuilder, - private router: Router - ) { - this.deltagareService.setCurrentDeltagareId(this.activatedRoute.snapshot.params.deltagareId); - } + private deltagareCardService: DeltagareCardService, + private userService: UserService + ) {} - get reportsFormControl(): AbstractControl | null { - return this.reportPickerFormGroup?.get(this.reportsFormControlName); - } - - 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); + setActiveTab(tab: number): void { + this._activeTab$.next(tab.toString()); } } diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.module.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.module.ts index 1b8faf8..7b8056d 100644 --- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.module.ts +++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-card/deltagare-card.module.ts @@ -1,39 +1,45 @@ import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select'; import { DigiNgLayoutExpansionPanelModule } from '@af/digi-ng/_layout/layout-expansion-panel'; 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 { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.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 { 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 { DeltagareCardComponent } from './deltagare-card.component'; @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], - declarations: [DeltagareCardComponent], + declarations: [ + DeltagareCardComponent, + DeltagareTabExperiencesComponent, + DeltagareTabReportsComponent, + DeltagareTabPersonalInformationComponent, + DeltagareTabSensitiveInformationComponent, + ], imports: [ CommonModule, RouterModule.forChild([{ path: '', component: DeltagareCardComponent }]), + ReactiveFormsModule, ReportsModule, LayoutModule, - DigiNgLinkInternalModule, - IconModule, BackLinkModule, - DigiNgLayoutExpansionPanelModule, HideTextModule, - DigiNgSkeletonBaseModule, + LoaderModule, + DigiNgLayoutExpansionPanelModule, DigiNgPopoverModule, DigiNgLinkButtonModule, DigiNgFormSelectModule, - ReactiveFormsModule ], exports: [DeltagareCardComponent], }) -export class DeltagareCardModule { } +export class DeltagareCardModule {} diff --git a/apps/mina-sidor-fa/src/app/shared/components/layout/components/sidebar/sidebar.component.ts b/apps/mina-sidor-fa/src/app/shared/components/layout/components/sidebar/sidebar.component.ts index 8ead4b8..73ba2f5 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/layout/components/sidebar/sidebar.component.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/layout/components/sidebar/sidebar.component.ts @@ -31,7 +31,9 @@ export class SidebarComponent { get deltagareVisible(): boolean { return ( 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 + ) ); } } diff --git a/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts b/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts index be37919..44c3784 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts @@ -1,6 +1,5 @@ import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive'; import { Employee } from '@msfa-models/employee.model'; diff --git a/apps/mina-sidor-fa/src/app/shared/components/loader/loader.component.scss b/apps/mina-sidor-fa/src/app/shared/components/loader/loader.component.scss index 85cdb76..70aa16a 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/loader/loader.component.scss +++ b/apps/mina-sidor-fa/src/app/shared/components/loader/loader.component.scss @@ -1,4 +1,5 @@ @import 'mixins/backdrop'; +@import 'variables/gutters'; @import 'variables/z-index'; @keyframes spinning { @@ -12,6 +13,10 @@ align-items: center; justify-content: center; + &--padded { + padding: $digi--layout--gutter--l; + } + &--absolute { @include msfa__backdrop($msfa__z-index-backdrop, false); } diff --git a/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts b/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts index c39c64a..0626aa2 100644 --- a/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts +++ b/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts @@ -8,4 +8,5 @@ export enum Feature { MOCK_LOGIN, VERSION_INFO, ACCESSIBILITY_REPORT, + REPORTING, } diff --git a/apps/mina-sidor-fa/src/app/shared/enums/loader-type.enum.ts b/apps/mina-sidor-fa/src/app/shared/enums/loader-type.enum.ts index bcc91e2..23fb4a9 100644 --- a/apps/mina-sidor-fa/src/app/shared/enums/loader-type.enum.ts +++ b/apps/mina-sidor-fa/src/app/shared/enums/loader-type.enum.ts @@ -1,4 +1,5 @@ export enum LoaderType { FULL_SCREEN = 'fullscreen', ABSOLUTE = 'absolute', + PADDED = 'padded', } diff --git a/apps/mina-sidor-fa/src/app/shared/guards/role.guard.ts b/apps/mina-sidor-fa/src/app/shared/guards/role.guard.ts index c49f554..cd69dc3 100644 --- a/apps/mina-sidor-fa/src/app/shared/guards/role.guard.ts +++ b/apps/mina-sidor-fa/src/app/shared/guards/role.guard.ts @@ -12,12 +12,12 @@ export class RoleGuard implements CanActivate { constructor(private router: Router, private userService: UserService) {} canActivate(route: ActivatedRouteSnapshot): Observable { - const expectedRole: RoleEnum = route.data.expectedRole as RoleEnum; + const expectedRoles: RoleEnum[] = route.data.expectedRoles as RoleEnum[]; return this.userService.userRoles$.pipe( filter(roles => !!roles), map(roles => { - const userHasRole = roles.some(role => role.type === expectedRole); + const userHasRole = roles.some(role => expectedRoles.includes(role.type)); if (userHasRole) { return true; diff --git a/apps/mina-sidor-fa/src/app/shared/models/api/avrop.response.model.ts b/apps/mina-sidor-fa/src/app/shared/models/api/avrop.response.model.ts index 73eeeea..1672bd4 100644 --- a/apps/mina-sidor-fa/src/app/shared/models/api/avrop.response.model.ts +++ b/apps/mina-sidor-fa/src/app/shared/models/api/avrop.response.model.ts @@ -27,7 +27,8 @@ export interface AvropResponse { sprakstod: string; sparkod: string; sparNamn: string; - supervisorId: number; + handledareCiamUserId: string; + handledare: string; recievedTimestamp: string; } diff --git a/apps/mina-sidor-fa/src/app/shared/models/avrop.model.ts b/apps/mina-sidor-fa/src/app/shared/models/avrop.model.ts index a337838..0e018c1 100644 --- a/apps/mina-sidor-fa/src/app/shared/models/avrop.model.ts +++ b/apps/mina-sidor-fa/src/app/shared/models/avrop.model.ts @@ -19,6 +19,8 @@ export interface Avrop extends AvropCompact { genomforandeReferens: number; // genomforandeReferens participationFrequency: number; // deltagandeGrad utforandeVerksamhet: string; // utforandeverksamhet + handledareCiamUserId: string; + handledare: string; } export interface AvropCompactData { @@ -42,6 +44,8 @@ export function mapAvropResponseToAvrop(data: AvropResponse): Avrop { genomforandeReferens, deltagandeGrad, utforandeverksamhet, + handledareCiamUserId, + handledare, } = data; return { @@ -59,5 +63,7 @@ export function mapAvropResponseToAvrop(data: AvropResponse): Avrop { genomforandeReferens, participationFrequency: deltagandeGrad, utforandeVerksamhet: utforandeverksamhet, + handledareCiamUserId, + handledare, }; } diff --git a/apps/mina-sidor-fa/src/app/shared/models/error/custom-error.ts b/apps/mina-sidor-fa/src/app/shared/models/error/custom-error.ts index 3e1d6d1..5784e51 100644 --- a/apps/mina-sidor-fa/src/app/shared/models/error/custom-error.ts +++ b/apps/mina-sidor-fa/src/app/shared/models/error/custom-error.ts @@ -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 message = error.message || error; const severity = ErrorSeverity.HIGH; diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare-api.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/deltagare-api.service.ts deleted file mode 100644 index 4c15092..0000000 --- a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare-api.service.ts +++ /dev/null @@ -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(null); - private _limit$ = new BehaviorSubject(20); - private _page$ = new BehaviorSubject(1); - private _sort$ = new BehaviorSubject>({ key: 'fullName', order: SortOrder.ASC }); - public sort$: Observable> = 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(null); - public deltagare$: Observable = this._deltagare$.asObservable(); - - public allDeltagareData$: Observable = 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 - ): Observable { - const params: Params = { - sort: sort.key as string, - order: sort.order as string, - limit: limit.toString(), - page: page.toString(), - }; - - return this.httpClient - .get(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> { - 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> { - 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> { - 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 { - 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 { - 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 { - 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 { - 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 { - 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> { - 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 { - 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, - }) - ) - ); - } -} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.api.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.api.service.ts new file mode 100644 index 0000000..eb3e4e6 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.api.service.ts @@ -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, + onlyMyDeltagare?: boolean + ): Observable { + 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(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 { + 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(`${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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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> { + 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}` }) + ); + }) + ); + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts deleted file mode 100644 index ee971cf..0000000 --- a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts +++ /dev/null @@ -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(null); - private _limit$ = new BehaviorSubject(20); - private _page$ = new BehaviorSubject(1); - private _sort$ = new BehaviorSubject>({ key: 'fullName', order: SortOrder.ASC }); - public sort$: Observable> = this._sort$.asObservable(); - private _reportType$ = new BehaviorSubject(null); - reportType$: Observable = this._reportType$.asObservable(); - private _onlyMyDeltagare$ = new BehaviorSubject(false); - public onlyMyDeltagare$: Observable = 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(null); - public deltagare$: Observable = this._deltagare$.asObservable(); - - public allDeltagareData$: Observable = combineLatest([ - this._limit$, - this._page$, - this._sort$, - this._onlyMyDeltagare$, - ]).pipe( - switchMap(([limit, page, sort, onlyMyDeltagare]) => this._fetchAllDeltagare$(limit, page, sort, onlyMyDeltagare)) - ); - - public reportsData$: Observable = 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, - onlyMyDeltagare?: boolean - ): Observable { - 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(this._apiBaseUrl, { - params, - }) - .pipe( - map(({ data, meta }) => { - return { data: data.map(deltagare => mapResponseToDeltagareCompact(deltagare)), meta }; - }) - ); - } - - private _fetchReports$( - limit: number, - page: number - ): Observable { - const params: { [param: string]: string | string[] } = { - limit: limit.toString(), - page: page.toString() - }; - - return this.httpClient - .get(`${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> { - 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> { - 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> { - 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 { - 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 { - 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 { - 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 { - 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 { - 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> { - 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 { - 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, - }) - ) - ); - } -} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts index 0f0cc54..e3813fb 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts @@ -34,6 +34,10 @@ export class UserService extends UnsubscribeDirective { public userRoles$: Observable = this._userRoles$.asObservable(); private _selectedOrganizationNumber$ = new BehaviorSubject(null); + public get userRolesSnapshot(): Role[] { + return this._userRoles$.getValue(); + } + constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) { super(); this._selectedOrganizationNumber$.next(this._selectedOrganizationNumber); diff --git a/apps/mina-sidor-fa/src/app/shared/services/deltagare-card.service.ts b/apps/mina-sidor-fa/src/app/shared/services/deltagare-card.service.ts new file mode 100644 index 0000000..50e865c --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/services/deltagare-card.service.ts @@ -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 { + return this.deltagareApiService.fetchContactInformation$(deltagareId); + } + public fetchAvropInformation$(deltagareId: string): Observable { + return this.deltagareApiService.fetchAvropInformation$(deltagareId) as Observable; + } + public fetchWorkExperiences$(deltagareId: string): Observable { + return this.deltagareApiService.fetchWorkExperiences$(deltagareId); + } + public fetchHighestEducation$(deltagareId: string): Observable { + return this.deltagareApiService.fetchHighestEducation$(deltagareId) as Observable; + } + public fetchEducations$(deltagareId: string): Observable { + return this.deltagareApiService.fetchEducations$(deltagareId); + } + public fetchDriversLicense$(deltagareId: string): Observable { + return this.deltagareApiService.fetchDriversLicense$(deltagareId) as Observable; + } + public fetchWorkLanguages$(deltagareId: string): Observable { + return this.deltagareApiService.fetchWorkLanguages$(deltagareId); + } + public fetchDisabilities$(deltagareId: string): Observable { + return this.deltagareApiService.fetchDisabilities$(deltagareId); + } + public fetchReports$(limit: number, page: number, deltagareId: string): Observable { + return this.deltagareApiService.fetchReports$(limit, page, deltagareId); + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/services/deltagare.service.ts b/apps/mina-sidor-fa/src/app/shared/services/deltagare.service.ts new file mode 100644 index 0000000..284fa83 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/services/deltagare.service.ts @@ -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(20); + private _page$ = new BehaviorSubject(1); + private _sort$ = new BehaviorSubject>({ key: 'fullName', order: SortOrder.ASC }); + public sort$: Observable> = this._sort$.asObservable(); + private _onlyMyDeltagare$ = new BehaviorSubject(false); + public onlyMyDeltagare$: Observable = this._onlyMyDeltagare$.asObservable(); + public allDeltagareData$: Observable = 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); + } +} diff --git a/apps/mina-sidor-fa/src/environments/active-features.ts b/apps/mina-sidor-fa/src/environments/active-features.ts index 558c712..00aae06 100644 --- a/apps/mina-sidor-fa/src/environments/active-features.ts +++ b/apps/mina-sidor-fa/src/environments/active-features.ts @@ -5,6 +5,8 @@ export const ACTIVE_FEATURES_PROD: Feature[] = [ Feature.MY_ACCOUNT, Feature.MY_ORGANIZATION, Feature.ACCESSIBILITY_REPORT, + Feature.DELTAGARE, + Feature.AVROP, ]; export const ACTIVE_FEATURES_TEST: Feature[] = [ @@ -16,4 +18,5 @@ export const ACTIVE_FEATURES_TEST: Feature[] = [ Feature.RELEASES, Feature.VERSION_INFO, Feature.ACCESSIBILITY_REPORT, + Feature.REPORTING, ]; diff --git a/mock-api/mina-sidor-fa/scripts/avrop.js b/mock-api/mina-sidor-fa/scripts/avrop.js index 862523e..d0c0c21 100644 --- a/mock-api/mina-sidor-fa/scripts/avrop.js +++ b/mock-api/mina-sidor-fa/scripts/avrop.js @@ -57,6 +57,7 @@ function generateAvrop(amount = 10, deltagare, handledare) { sparkod: track.kod, sparNamn: track.name, handledareCiamUserId: null, + handledare: null, recievedTimestamp: faker.date.recent(), hasAvbrott: currentDeltagare.hasAvbrott, });