Merge pull request #200 in TEA/mina-sidor-fa-web from feature/report-changes to develop

Squashed commit of the following:

commit 72a580d3529536bfb118c0a6a45a57ca8f1ef165
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Oct 14 12:18:29 2021 +0200

    Added better error-handling

commit f41eece77e11c325715f93f175e3250630a75382
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Oct 14 07:08:08 2021 +0200

    Updated padding of report-links

commit e37d973707b48d7c351618ba1ec65637ffd3c9cb
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Oct 14 07:02:43 2021 +0200

    Fixed minor issues after PR

commit 6dbf12f71f6ef4ae1dab89c92b2e23725bbce956
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Oct 13 15:36:41 2021 +0200

    Made adjustments to signal to match other reports

commit f2260dd20eb0a7ecf8e42d18db721fe4831ee760
Merge: ec6d6ca9 bdb1d161
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Oct 13 12:06:02 2021 +0200

    Merge branch 'develop' into feature/report-changes

commit ec6d6ca9a177345994f5bab60fe0bbc908634fe3
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Oct 13 09:00:03 2021 +0200

    WIP - signal
This commit is contained in:
Erik Tiekstra
2021-10-15 11:43:50 +02:00
parent c68d69be3f
commit 3dfbf4683e
42 changed files with 621 additions and 555 deletions

View File

@@ -66,10 +66,7 @@ const routes: Routes = [
{
path: 'signal',
data: { title: 'Skapa signal om arbete eller studier' },
loadChildren: () =>
import('./pages/report-forms/deltagare-signal-arbete-studier/deltagare-signal-arbete-studier.module').then(
m => m.DeltagareSignalArbeteStudierModule
),
loadChildren: () => import('./pages/report-forms/signal-form/signal-form.module').then(m => m.SignalFormModule),
},
];

View File

@@ -2,29 +2,35 @@
<h3>Skapa ny rapport</h3>
<p>Här kan du skicka rapporter om deltagaren till Arbetsförmedlingen.</p>
<!-- TODO put these in a styled list-->
<ul class="deltagare-tab-reports__button-list">
<li>
<digi-ng-link-button
class="deltagare-tab-reports__button"
[afRoute]="'./gemensam-planering'"
afRoute="./gemensam-planering"
afText="Skapa ny Gemensam planering"
></digi-ng-link-button>
</li>
<li>
<digi-ng-link-button
class="deltagare-tab-reports__button"
[afRoute]="'./franvarorapport'"
afRoute="./franvarorapport"
afText="Skapa ny Avvikelserapport (frånvaro)"
></digi-ng-link-button>
</li>
<li>
<digi-ng-link-button
class="deltagare-tab-reports__button"
[afRoute]="'./avvikelserapport'"
afRoute="./avvikelserapport"
afText="Skapa ny Avvikelserapport (avvikelse)"
></digi-ng-link-button>
</li>
<li>
<digi-ng-link-button
class="deltagare-tab-reports__button"
afRoute="./signal"
afText="Skapa ny Signal om arbete eller studier"
></digi-ng-link-button>
</li>
</ul>
<ng-container *ngIf="reportsData; else loadingRef">

View File

@@ -21,7 +21,7 @@
&__button {
::ng-deep .digi-ng-link-button {
padding: var(--digi--layout--padding--15) var(--digi--layout--padding--30) !important;
padding: var(--digi--layout--padding--10) var(--digi--layout--padding--30) !important;
}
}
}

View File

@@ -128,6 +128,15 @@
<dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
</ng-container>
</dl>
<digi-notification-alert
*ngIf="error$ | async as error"
class="avvikelse-report__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Avvikelserapport (avvikelse). Ladda om sidan och försök igen.</p>
<p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
</digi-notification-alert>
</digi-ng-dialog>
</ng-template>
</div>

View File

@@ -40,6 +40,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
submitIsLoading$ = new BehaviorSubject<boolean>(false);
error$ = new BehaviorSubject<CustomError>(null);
genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
map((params: Params) => +params.genomforandeReferens)
@@ -155,9 +156,10 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
this.submittedDate$.next(new Date());
this.confirmDialogIsOpen$.next(false);
},
error: error => {
error: (customError: CustomError) => {
this.error$.next({ ...customError, message: customError.error.message });
this.submitIsLoading$.next(false);
throw new CustomError(error);
throw { ...customError, avoidToast: true };
},
})
);
@@ -165,6 +167,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
cancelConfirmDialog(): void {
this.confirmDialogIsOpen$.next(false);
this.error$.next(null);
}
ngOnDestroy(): void {

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { AvvikelseReportRequest } from '@msfa-models/api/avvikelse-request.model';
import { Avrop } from '@msfa-models/avrop.model';
import { AvvikelseQuestion } from '@msfa-models/avvikelse-question.model';
import { AvvikelseReason } from '@msfa-models/avvikelse-reason.model';
import { AvvikelseApiService } from '@msfa-services/api/avvikelse-api.service';
@@ -21,7 +22,7 @@ export class AvvikelseReportFormService {
return this.avvikelseApiService.createAvvikelse$(avvikelse);
}
fetchAvropInformation$(genomforandeReferens: number) {
fetchAvropInformation$(genomforandeReferens: number): Observable<Avrop> {
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
}
}

View File

@@ -1,12 +0,0 @@
<section class="deltagare-confirm">
<h3 class="deltagare-confirm__header">Ersättningsgrund sysselsättning</h3>
<p>{{ ersattningsgrundTypNamn }}</p>
<ng-container *ngIf="formGroup?.get('percent').value;">
<h3 class="deltagare-confirm__header">Antal procent vid deltid</h3>
<p>{{formGroup?.get('percent').value}} %</p>
</ng-container>
<h3 class="deltagare-confirm__header">Startdatum</h3>
<p>{{formGroup?.get('date').value}}</p>
</section>

View File

@@ -1,14 +0,0 @@
@import 'variables/gutters';
.deltagare-confirm {
margin-bottom: $digi--layout--gutter--xl;
p {
margin-top: 0;
}
&__header {
font-size: var(--digi--typography--font-size--l);
margin: 0;
}
}

View File

@@ -1,25 +0,0 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DeltagareConfirmSignalFormComponent } from './deltagare-confirm-signal-form.component';
describe('DeltagareConfirmSignalFormComponent', () => {
let component: DeltagareConfirmSignalFormComponent;
let fixture: ComponentFixture<DeltagareConfirmSignalFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareConfirmSignalFormComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DeltagareConfirmSignalFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,17 +0,0 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { getErsattningsGrundTyp } from '@msfa-enums/ersattning-grund-typ-kod.enum';
@Component({
selector: 'msfa-deltagare-confirm-signal-form',
templateUrl: './deltagare-confirm-signal-form.component.html',
styleUrls: ['./deltagare-confirm-signal-form.component.scss'],
changeDetection: ChangeDetectionStrategy.Default,
})
export class DeltagareConfirmSignalFormComponent {
@Input() formGroup: FormGroup | null = null;
get ersattningsgrundTypNamn(): string {
return getErsattningsGrundTyp(this.formGroup?.get('ersattningsTyp').value);
}
}

View File

@@ -1,11 +0,0 @@
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DeltagareConfirmSignalFormComponent } from './deltagare-confirm-signal-form.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareConfirmSignalFormComponent],
imports: [CommonModule],
exports: [DeltagareConfirmSignalFormComponent],
})
export class DeltagareConfirmSignalFormModule {}

View File

@@ -1,117 +0,0 @@
<msfa-layout *ngIf="avrop$ | async as avrop, else skeletenRef">
<msfa-report-layout
[currentStep]="currentStep"
[totalAmountOfSteps]="totalAmountOfSteps"
description="Här skickar du signal om att deltagare har påbörjat anställning eller utbildning."
reportTitle="Signal om arbete eller studier"
reportSubTitle="Skicka signal"
reportName="Signal om arbete eller studier"
[avrop]="avrop"
>
<div *ngIf="submittedDate$ | async as submittedDate" class="deltagare-signal__confirmation">
<digi-notification-alert
af-heading="Allt gick bra"
af-heading-level="h3"
af-variation="success"
class="deltagare-signal__alert"
>
<p>
Signalrapport för deltagare {{avrop.fullName}} är nu inskickad till Arbetsförmedlingen och inväntar
godkännande.
</p>
<dl>
<dt>Datum</dt>
<dd>{{submittedDate | date:'longDate'}} kl {{submittedDate | date:'shortTime'}}</dd>
</dl>
</digi-notification-alert>
<msfa-back-link [route]="['../']">Tillbaka till deltagaren</msfa-back-link>
</div>
<div *ngIf="currentStep === 1">
<h3 class="deltagare-signal__ersattning-heading">Har den sökande fått arbete eller påbörjat studier?</h3>
<h4>Ange sysselsättning (obligatoriskt)</h4>
<form [formGroup]="signalArbeteStudierFormGroup">
<div class="deltagare-signal__ersattning">
<digi-ng-form-radiobutton-group
[afRadiobuttons]="ersattningsTypAlternatives"
[formControlName]="ersattningsTypFormControlName"
[afRequired]="true"
></digi-ng-form-radiobutton-group>
<digi-form-validation-message
af-variation="error"
*ngIf="ersattningsTypFormControl.invalid && (ersattningsTypFormControl.dirty || ersattningsTypFormControl.touched)"
>
Typ av sysselsättning är obligatoriskt
</digi-form-validation-message>
</div>
<div class="deltagare-signal__input" *ngIf="partTimeIsSelected">
<digi-ng-form-input
afLabel="Antal procent vid deltid (valfritt)"
afDescription
[formControlName]="percentFormControlName"
[afInvalid]="percentFormControl.invalid && (percentFormControl.dirty || percentFormControl.touched)"
afType="number"
[afInvalidMessage]="'Ange procent från 1 till 99'"
></digi-ng-form-input>
</div>
<div class="deltagare-signal__datepicker">
<digi-ng-form-datepicker
[formControlName]="dateFormControlName"
afLabel="Välj startdatum för anställning/utbildning"
[afDisableValidStyle]="true"
[afMinDate]="setMinDate(avrop.startDate)"
[afMaxDate]="setMaxDate(avrop.startDate)"
></digi-ng-form-datepicker>
</div>
</form>
</div>
<msfa-deltagare-confirm-signal-form
*ngIf="currentStep === totalAmountOfSteps"
[formGroup]="signalArbeteStudierFormGroup"
></msfa-deltagare-confirm-signal-form>
<div class="deltagare-signal__alert-wrapper">
<digi-notification-alert
*ngIf="error$ | async as error"
af-heading="Någonting gick fel"
af-variation="danger"
class="deltagare-signal__alert"
>
<p>Kunde inte spara signalrapporten. Ladda om sidan och försök igen.</p>
<p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
</digi-notification-alert>
</div>
<div class="deltagare-signal__step-buttons-wrapper">
<ng-container *ngIf="(submittedDate$ | async) === null">
<digi-button
(afOnClick)="previousStep()"
*ngIf="currentStep > 1"
af-size="m"
af-variation="secondary"
class="deltagare-signal__step-buttons-wrapper--space-right"
>
Tillbaka
</digi-button>
<digi-button (afOnClick)="openConfirmDialog = true" *ngIf="currentStep === totalAmountOfSteps" af-size="m">
Skicka in
</digi-button>
</ng-container>
<digi-button (afOnClick)="nextStep" *ngIf="currentStep === (totalAmountOfSteps -1)" af-size="m">
Förhandsgranska
</digi-button>
</div>
</msfa-report-layout>
<msfa-confirm-dialog
[openConfirmDialog]="openConfirmDialog"
reportToConfirm="signal om arbete eller studier"
(confirmDialogChanged)="setConfirmDialogChanged($event)"
>
</msfa-confirm-dialog>
</msfa-layout>
<ng-template #skeletenRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar data för signal om arbete eller studier"></digi-ng-skeleton-base>
</ng-template>

View File

@@ -1,39 +0,0 @@
@import 'variables/gutters';
.deltagare-signal {
&__confirmation,
&__deltagare,
&__ersattning,
&__input,
&__alert-wrapper,
&__datepicker {
max-width: var(--digi--typography--text--max-width);
margin-bottom: $digi--layout--gutter--xl;
}
&__input {
width: 330px;
}
&__ersattning-heading {
font-size: var(--digi--typography--font-size--h3);
}
&__step-buttons-wrapper--space-right {
margin-right: var(--digi--layout--gutter--s);
}
&__go-back {
margin-right: $digi--layout--gutter;
}
&__step-buttons-wrapper--space-right {
margin-right: var(--digi--layout--gutter--s);
}
&__alert {
max-width: var(--digi--typography--text--max-width);
margin-bottom: $digi--layout--gutter--xl;
}
}

View File

@@ -1,142 +0,0 @@
import { RadiobuttonModel } from '@af/digi-ng/_form/form-radiobutton-group';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { ConfirmDialog } from '@msfa-enums/confirm-dialog.enum';
import { ErrorType } from '@msfa-enums/error-type.enum';
import { ErsattningsGrundTypKod, getErsattningsGrundTyp } from '@msfa-enums/ersattning-grund-typ-kod.enum';
import { Avrop } from '@msfa-models/avrop.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { SignalArbeteStudier } from '@msfa-models/signal-arbete-studier.model';
import { DeltagareSignalArbeteStudierService } from '@msfa-services/api/deltagare-signal-arbete-studier.service';
import { DeltagareApiService } from '@msfa-services/api/deltagare.api.service';
import { RequiredValidator } from '@msfa-utils/validators/required.validator';
import { add } from 'date-fns';
import { BehaviorSubject, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Component({
selector: 'msfa-deltagare-signal-arbete-studier',
templateUrl: './deltagare-signal-arbete-studier.component.html',
styleUrls: ['./deltagare-signal-arbete-studier.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeltagareSignalArbeteStudierComponent implements OnInit {
readonly ersattningsTypFormControlName = 'ersattningsTyp';
readonly dateFormControlName = 'date';
readonly percentFormControlName = 'percent';
private _submittedDate$ = new BehaviorSubject<Date | null>(null);
private _error$ = new BehaviorSubject<CustomError>(null);
submittedDate$ = this._submittedDate$.asObservable();
error$ = this._error$.asObservable();
totalAmountOfSteps = 2;
currentStep = 1;
openConfirmDialog = false;
avrop$: Observable<Avrop>;
signalArbeteStudierFormGroup: FormGroup | null = null;
todayDate = new Date().toISOString().slice(0, 10);
ersattningsTypAlternatives: RadiobuttonModel[] = [
{
label: getErsattningsGrundTyp(ErsattningsGrundTypKod.ARBETE_HELTID),
value: ErsattningsGrundTypKod.ARBETE_HELTID,
},
{
label: getErsattningsGrundTyp(ErsattningsGrundTypKod.ARBETE_DELTID),
value: ErsattningsGrundTypKod.ARBETE_DELTID,
},
{
label: getErsattningsGrundTyp(ErsattningsGrundTypKod.UTBILDNING_HELTID),
value: ErsattningsGrundTypKod.UTBILDNING_HELTID,
},
{
label: getErsattningsGrundTyp(ErsattningsGrundTypKod.UTBILDNING_DELTID),
value: ErsattningsGrundTypKod.UTBILDNING_DELTID,
},
];
constructor(
private deltagareSignalArbeteStudierService: DeltagareSignalArbeteStudierService,
private deltagareApiService: DeltagareApiService,
private activatedRoute: ActivatedRoute
) {}
ngOnInit(): void {
this.avrop$ = this.activatedRoute.params.pipe(
switchMap(({ genomforandeReferens }) => this.deltagareApiService.fetchAvropInformation$(genomforandeReferens))
);
this.signalArbeteStudierFormGroup = new FormGroup({
ersattningsTyp: new FormControl(null, [RequiredValidator()]),
date: new FormControl(new Date().toLocaleDateString(), [RequiredValidator()]),
percent: new FormControl('', [Validators.min(1), Validators.max(99)]),
});
}
setConfirmDialogChanged(confirm: ConfirmDialog): void {
this.openConfirmDialog = false;
if (confirm === ConfirmDialog.ACCEPTED) {
const postSignalArbeteStudier: SignalArbeteStudier = {
sokandeId: +this.activatedRoute.snapshot.params['genomforandeReferens'],
ersattningsgrund: this.ersattningsTypFormControl.value as number,
startDatum: this.dateFormControl.value as Date,
antalProcent: +this.percentFormControl.value,
};
this.deltagareSignalArbeteStudierService
.createSignalArbeteStudier$(postSignalArbeteStudier)
.then(() => {
this._submittedDate$.next(new Date());
this.signalArbeteStudierFormGroup.reset();
this.currentStep = 3;
})
.catch((error: Error) => {
this._error$.next(new CustomError({ error, message: error.message, type: ErrorType.API }));
});
}
}
get ersattningsTypFormControl(): AbstractControl | undefined {
return this.signalArbeteStudierFormGroup?.get(this.ersattningsTypFormControlName);
}
get dateFormControl(): AbstractControl | undefined {
return this.signalArbeteStudierFormGroup?.get(this.dateFormControlName);
}
get percentFormControl(): AbstractControl | undefined {
return this.signalArbeteStudierFormGroup?.get(this.percentFormControlName);
}
get nextStep(): number {
console.log(this.signalArbeteStudierFormGroup);
this.signalArbeteStudierFormGroup.markAllAsTouched();
if (this.signalArbeteStudierFormGroup.valid && this.currentStep < this.totalAmountOfSteps) {
return this.currentStep++;
}
}
get partTimeIsSelected(): boolean {
return (
+this.ersattningsTypFormControl.value === ErsattningsGrundTypKod.ARBETE_DELTID ||
+this.ersattningsTypFormControl.value === ErsattningsGrundTypKod.UTBILDNING_DELTID
);
}
setMinDate(startdatumAvrop: Date): Date {
return new Date(startdatumAvrop);
}
setMaxDate(startdatumAvrop: Date): Date {
return add(new Date(startdatumAvrop), { months: 10, days: 1 });
}
previousStep(): void {
if (this.currentStep > 1) {
this.currentStep--;
this._submittedDate$.next(null);
this._error$.next(null);
}
}
}

View File

@@ -28,7 +28,7 @@
</dd>
</dl>
</digi-notification-alert>
<msfa-back-link [route]="['/deltagare/'+ genomforandeReferens]">Tillbaka till deltagaren</msfa-back-link>
<msfa-back-link route="../">Tillbaka till deltagaren</msfa-back-link>
</div>
<ng-template #formRef>
<form
@@ -46,7 +46,6 @@
[afSelectItems]="reasons"
[afAnnounceIfOptional]="true"
[afRequired]="true"
[afAnnounceIfOptional]="true"
[afDisableValidStyle]="true"
[afInvalid]="formControlIsInvalid(['reason'])"
(afOnChange)="reasonChanged()"
@@ -250,15 +249,6 @@
</ng-container>
<footer class="franvaro-report-form__footer">
<digi-notification-alert
*ngIf="error$ | async as error"
class="franvaro-report-form__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Avvikelserapport (frånvaro). Ladda om sidan och försök igen.</p>
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>
</digi-notification-alert>
<div class="franvaro-report-form__cta-wrapper">
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
@@ -327,6 +317,15 @@
<dt>Tid för förväntad närvaro</dt>
<dd>{{expectedPresenceStartTimeFormControl.value}} - {{expectedPresenceEndTimeFormControl.value}}</dd>
</dl>
<digi-notification-alert
*ngIf="error$ | async as error"
class="franvaro-report-form__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Avvikelserapport (frånvaro). Ladda om sidan och försök igen.</p>
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>
</digi-notification-alert>
</digi-ng-dialog>
</ng-template>
</ng-template>

View File

@@ -3,7 +3,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { ANNAN_KAND_ORSAK_ID, ANNAN_ORSAK_ID } from '@msfa-constants/franvaro-reasons';
import { ErrorType } from '@msfa-enums/error-type.enum';
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
import { Avrop } from '@msfa-models/avrop.model';
import { CustomError } from '@msfa-models/error/custom-error';
@@ -11,7 +10,7 @@ import { FranvaroReason } from '@msfa-models/franvaro-reason.model';
import { Franvaro } from '@msfa-models/franvaro.model';
import { dateToIsoString } from '@msfa-utils/format-to-date.util';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { FranvaroReportFormService } from './franvaro-report-form.service';
import { FranvaroReportFormValidator } from './franvaro-report-form.validator';
@@ -143,13 +142,14 @@ export class FranvaroReportFormComponent {
cancelConfirmDialog(): void {
this.confirmDialogOpen$.next(false);
this.error$.next(null);
}
reasonChanged(): void {
this.otherKnownReasonFormControl.reset();
}
async submitAndCloseConfirmDialog(genomforandeReferens: number): Promise<void> {
submitAndCloseConfirmDialog(genomforandeReferens: number): void {
this.submitLoading$.next(true);
const {
@@ -185,17 +185,20 @@ export class FranvaroReportFormComponent {
},
};
return this.franvaroReportFormService
.postFranvaroReport(postRequest)
.then(() => {
this.lastSubmittedFranvaroReport$.next(new Date());
})
.catch((error: Error) => {
this.error$.next(new CustomError({ error, message: error.message, type: ErrorType.API }));
})
.finally(() => {
this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
this.franvaroReportFormService
.postFranvaroReport$(postRequest)
.pipe(take(1))
.subscribe({
next: () => {
this.lastSubmittedFranvaroReport$.next(new Date());
this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
},
error: (customError: CustomError) => {
this.error$.next({ ...customError, message: customError.error.message });
this.submitLoading$.next(false);
throw { ...customError, avoidToast: true };
},
});
}
}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
import { Avrop } from '@msfa-models/avrop.model';
import { FranvaroReason } from '@msfa-models/franvaro-reason.model';
import { DeltagareApiService } from '@msfa-services/api/deltagare.api.service';
import { FranvaroReportApiService } from '@msfa-services/api/franvaro-report.api.service';
import { Observable } from 'rxjs';
@@ -10,13 +11,16 @@ export class FranvaroReportFormService {
public reasons$: Observable<FranvaroReason[]> = this.franvaroReportApiService.fetchReasons$();
public otherKnownReasons$: Observable<FranvaroReason[]> = this.franvaroReportApiService.fetchOtherKnownReasons$();
constructor(private franvaroReportApiService: FranvaroReportApiService) {}
constructor(
private franvaroReportApiService: FranvaroReportApiService,
private deltagareApiService: DeltagareApiService
) {}
public fetchAvropInformation$(genomforandeReferens: number): Observable<Avrop> {
return this.franvaroReportApiService.fetchAvropInformation$(genomforandeReferens);
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
}
public async postFranvaroReport(requestData: FranvaroReportRequest): Promise<void> {
public postFranvaroReport$(requestData: FranvaroReportRequest): Observable<void> {
return this.franvaroReportApiService.postFranvaroReport$(requestData);
}
}

View File

@@ -32,7 +32,7 @@
<dd>{{lastSubmittedGP | date:'longDate'}} kl {{lastSubmittedGP | date:'shortTime'}}</dd>
</dl>
</digi-notification-alert>
<msfa-back-link [route]="['/deltagare/'+ genomforandeReferens]">Tillbaka till deltagaren</msfa-back-link>
<msfa-back-link route="../">Tillbaka till deltagaren</msfa-back-link>
</div>
<ng-template #formRef>
<form
@@ -78,15 +78,6 @@
</digi-form-fieldset>
<footer class="gemensam-planering-form__footer">
<digi-notification-alert
*ngIf="error$ | async as error"
class="gemensam-planering-form__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Gemensam planering. Ladda om sidan och försök igen.</p>
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>
</digi-notification-alert>
<div class="gemensam-planering-form__cta-wrapper">
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
@@ -148,6 +139,15 @@
</ul>
</dd>
</dl>
<digi-notification-alert
*ngIf="error$ | async as error"
class="gemensam-planering-form__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Gemensam planering. Ladda om sidan och försök igen.</p>
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>
</digi-notification-alert>
</digi-ng-dialog>
</ng-template>
</ng-template>

View File

@@ -2,7 +2,6 @@ import { RadiobuttonGroupDirection, RadiobuttonModel } from '@af/digi-ng/_form/f
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { ErrorType } from '@msfa-enums/error-type.enum';
import { Activity } from '@msfa-models/activity.model';
import { Avrop } from '@msfa-models/avrop.model';
import { CustomError } from '@msfa-models/error/custom-error';
@@ -11,9 +10,9 @@ import {
mapGemensamPlaneringToGemensamPlaneringPostRequest,
} from '@msfa-models/gemensam-planering.model';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { GemensamPlaneringFormService } from './gemensam-planering-form.service';
import { GemensamPlaneringValidator } from './gemensam-planering.validator';
import { GemensamPlaneringFormValidator } from './gemensam-planering-form.validator';
@Component({
selector: 'msfa-gemensam-planering-form',
@@ -45,7 +44,7 @@ export class GemensamPlaneringFormComponent {
distance: new FormControl(false),
activityIds: new FormArray(this.obligatoryActivityIds.map(id => new FormControl(id))),
},
[GemensamPlaneringValidator.isGemensamPlaneringValid(this.obligatoryActivityIds)]
[GemensamPlaneringFormValidator.isGemensamPlaneringValid(this.obligatoryActivityIds)]
);
distanceRadiobuttons: RadiobuttonModel[] = [
@@ -118,9 +117,10 @@ export class GemensamPlaneringFormComponent {
cancelConfirmDialog(): void {
this.confirmDialogOpen$.next(false);
this.error$.next(null);
}
async submitAndCloseConfirmDialog(genomforandeReferens: number): Promise<void> {
submitAndCloseConfirmDialog(genomforandeReferens: number): void {
this.submitLoading$.next(true);
const { distance, activityIds } = this.gpFormGroup.value as GemensamPlanering;
@@ -131,17 +131,20 @@ export class GemensamPlaneringFormComponent {
genomforandeReferens,
};
return this.gemensamPlaneringFormService
.postGemensamPlanering(mapGemensamPlaneringToGemensamPlaneringPostRequest(postRequest))
.then(() => {
this.lastSubmittedGP$.next(new Date());
})
.catch((error: Error) => {
this.error$.next(new CustomError({ error, message: error.message, type: ErrorType.API }));
})
.finally(() => {
this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
this.gemensamPlaneringFormService
.postGemensamPlanering$(mapGemensamPlaneringToGemensamPlaneringPostRequest(postRequest))
.pipe(take(1))
.subscribe({
next: () => {
this.lastSubmittedGP$.next(new Date());
this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
},
error: (customError: CustomError) => {
this.error$.next({ ...customError, message: customError.error.message });
this.submitLoading$.next(false);
throw { ...customError, avoidToast: true };
},
});
}
}

View File

@@ -15,7 +15,7 @@ export class GemensamPlaneringFormService {
return this.gemensamPlaneringApiService.fetchAvropInformation$(genomforandeReferens);
}
public async postGemensamPlanering(requestData: GemensamPlaneringPostRequest): Promise<void> {
public postGemensamPlanering$(requestData: GemensamPlaneringPostRequest): Observable<void> {
return this.gemensamPlaneringApiService.postGemensamPlanering$(requestData);
}
}

View File

@@ -1,6 +1,6 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export class GemensamPlaneringValidator {
export class GemensamPlaneringFormValidator {
static isGemensamPlaneringValid(obligatoryActivityIds: number[] = []): ValidatorFn {
return (c: AbstractControl): { [key: string]: string } => {
let errors: { [key: string]: string } = null;

View File

@@ -0,0 +1,159 @@
<msfa-layout>
<msfa-report-layout
*ngIf="avrop$ | async as avrop, else skeletonRef"
[avrop]="avrop"
description="Här skickar du signal om att deltagare har påbörjat anställning eller studier."
reportTitle="Skapa Signal om arbete eller studier"
>
<div class="signal-form" *ngIf="currentGenomforandeReferens$ | async as genomforandeReferens">
<div class="signal-form__confirmation" *ngIf="lastSubmittedSignal$ | async as lastSubmittedSignal; else formRef">
<digi-notification-alert af-variation="success" af-heading="Allt gick bra" af-heading-level="h3">
<p>
Signal om arbete eller studier för deltagare {{avrop.fullName}} är nu inskickad till Arbetsförmedlingen och
inväntar godkännande.
</p>
<dl>
<dt>Datum</dt>
<dd>{{lastSubmittedSignal | date:'longDate'}} kl {{lastSubmittedSignal | date:'shortTime'}}</dd>
</dl>
</digi-notification-alert>
<msfa-back-link route="../">Tillbaka till deltagaren</msfa-back-link>
</div>
<ng-template #formRef>
<form class="signal-form__form" [formGroup]="signalFormGroup" id="signal-form" (ngSubmit)="openConfirmDialog()">
<div class="signal-form__form-item">
<digi-ng-form-select
[formControl]="typeFormControl"
afLabel="Typ av sysselsättning"
afPlaceholder="Välj typ av sysselsättning"
[afSelectItems]="typeSelectItems"
[afRequired]="true"
[afAnnounceIfOptional]="true"
[afDisableValidStyle]="true"
[afInvalid]="formControlIsInvalid('type')"
></digi-ng-form-select>
<div aria-atomic="true" role="alert">
<digi-ng-form-validation-message
*ngIf="formControlIsInvalid('type')"
class="signal-form__validation-message"
[afPositive]="false"
[afValidationText]="formErrors.type"
></digi-ng-form-validation-message>
</div>
</div>
<div class="signal-form__form-item">
<digi-form-fieldset af-legend="Omfattning" af-name="omfattning" af-form="signal-form">
<digi-ng-form-radiobutton-group
[afRadiobuttons]="omfattningRadioButtons"
[afRequired]="true"
[formControl]="omfattningFormControl"
></digi-ng-form-radiobutton-group>
</digi-form-fieldset>
<div aria-atomic="true" role="alert">
<digi-ng-form-validation-message
*ngIf="formControlIsInvalid('omfattning')"
class="signal-form__validation-message"
[afPositive]="false"
[afValidationText]="formErrors.omfattning"
></digi-ng-form-validation-message>
</div>
</div>
<div class="signal-form__form-item" *ngIf="showPercentFormControl">
<digi-ng-form-range
class="signal-form__percent-range"
[afMin]="5"
[afMax]="100"
[afStep]="5"
afValueSuffix="%"
[formControl]="percentFormControl"
afLabel="Antal procent vid deltid"
></digi-ng-form-range>
<div aria-atomic="true" role="alert">
<digi-ng-form-validation-message
*ngIf="formControlIsInvalid('percent')"
class="signal-form__validation-message"
[afPositive]="false"
[afValidationText]="formErrors.percent"
></digi-ng-form-validation-message>
</div>
</div>
<div class="signal-form__form-item">
<digi-ng-form-datepicker
[formControl]="startDateFormControl"
[afLabel]="'Startdatum för ' + (typeFormControl.value || 'arbete/utbildning')"
[afDisableValidStyle]="true"
[afRequired]="true"
[afInvalid]="formControlIsInvalid('startDate')"
[afAnnounceIfOptional]="true"
[afMinDate]="avrop.startDate"
[afMaxDate]="getMaxDate(avrop.startDate)"
></digi-ng-form-datepicker>
<div aria-atomic="true" role="alert">
<digi-ng-form-validation-message
*ngIf="formControlIsInvalid('startDate')"
class="signal-form__validation-message"
[afPositive]="false"
[afValidationText]="formErrors.startDate"
></digi-ng-form-validation-message>
</div>
</div>
<footer class="signal-form__footer">
<div class="signal-form__cta-wrapper">
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
<span>Avbryt</span>
<span class="msfa__a11y-sr-only">&nbsp;och gå tillbaka till deltagaren</span>
</msfa-back-link>
</div>
</footer>
<digi-ng-dialog
[afActive]="confirmDialogOpen$ | async"
(afOnPrimaryClick)="submitAndCloseConfirmDialog(genomforandeReferens)"
(afOnInactive)="cancelConfirmDialog()"
afHeadingLevel="h2"
afPrimaryButtonText="Skicka in"
afSecondaryButtonText="Avbryt"
(afOnSecondaryClick)="cancelConfirmDialog()"
afHeading="Vill du skicka in Signal om arbete eller studier"
afAriaLabel="Förhandsgranska och skicka in Signal om arbete eller studier"
id="confirm-signal"
>
<msfa-loader *ngIf="submitLoading$ | async" type="absolute"></msfa-loader>
<dl>
<dt>Typ av sysselsättning</dt>
<dd>{{typeFormControl.value}}</dd>
<dt>Omfattning</dt>
<dd>{{omfattningFormControl.value}}</dd>
<ng-container *ngIf="showPercentFormControl">
<dt>Antal procent vid deltid</dt>
<dd>{{percentFormControl.value}}%</dd>
</ng-container>
<dt>Startdatum</dt>
<dd><digi-typography-time [afDateTime]="startDateFormValueAsDate"></digi-typography-time></dd>
</dl>
<digi-notification-alert
*ngIf="error$ | async as error"
class="signal-form__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Signal om arbete eller studier. Ladda om sidan och försök igen.</p>
<p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
</digi-notification-alert>
</digi-ng-dialog>
</form>
</ng-template>
</div>
</msfa-report-layout>
</msfa-layout>
<ng-template #skeletonRef>
<digi-ng-skeleton-base
[afCount]="3"
afText="Laddar data för att kunna skapa Signal om arbete eller studier"
></digi-ng-skeleton-base>
</ng-template>

View File

@@ -0,0 +1,34 @@
@import 'variables/gutters';
@import 'variables/z-index';
.signal-form {
max-width: var(--digi--typography--text--max-width);
&__confirmation,
&__warning,
&__form {
display: flex;
flex-direction: column;
gap: $digi--layout--gutter--l;
}
&__footer {
display: flex;
flex-direction: column;
gap: var(--digi--layout--gutter);
}
&__cta-wrapper {
display: flex;
gap: var(--digi--layout--gutter);
}
&__validation-message {
margin-top: var(--digi--layout--gutter--s);
}
&__percent-range {
max-width: 20rem;
display: block;
}
}

View File

@@ -1,26 +1,28 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DeltagareSignalArbeteStudierComponent } from './deltagare-signal-arbete-studier.component';
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
import { SignalFormComponent } from './signal-form.component';
import { SignalFormService } from './signal-form.service';
describe('DeltagareSignalArbeteStudierComponent', () => {
let component: DeltagareSignalArbeteStudierComponent;
let fixture: ComponentFixture<DeltagareSignalArbeteStudierComponent>;
describe('SignalFormComponent', () => {
let component: SignalFormComponent;
let fixture: ComponentFixture<SignalFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareSignalArbeteStudierComponent, LayoutComponent],
declarations: [SignalFormComponent, LayoutComponent],
imports: [RouterTestingModule, HttpClientTestingModule, ReactiveFormsModule, DigiNgFormRadiobuttonGroupModule],
providers: [SignalFormService],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DeltagareSignalArbeteStudierComponent);
fixture = TestBed.createComponent(SignalFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,147 @@
import { RadiobuttonModel } from '@af/digi-ng/_form/form-radiobutton-group';
import { FormSelectItem } from '@af/digi-ng/_form/form-select';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { SignalRequest } from '@msfa-models/api/signal.request.model';
import { Avrop } from '@msfa-models/avrop.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { Signal } from '@msfa-models/signal.model';
import { dateToIsoString } from '@msfa-utils/format-to-date.util';
import { add } from 'date-fns';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { SignalFormService } from './signal-form.service';
import { SignalFormValidator } from './signal-form.validator';
@Component({
selector: 'msfa-signal-form',
templateUrl: './signal-form.component.html',
styleUrls: ['./signal-form.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SignalFormComponent {
shouldValidate$ = new BehaviorSubject<boolean>(false);
confirmDialogOpen$ = new BehaviorSubject<boolean>(false);
signalFormGroup = new FormGroup(
{
type: new FormControl(null),
omfattning: new FormControl('heltid'),
percent: new FormControl(50),
startDate: new FormControl(new Date()),
},
[SignalFormValidator.isSignalValid()]
);
submitLoading$ = new BehaviorSubject<boolean>(false);
lastSubmittedSignal$ = new BehaviorSubject<Date>(null);
error$ = new BehaviorSubject<CustomError>(null);
currentGenomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
map(params => +params.genomforandeReferens)
);
avrop$: Observable<Avrop> = this.currentGenomforandeReferens$.pipe(
switchMap(genomforandeReferens => this.signalFormService.fetchAvropInformation$(genomforandeReferens)),
shareReplay(1)
);
typeSelectItems: FormSelectItem[] = [
{
name: 'Arbete',
value: 'arbete',
},
{
name: 'Utbildning',
value: 'utbildning',
},
];
omfattningRadioButtons: RadiobuttonModel[] = [
{
label: 'Heltid',
value: 'heltid',
},
{
label: 'Deltid',
value: 'deltid',
},
];
constructor(private signalFormService: SignalFormService, private activatedRoute: ActivatedRoute) {}
get formErrors(): { [key: string]: string } {
return this.signalFormGroup.errors || {};
}
get typeFormControl(): FormControl {
return this.signalFormGroup.get('type') as FormControl;
}
get omfattningFormControl(): FormControl {
return this.signalFormGroup.get('omfattning') as FormControl;
}
get percentFormControl(): FormControl {
return this.signalFormGroup.get('percent') as FormControl;
}
get startDateFormControl(): FormControl {
return this.signalFormGroup.get('startDate') as FormControl;
}
get showPercentFormControl(): boolean {
return this.omfattningFormControl.value === 'deltid';
}
get startDateFormValueAsDate(): Date {
return new Date(this.startDateFormControl.value);
}
formControlIsInvalid(formControlName: string): boolean {
return this.formErrors[formControlName] && this.shouldValidate$.getValue();
}
getMaxDate(startDate: Date): Date {
return add(new Date(startDate), { months: 10, days: 1 });
}
openConfirmDialog(): void {
this.shouldValidate$.next(true);
if (this.signalFormGroup.invalid) {
return;
}
this.confirmDialogOpen$.next(true);
}
cancelConfirmDialog(): void {
this.confirmDialogOpen$.next(false);
this.error$.next(null);
}
submitAndCloseConfirmDialog(genomforandeReferens: number): void {
this.submitLoading$.next(true);
const { type, omfattning, startDate, percent } = this.signalFormGroup.value as Signal;
const postRequest: SignalRequest = {
genomforandeReferens: +genomforandeReferens,
typ: type,
omfattning,
omfattning_procent: omfattning === 'deltid' ? percent : null,
startdatum: dateToIsoString(startDate),
};
this.signalFormService
.postSignal$(postRequest)
.pipe(take(1))
.subscribe({
next: () => {
this.lastSubmittedSignal$.next(new Date());
this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
},
error: (customError: CustomError) => {
this.error$.next({ ...customError, message: customError.error.message });
this.submitLoading$.next(false);
throw { ...customError, avoidToast: true };
},
});
}
}

View File

@@ -1,7 +1,9 @@
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
import { DigiNgFormTextareaModule } from '@af/digi-ng/_form/form-textarea';
import { DigiNgFormRangeModule } from '@af/digi-ng/_form/form-range';
import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
import { DigiNgFormValidationMessageModule } from '@af/digi-ng/_form/form-validation-message';
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@@ -11,27 +13,29 @@ import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.modu
import { ConfirmDialogModule } from '@msfa-shared/components/confirm-dialog/confirm-dialog.module';
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
import { ReportLayoutModule } from '../../../components/report-layout/report-layout.module';
import { DeltagareConfirmSignalFormModule } from './components/deltagare-confirm-signal-form/deltagare-confirm-signal-form.module';
import { DeltagareSignalArbeteStudierComponent } from './deltagare-signal-arbete-studier.component';
import { SignalFormComponent } from './signal-form.component';
import { SignalFormService } from './signal-form.service';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareSignalArbeteStudierComponent],
declarations: [SignalFormComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: DeltagareSignalArbeteStudierComponent }]),
RouterModule.forChild([{ path: '', component: SignalFormComponent }]),
LayoutModule,
ReactiveFormsModule,
DigiNgFormRadiobuttonGroupModule,
DigiNgFormDatepickerModule,
DigiNgFormTextareaModule,
DigiNgFormInputModule,
DeltagareConfirmSignalFormModule,
ReportLayoutModule,
ConfirmDialogModule,
DigiNgSkeletonBaseModule,
BackLinkModule,
DigiNgFormRadiobuttonGroupModule,
DigiNgFormDatepickerModule,
DigiNgSkeletonBaseModule,
DigiNgDialogModule,
DigiNgFormRangeModule,
DigiNgFormValidationMessageModule,
DigiNgFormSelectModule,
],
exports: [DeltagareSignalArbeteStudierComponent],
providers: [SignalFormService],
exports: [SignalFormComponent],
})
export class DeltagareSignalArbeteStudierModule {}
export class SignalFormModule {}

View File

@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { SignalRequest } from '@msfa-models/api/signal.request.model';
import { Avrop } from '@msfa-models/avrop.model';
import { DeltagareApiService } from '@msfa-services/api/deltagare.api.service';
import { SignalApiService } from '@msfa-services/api/signal.api.service';
import { Observable } from 'rxjs';
@Injectable()
export class SignalFormService {
constructor(private signalApiService: SignalApiService, private deltagareApiService: DeltagareApiService) {}
public postSignal$(requestData: SignalRequest): Observable<void> {
return this.signalApiService.postSignal$(requestData);
}
public fetchAvropInformation$(genomforandeReferens: number): Observable<Avrop> {
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
}
}

View File

@@ -0,0 +1,40 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { Signal } from '@msfa-models/signal.model';
export class SignalFormValidator {
static isSignalValid(): ValidatorFn {
return (c: AbstractControl): { [key: string]: string } => {
let errors: { [key: string]: string } = null;
const { type, omfattning, percent, startDate } = c.value as Signal;
if (!type) {
errors = {
...errors,
type: 'Typ av sysselsättning måste väljas',
};
}
if (type && omfattning === 'deltid') {
if (percent < 5) {
errors = {
...errors,
percent: 'Antal procent måste vara högre än 5%',
};
}
if (percent > 100) {
errors = {
...errors,
percent: 'Antal procent måste vara lägre än 100%',
};
}
}
if (!startDate) {
errors = {
...errors,
startDate: 'Startdatum måste väljas',
};
}
return errors;
};
}
}

View File

@@ -3,6 +3,7 @@ export const DELTAGARE_REPORTING_ROUTES = {
'periodisk-redovisning': 'Periodisk redovisning',
franvarorapport: 'Avvikelserapport (frånvaro)',
avvikelserapport: 'Avvikelserapport (avvikelse)',
signal: 'Signal om arbete eller studier',
};
export const NAVIGATION = {

View File

@@ -1,19 +0,0 @@
export enum ErsattningsGrundTypKod {
ARBETE_HELTID = 1,
ARBETE_DELTID = 2,
UTBILDNING_HELTID = 3,
UTBILDNING_DELTID = 4,
}
export function getErsattningsGrundTyp(kod: number): string {
switch (kod) {
case 1:
return 'Arbete heltid';
case 2:
return 'Arbete deltid';
case 3:
return 'Utbildning heltid';
case 4:
return 'Utbildning deltid'
}
}

View File

@@ -2,7 +2,7 @@ import { ErrorHandler, Injectable } from '@angular/core';
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
import { Feature } from '@msfa-enums/feature.enum';
import { environment } from '@msfa-environment';
import { CustomError, errorToCustomError } from '@msfa-models/error/custom-error';
import { CustomError } from '@msfa-models/error/custom-error';
import { ErrorService } from '@msfa-services/error.service';
@Injectable()
@@ -11,9 +11,10 @@ export class CustomErrorHandler implements ErrorHandler {
private _activeFeatures = environment.activeFeatures;
constructor(private errorService: ErrorService, public apmErrorHandler: ApmErrorHandler) {}
handleError(error: Error & { ngDebugContext: unknown }): void {
const customError: CustomError = errorToCustomError(error);
this.errorService.add(customError);
handleError(customError: CustomError & { ngDebugContext: unknown }): void {
if (!customError.avoidToast) {
this.errorService.add(customError);
}
if (this._elasticConfig && this._activeFeatures.includes(Feature.LOGGING)) {
this.apmErrorHandler.handleError(customError);

View File

@@ -0,0 +1,7 @@
export interface SignalRequest {
genomforandeReferens: number;
typ: 'arbete' | 'utbildning';
omfattning: 'heltid' | 'deltid';
omfattning_procent: number;
startdatum: string;
}

View File

@@ -0,0 +1,6 @@
export interface SignalResponse {
typ: 'arbete' | 'utbildning';
omfattning: 'heltid' | 'deltid';
omfattning_procent: number;
startdatum: string;
}

View File

@@ -11,15 +11,24 @@ export class CustomError implements Error {
timestamp: Date;
error: Error;
removeAfter: number;
avoidToast?: boolean;
constructor(args: { error: Error; type?: ErrorType; message?: string; severity?: ErrorSeverity; stack?: string }) {
constructor(args: {
error: Error;
type?: ErrorType;
message?: string;
severity?: ErrorSeverity;
stack?: string;
avoidToast?: boolean;
}) {
this.timestamp = new Date();
this.id = this.timestamp.getTime().toString();
this.type = this.name = args.type || ErrorType.UNKNOWN;
this.type = this.name = args.type || CustomError.getErrorType(args.error);
this.message = args.message || args.error.message;
this.severity = args.severity || ErrorSeverity.HIGH;
this.stack = args.stack || CustomError.getStack(args.error);
this.error = args.error;
this.avoidToast = args.avoidToast || false;
this.removeAfter =
this.severity === ErrorSeverity.LOW ? 5000 : this.severity === ErrorSeverity.MEDIUM ? 10000 : 20000;
}

View File

@@ -1,26 +0,0 @@
import { ErsattningsGrundTypKod } from '../enums/ersattning-grund-typ-kod.enum';
export interface SignalArbeteStudier {
sokandeId: number;
ersattningsgrund: ErsattningsGrundTypKod;
antalProcent?: number;
startDatum: Date;
}
export interface SignalArbeteStudierRequestData {
sokandeId: number;
ersattningsgrund: ErsattningsGrundTypKod;
antalProcent?: number;
startDatum: Date;
}
export function mapSignalArbeteStudierRequestDataToReport(data: SignalArbeteStudierRequestData): SignalArbeteStudier {
const { sokandeId, ersattningsgrund, antalProcent, startDatum } = data;
return {
sokandeId,
ersattningsgrund,
antalProcent,
startDatum,
};
}

View File

@@ -0,0 +1,19 @@
import { SignalResponse } from './api/signal.response.model';
export interface Signal {
type: 'arbete' | 'utbildning';
omfattning: 'heltid' | 'deltid';
percent: number;
startDate: Date;
}
export function mapResponseToSignal(data: SignalResponse): Signal {
const { typ, startdatum, omfattning, omfattning_procent } = data;
return {
type: typ,
omfattning,
percent: omfattning_procent,
startDate: new Date(startdatum),
};
}

View File

@@ -38,7 +38,7 @@ export class AvvikelseApiService {
catchError((error: Error) => {
throw new CustomError({
error,
message: 'Det gick inte att skicka avvikelse \n\n' + error.message,
message: `Kunde inte spara Avvikelserapport (avvikelse).\n\n${error.message}`,
type: ErrorType.API,
});
})

View File

@@ -1,14 +0,0 @@
import { Injectable } from '@angular/core';
import { SignalArbeteStudier } from '@msfa-models/signal-arbete-studier.model';
import { SignalArbeteStudierApiService } from '@msfa-services/api/signal-arbete-studier-api.service';
@Injectable({
providedIn: 'root',
})
export class DeltagareSignalArbeteStudierService {
constructor(private signalArbeteStudierApiService: SignalArbeteStudierApiService) { }
public createSignalArbeteStudier$(signalArbeteStudier: SignalArbeteStudier): Promise<void> {
return this.signalArbeteStudierApiService.createSignalArbeteStudier$(signalArbeteStudier);
}
}

View File

@@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ErrorType } from '@msfa-enums/error-type.enum';
import { environment } from '@msfa-environment';
import { FranvaroReasonResponse } from '@msfa-models/api/franvaro-reason.response.model';
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
@@ -47,7 +48,15 @@ export class FranvaroReportApiService {
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
}
public async postFranvaroReport$(requestData: FranvaroReportRequest): Promise<void> {
return this.httpClient.post<void>(`${this._apiBaseUrl}/franvaro`, requestData).toPromise();
public postFranvaroReport$(requestData: FranvaroReportRequest): Observable<void> {
return this.httpClient.post<void>(`${this._apiBaseUrl}/franvaro`, requestData).pipe(
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte spara Avvikelserapport (frånvaro).\n\n${error.message}`,
type: ErrorType.API,
});
})
);
}
}

View File

@@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ErrorType } from '@msfa-enums/error-type.enum';
import { environment } from '@msfa-environment';
import { Activity, mapResponseToActivity } from '@msfa-models/activity.model';
import { ActivityResponse } from '@msfa-models/api/activity.response.model';
@@ -53,8 +54,16 @@ export class GemensamPlaneringApiService {
);
}
public async postGemensamPlanering$(requestData: GemensamPlaneringPostRequest): Promise<void> {
return this.httpClient.post<void>(`${this._apiBaseUrl}`, requestData).toPromise();
public postGemensamPlanering$(requestData: GemensamPlaneringPostRequest): Observable<void> {
return this.httpClient.post<void>(`${this._apiBaseUrl}`, requestData).pipe(
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte spara Gemensam planering.\n\n${error.message}`,
type: ErrorType.API,
});
})
);
}
constructor(private httpClient: HttpClient, private deltagareApiService: DeltagareApiService) {}

View File

@@ -1,18 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@msfa-environment';
import { SignalArbeteStudier } from '@msfa-models/signal-arbete-studier.model';
@Injectable({
providedIn: 'root',
})
export class SignalArbeteStudierApiService {
private _apiBaseUrl = `${environment.api.url}`;
public createSignalArbeteStudier$(signalArbeteStudier: SignalArbeteStudier): Promise<void> {
return this.httpClient.post<void>(`${this._apiBaseUrl}/signal`, signalArbeteStudier).toPromise();
}
constructor(private httpClient: HttpClient) { }
}

View File

@@ -0,0 +1,29 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ErrorType } from '@msfa-enums/error-type.enum';
import { environment } from '@msfa-environment';
import { SignalRequest } from '@msfa-models/api/signal.request.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class SignalApiService {
private _apiBaseUrl = `${environment.api.url}/rapporter/signal`;
public postSignal$(requestData: SignalRequest): Observable<void> {
return this.httpClient.post<void>(this._apiBaseUrl, requestData).pipe(
catchError((error: Error) => {
throw new CustomError({
error,
message: `Kunde inte spara Signal om arbete eller studier.\n\n${error.message}`,
type: ErrorType.API,
});
})
);
}
constructor(private httpClient: HttpClient) {}
}