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:
@@ -66,10 +66,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'signal',
|
path: 'signal',
|
||||||
data: { title: 'Skapa signal om arbete eller studier' },
|
data: { title: 'Skapa signal om arbete eller studier' },
|
||||||
loadChildren: () =>
|
loadChildren: () => import('./pages/report-forms/signal-form/signal-form.module').then(m => m.SignalFormModule),
|
||||||
import('./pages/report-forms/deltagare-signal-arbete-studier/deltagare-signal-arbete-studier.module').then(
|
|
||||||
m => m.DeltagareSignalArbeteStudierModule
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -2,29 +2,35 @@
|
|||||||
<h3>Skapa ny rapport</h3>
|
<h3>Skapa ny rapport</h3>
|
||||||
<p>Här kan du skicka rapporter om deltagaren till Arbetsförmedlingen.</p>
|
<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">
|
<ul class="deltagare-tab-reports__button-list">
|
||||||
<li>
|
<li>
|
||||||
<digi-ng-link-button
|
<digi-ng-link-button
|
||||||
class="deltagare-tab-reports__button"
|
class="deltagare-tab-reports__button"
|
||||||
[afRoute]="'./gemensam-planering'"
|
afRoute="./gemensam-planering"
|
||||||
afText="Skapa ny Gemensam planering"
|
afText="Skapa ny Gemensam planering"
|
||||||
></digi-ng-link-button>
|
></digi-ng-link-button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<digi-ng-link-button
|
<digi-ng-link-button
|
||||||
class="deltagare-tab-reports__button"
|
class="deltagare-tab-reports__button"
|
||||||
[afRoute]="'./franvarorapport'"
|
afRoute="./franvarorapport"
|
||||||
afText="Skapa ny Avvikelserapport (frånvaro)"
|
afText="Skapa ny Avvikelserapport (frånvaro)"
|
||||||
></digi-ng-link-button>
|
></digi-ng-link-button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<digi-ng-link-button
|
<digi-ng-link-button
|
||||||
class="deltagare-tab-reports__button"
|
class="deltagare-tab-reports__button"
|
||||||
[afRoute]="'./avvikelserapport'"
|
afRoute="./avvikelserapport"
|
||||||
afText="Skapa ny Avvikelserapport (avvikelse)"
|
afText="Skapa ny Avvikelserapport (avvikelse)"
|
||||||
></digi-ng-link-button>
|
></digi-ng-link-button>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
|
|
||||||
<ng-container *ngIf="reportsData; else loadingRef">
|
<ng-container *ngIf="reportsData; else loadingRef">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
&__button {
|
&__button {
|
||||||
::ng-deep .digi-ng-link-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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,15 @@
|
|||||||
<dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
|
<dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</dl>
|
</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>
|
</digi-ng-dialog>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
|
|||||||
reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
|
reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
|
||||||
|
|
||||||
submitIsLoading$ = new BehaviorSubject<boolean>(false);
|
submitIsLoading$ = new BehaviorSubject<boolean>(false);
|
||||||
|
error$ = new BehaviorSubject<CustomError>(null);
|
||||||
|
|
||||||
genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
|
genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
|
||||||
map((params: Params) => +params.genomforandeReferens)
|
map((params: Params) => +params.genomforandeReferens)
|
||||||
@@ -155,9 +156,10 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
|
|||||||
this.submittedDate$.next(new Date());
|
this.submittedDate$.next(new Date());
|
||||||
this.confirmDialogIsOpen$.next(false);
|
this.confirmDialogIsOpen$.next(false);
|
||||||
},
|
},
|
||||||
error: error => {
|
error: (customError: CustomError) => {
|
||||||
|
this.error$.next({ ...customError, message: customError.error.message });
|
||||||
this.submitIsLoading$.next(false);
|
this.submitIsLoading$.next(false);
|
||||||
throw new CustomError(error);
|
throw { ...customError, avoidToast: true };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -165,6 +167,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
cancelConfirmDialog(): void {
|
cancelConfirmDialog(): void {
|
||||||
this.confirmDialogIsOpen$.next(false);
|
this.confirmDialogIsOpen$.next(false);
|
||||||
|
this.error$.next(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { AvvikelseReportRequest } from '@msfa-models/api/avvikelse-request.model';
|
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 { AvvikelseQuestion } from '@msfa-models/avvikelse-question.model';
|
||||||
import { AvvikelseReason } from '@msfa-models/avvikelse-reason.model';
|
import { AvvikelseReason } from '@msfa-models/avvikelse-reason.model';
|
||||||
import { AvvikelseApiService } from '@msfa-services/api/avvikelse-api.service';
|
import { AvvikelseApiService } from '@msfa-services/api/avvikelse-api.service';
|
||||||
@@ -21,7 +22,7 @@ export class AvvikelseReportFormService {
|
|||||||
return this.avvikelseApiService.createAvvikelse$(avvikelse);
|
return this.avvikelseApiService.createAvvikelse$(avvikelse);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchAvropInformation$(genomforandeReferens: number) {
|
fetchAvropInformation$(genomforandeReferens: number): Observable<Avrop> {
|
||||||
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
|
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</digi-notification-alert>
|
</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>
|
</div>
|
||||||
<ng-template #formRef>
|
<ng-template #formRef>
|
||||||
<form
|
<form
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
[afSelectItems]="reasons"
|
[afSelectItems]="reasons"
|
||||||
[afAnnounceIfOptional]="true"
|
[afAnnounceIfOptional]="true"
|
||||||
[afRequired]="true"
|
[afRequired]="true"
|
||||||
[afAnnounceIfOptional]="true"
|
|
||||||
[afDisableValidStyle]="true"
|
[afDisableValidStyle]="true"
|
||||||
[afInvalid]="formControlIsInvalid(['reason'])"
|
[afInvalid]="formControlIsInvalid(['reason'])"
|
||||||
(afOnChange)="reasonChanged()"
|
(afOnChange)="reasonChanged()"
|
||||||
@@ -250,15 +249,6 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<footer class="franvaro-report-form__footer">
|
<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">
|
<div class="franvaro-report-form__cta-wrapper">
|
||||||
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
|
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
|
||||||
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
|
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
|
||||||
@@ -327,6 +317,15 @@
|
|||||||
<dt>Tid för förväntad närvaro</dt>
|
<dt>Tid för förväntad närvaro</dt>
|
||||||
<dd>{{expectedPresenceStartTimeFormControl.value}} - {{expectedPresenceEndTimeFormControl.value}}</dd>
|
<dd>{{expectedPresenceStartTimeFormControl.value}} - {{expectedPresenceEndTimeFormControl.value}}</dd>
|
||||||
</dl>
|
</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>
|
</digi-ng-dialog>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ANNAN_KAND_ORSAK_ID, ANNAN_ORSAK_ID } from '@msfa-constants/franvaro-reasons';
|
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 { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
|
||||||
import { Avrop } from '@msfa-models/avrop.model';
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
import { CustomError } from '@msfa-models/error/custom-error';
|
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 { Franvaro } from '@msfa-models/franvaro.model';
|
||||||
import { dateToIsoString } from '@msfa-utils/format-to-date.util';
|
import { dateToIsoString } from '@msfa-utils/format-to-date.util';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
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 { FranvaroReportFormService } from './franvaro-report-form.service';
|
||||||
import { FranvaroReportFormValidator } from './franvaro-report-form.validator';
|
import { FranvaroReportFormValidator } from './franvaro-report-form.validator';
|
||||||
|
|
||||||
@@ -143,13 +142,14 @@ export class FranvaroReportFormComponent {
|
|||||||
|
|
||||||
cancelConfirmDialog(): void {
|
cancelConfirmDialog(): void {
|
||||||
this.confirmDialogOpen$.next(false);
|
this.confirmDialogOpen$.next(false);
|
||||||
|
this.error$.next(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
reasonChanged(): void {
|
reasonChanged(): void {
|
||||||
this.otherKnownReasonFormControl.reset();
|
this.otherKnownReasonFormControl.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitAndCloseConfirmDialog(genomforandeReferens: number): Promise<void> {
|
submitAndCloseConfirmDialog(genomforandeReferens: number): void {
|
||||||
this.submitLoading$.next(true);
|
this.submitLoading$.next(true);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -185,17 +185,20 @@ export class FranvaroReportFormComponent {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.franvaroReportFormService
|
this.franvaroReportFormService
|
||||||
.postFranvaroReport(postRequest)
|
.postFranvaroReport$(postRequest)
|
||||||
.then(() => {
|
.pipe(take(1))
|
||||||
this.lastSubmittedFranvaroReport$.next(new Date());
|
.subscribe({
|
||||||
})
|
next: () => {
|
||||||
.catch((error: Error) => {
|
this.lastSubmittedFranvaroReport$.next(new Date());
|
||||||
this.error$.next(new CustomError({ error, message: error.message, type: ErrorType.API }));
|
this.submitLoading$.next(false);
|
||||||
})
|
this.confirmDialogOpen$.next(false);
|
||||||
.finally(() => {
|
},
|
||||||
this.submitLoading$.next(false);
|
error: (customError: CustomError) => {
|
||||||
this.confirmDialogOpen$.next(false);
|
this.error$.next({ ...customError, message: customError.error.message });
|
||||||
|
this.submitLoading$.next(false);
|
||||||
|
throw { ...customError, avoidToast: true };
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
|
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
|
||||||
import { Avrop } from '@msfa-models/avrop.model';
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
import { FranvaroReason } from '@msfa-models/franvaro-reason.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 { FranvaroReportApiService } from '@msfa-services/api/franvaro-report.api.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@@ -10,13 +11,16 @@ export class FranvaroReportFormService {
|
|||||||
public reasons$: Observable<FranvaroReason[]> = this.franvaroReportApiService.fetchReasons$();
|
public reasons$: Observable<FranvaroReason[]> = this.franvaroReportApiService.fetchReasons$();
|
||||||
public otherKnownReasons$: Observable<FranvaroReason[]> = this.franvaroReportApiService.fetchOtherKnownReasons$();
|
public otherKnownReasons$: Observable<FranvaroReason[]> = this.franvaroReportApiService.fetchOtherKnownReasons$();
|
||||||
|
|
||||||
constructor(private franvaroReportApiService: FranvaroReportApiService) {}
|
constructor(
|
||||||
|
private franvaroReportApiService: FranvaroReportApiService,
|
||||||
|
private deltagareApiService: DeltagareApiService
|
||||||
|
) {}
|
||||||
|
|
||||||
public fetchAvropInformation$(genomforandeReferens: number): Observable<Avrop> {
|
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);
|
return this.franvaroReportApiService.postFranvaroReport$(requestData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<dd>{{lastSubmittedGP | date:'longDate'}} kl {{lastSubmittedGP | date:'shortTime'}}</dd>
|
<dd>{{lastSubmittedGP | date:'longDate'}} kl {{lastSubmittedGP | date:'shortTime'}}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</digi-notification-alert>
|
</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>
|
</div>
|
||||||
<ng-template #formRef>
|
<ng-template #formRef>
|
||||||
<form
|
<form
|
||||||
@@ -78,15 +78,6 @@
|
|||||||
</digi-form-fieldset>
|
</digi-form-fieldset>
|
||||||
|
|
||||||
<footer class="gemensam-planering-form__footer">
|
<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">
|
<div class="gemensam-planering-form__cta-wrapper">
|
||||||
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
|
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
|
||||||
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
|
<msfa-back-link [showIcon]="false" [asButton]="true" route="../">
|
||||||
@@ -148,6 +139,15 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</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>
|
</digi-ng-dialog>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { RadiobuttonGroupDirection, RadiobuttonModel } from '@af/digi-ng/_form/f
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { FormArray, FormControl, FormGroup } from '@angular/forms';
|
import { FormArray, FormControl, FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ErrorType } from '@msfa-enums/error-type.enum';
|
|
||||||
import { Activity } from '@msfa-models/activity.model';
|
import { Activity } from '@msfa-models/activity.model';
|
||||||
import { Avrop } from '@msfa-models/avrop.model';
|
import { Avrop } from '@msfa-models/avrop.model';
|
||||||
import { CustomError } from '@msfa-models/error/custom-error';
|
import { CustomError } from '@msfa-models/error/custom-error';
|
||||||
@@ -11,9 +10,9 @@ import {
|
|||||||
mapGemensamPlaneringToGemensamPlaneringPostRequest,
|
mapGemensamPlaneringToGemensamPlaneringPostRequest,
|
||||||
} from '@msfa-models/gemensam-planering.model';
|
} from '@msfa-models/gemensam-planering.model';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
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 { GemensamPlaneringFormService } from './gemensam-planering-form.service';
|
||||||
import { GemensamPlaneringValidator } from './gemensam-planering.validator';
|
import { GemensamPlaneringFormValidator } from './gemensam-planering-form.validator';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-gemensam-planering-form',
|
selector: 'msfa-gemensam-planering-form',
|
||||||
@@ -45,7 +44,7 @@ export class GemensamPlaneringFormComponent {
|
|||||||
distance: new FormControl(false),
|
distance: new FormControl(false),
|
||||||
activityIds: new FormArray(this.obligatoryActivityIds.map(id => new FormControl(id))),
|
activityIds: new FormArray(this.obligatoryActivityIds.map(id => new FormControl(id))),
|
||||||
},
|
},
|
||||||
[GemensamPlaneringValidator.isGemensamPlaneringValid(this.obligatoryActivityIds)]
|
[GemensamPlaneringFormValidator.isGemensamPlaneringValid(this.obligatoryActivityIds)]
|
||||||
);
|
);
|
||||||
|
|
||||||
distanceRadiobuttons: RadiobuttonModel[] = [
|
distanceRadiobuttons: RadiobuttonModel[] = [
|
||||||
@@ -118,9 +117,10 @@ export class GemensamPlaneringFormComponent {
|
|||||||
|
|
||||||
cancelConfirmDialog(): void {
|
cancelConfirmDialog(): void {
|
||||||
this.confirmDialogOpen$.next(false);
|
this.confirmDialogOpen$.next(false);
|
||||||
|
this.error$.next(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitAndCloseConfirmDialog(genomforandeReferens: number): Promise<void> {
|
submitAndCloseConfirmDialog(genomforandeReferens: number): void {
|
||||||
this.submitLoading$.next(true);
|
this.submitLoading$.next(true);
|
||||||
|
|
||||||
const { distance, activityIds } = this.gpFormGroup.value as GemensamPlanering;
|
const { distance, activityIds } = this.gpFormGroup.value as GemensamPlanering;
|
||||||
@@ -131,17 +131,20 @@ export class GemensamPlaneringFormComponent {
|
|||||||
genomforandeReferens,
|
genomforandeReferens,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gemensamPlaneringFormService
|
this.gemensamPlaneringFormService
|
||||||
.postGemensamPlanering(mapGemensamPlaneringToGemensamPlaneringPostRequest(postRequest))
|
.postGemensamPlanering$(mapGemensamPlaneringToGemensamPlaneringPostRequest(postRequest))
|
||||||
.then(() => {
|
.pipe(take(1))
|
||||||
this.lastSubmittedGP$.next(new Date());
|
.subscribe({
|
||||||
})
|
next: () => {
|
||||||
.catch((error: Error) => {
|
this.lastSubmittedGP$.next(new Date());
|
||||||
this.error$.next(new CustomError({ error, message: error.message, type: ErrorType.API }));
|
this.submitLoading$.next(false);
|
||||||
})
|
this.confirmDialogOpen$.next(false);
|
||||||
.finally(() => {
|
},
|
||||||
this.submitLoading$.next(false);
|
error: (customError: CustomError) => {
|
||||||
this.confirmDialogOpen$.next(false);
|
this.error$.next({ ...customError, message: customError.error.message });
|
||||||
|
this.submitLoading$.next(false);
|
||||||
|
throw { ...customError, avoidToast: true };
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class GemensamPlaneringFormService {
|
|||||||
return this.gemensamPlaneringApiService.fetchAvropInformation$(genomforandeReferens);
|
return this.gemensamPlaneringApiService.fetchAvropInformation$(genomforandeReferens);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async postGemensamPlanering(requestData: GemensamPlaneringPostRequest): Promise<void> {
|
public postGemensamPlanering$(requestData: GemensamPlaneringPostRequest): Observable<void> {
|
||||||
return this.gemensamPlaneringApiService.postGemensamPlanering$(requestData);
|
return this.gemensamPlaneringApiService.postGemensamPlanering$(requestData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AbstractControl, ValidatorFn } from '@angular/forms';
|
import { AbstractControl, ValidatorFn } from '@angular/forms';
|
||||||
|
|
||||||
export class GemensamPlaneringValidator {
|
export class GemensamPlaneringFormValidator {
|
||||||
static isGemensamPlaneringValid(obligatoryActivityIds: number[] = []): ValidatorFn {
|
static isGemensamPlaneringValid(obligatoryActivityIds: number[] = []): ValidatorFn {
|
||||||
return (c: AbstractControl): { [key: string]: string } => {
|
return (c: AbstractControl): { [key: string]: string } => {
|
||||||
let errors: { [key: string]: string } = null;
|
let errors: { [key: string]: string } = null;
|
||||||
@@ -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"> 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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
|
||||||
import { DeltagareSignalArbeteStudierComponent } from './deltagare-signal-arbete-studier.component';
|
|
||||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
|
import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
|
||||||
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
|
import { SignalFormComponent } from './signal-form.component';
|
||||||
|
import { SignalFormService } from './signal-form.service';
|
||||||
|
|
||||||
describe('DeltagareSignalArbeteStudierComponent', () => {
|
describe('SignalFormComponent', () => {
|
||||||
let component: DeltagareSignalArbeteStudierComponent;
|
let component: SignalFormComponent;
|
||||||
let fixture: ComponentFixture<DeltagareSignalArbeteStudierComponent>;
|
let fixture: ComponentFixture<SignalFormComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
declarations: [DeltagareSignalArbeteStudierComponent, LayoutComponent],
|
declarations: [SignalFormComponent, LayoutComponent],
|
||||||
imports: [RouterTestingModule, HttpClientTestingModule, ReactiveFormsModule, DigiNgFormRadiobuttonGroupModule],
|
imports: [RouterTestingModule, HttpClientTestingModule, ReactiveFormsModule, DigiNgFormRadiobuttonGroupModule],
|
||||||
|
providers: [SignalFormService],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(DeltagareSignalArbeteStudierComponent);
|
fixture = TestBed.createComponent(SignalFormComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -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 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
|
||||||
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
|
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 { 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 { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
@@ -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 { ConfirmDialogModule } from '@msfa-shared/components/confirm-dialog/confirm-dialog.module';
|
||||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||||
import { ReportLayoutModule } from '../../../components/report-layout/report-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 { SignalFormComponent } from './signal-form.component';
|
||||||
import { DeltagareSignalArbeteStudierComponent } from './deltagare-signal-arbete-studier.component';
|
import { SignalFormService } from './signal-form.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
declarations: [DeltagareSignalArbeteStudierComponent],
|
declarations: [SignalFormComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
RouterModule.forChild([{ path: '', component: DeltagareSignalArbeteStudierComponent }]),
|
RouterModule.forChild([{ path: '', component: SignalFormComponent }]),
|
||||||
LayoutModule,
|
LayoutModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
DigiNgFormRadiobuttonGroupModule,
|
|
||||||
DigiNgFormDatepickerModule,
|
|
||||||
DigiNgFormTextareaModule,
|
|
||||||
DigiNgFormInputModule,
|
|
||||||
DeltagareConfirmSignalFormModule,
|
|
||||||
ReportLayoutModule,
|
ReportLayoutModule,
|
||||||
ConfirmDialogModule,
|
ConfirmDialogModule,
|
||||||
DigiNgSkeletonBaseModule,
|
|
||||||
BackLinkModule,
|
BackLinkModule,
|
||||||
|
DigiNgFormRadiobuttonGroupModule,
|
||||||
|
DigiNgFormDatepickerModule,
|
||||||
|
DigiNgSkeletonBaseModule,
|
||||||
|
DigiNgDialogModule,
|
||||||
|
DigiNgFormRangeModule,
|
||||||
|
DigiNgFormValidationMessageModule,
|
||||||
|
DigiNgFormSelectModule,
|
||||||
],
|
],
|
||||||
exports: [DeltagareSignalArbeteStudierComponent],
|
providers: [SignalFormService],
|
||||||
|
exports: [SignalFormComponent],
|
||||||
})
|
})
|
||||||
export class DeltagareSignalArbeteStudierModule {}
|
export class SignalFormModule {}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ export const DELTAGARE_REPORTING_ROUTES = {
|
|||||||
'periodisk-redovisning': 'Periodisk redovisning',
|
'periodisk-redovisning': 'Periodisk redovisning',
|
||||||
franvarorapport: 'Avvikelserapport (frånvaro)',
|
franvarorapport: 'Avvikelserapport (frånvaro)',
|
||||||
avvikelserapport: 'Avvikelserapport (avvikelse)',
|
avvikelserapport: 'Avvikelserapport (avvikelse)',
|
||||||
|
signal: 'Signal om arbete eller studier',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NAVIGATION = {
|
export const NAVIGATION = {
|
||||||
|
|||||||
@@ -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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import { ErrorHandler, Injectable } from '@angular/core';
|
|||||||
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
|
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
|
||||||
import { Feature } from '@msfa-enums/feature.enum';
|
import { Feature } from '@msfa-enums/feature.enum';
|
||||||
import { environment } from '@msfa-environment';
|
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';
|
import { ErrorService } from '@msfa-services/error.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -11,9 +11,10 @@ export class CustomErrorHandler implements ErrorHandler {
|
|||||||
private _activeFeatures = environment.activeFeatures;
|
private _activeFeatures = environment.activeFeatures;
|
||||||
constructor(private errorService: ErrorService, public apmErrorHandler: ApmErrorHandler) {}
|
constructor(private errorService: ErrorService, public apmErrorHandler: ApmErrorHandler) {}
|
||||||
|
|
||||||
handleError(error: Error & { ngDebugContext: unknown }): void {
|
handleError(customError: CustomError & { ngDebugContext: unknown }): void {
|
||||||
const customError: CustomError = errorToCustomError(error);
|
if (!customError.avoidToast) {
|
||||||
this.errorService.add(customError);
|
this.errorService.add(customError);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._elasticConfig && this._activeFeatures.includes(Feature.LOGGING)) {
|
if (this._elasticConfig && this._activeFeatures.includes(Feature.LOGGING)) {
|
||||||
this.apmErrorHandler.handleError(customError);
|
this.apmErrorHandler.handleError(customError);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface SignalRequest {
|
||||||
|
genomforandeReferens: number;
|
||||||
|
typ: 'arbete' | 'utbildning';
|
||||||
|
omfattning: 'heltid' | 'deltid';
|
||||||
|
omfattning_procent: number;
|
||||||
|
startdatum: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface SignalResponse {
|
||||||
|
typ: 'arbete' | 'utbildning';
|
||||||
|
omfattning: 'heltid' | 'deltid';
|
||||||
|
omfattning_procent: number;
|
||||||
|
startdatum: string;
|
||||||
|
}
|
||||||
@@ -11,15 +11,24 @@ export class CustomError implements Error {
|
|||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
error: Error;
|
error: Error;
|
||||||
removeAfter: number;
|
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.timestamp = new Date();
|
||||||
this.id = this.timestamp.getTime().toString();
|
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.message = args.message || args.error.message;
|
||||||
this.severity = args.severity || ErrorSeverity.HIGH;
|
this.severity = args.severity || ErrorSeverity.HIGH;
|
||||||
this.stack = args.stack || CustomError.getStack(args.error);
|
this.stack = args.stack || CustomError.getStack(args.error);
|
||||||
this.error = args.error;
|
this.error = args.error;
|
||||||
|
this.avoidToast = args.avoidToast || false;
|
||||||
this.removeAfter =
|
this.removeAfter =
|
||||||
this.severity === ErrorSeverity.LOW ? 5000 : this.severity === ErrorSeverity.MEDIUM ? 10000 : 20000;
|
this.severity === ErrorSeverity.LOW ? 5000 : this.severity === ErrorSeverity.MEDIUM ? 10000 : 20000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
19
apps/mina-sidor-fa/src/app/shared/models/signal.model.ts
Normal file
19
apps/mina-sidor-fa/src/app/shared/models/signal.model.ts
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ export class AvvikelseApiService {
|
|||||||
catchError((error: Error) => {
|
catchError((error: Error) => {
|
||||||
throw new CustomError({
|
throw new CustomError({
|
||||||
error,
|
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,
|
type: ErrorType.API,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ErrorType } from '@msfa-enums/error-type.enum';
|
||||||
import { environment } from '@msfa-environment';
|
import { environment } from '@msfa-environment';
|
||||||
import { FranvaroReasonResponse } from '@msfa-models/api/franvaro-reason.response.model';
|
import { FranvaroReasonResponse } from '@msfa-models/api/franvaro-reason.response.model';
|
||||||
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
|
import { FranvaroReportRequest } from '@msfa-models/api/franvaro-request.model';
|
||||||
@@ -47,7 +48,15 @@ export class FranvaroReportApiService {
|
|||||||
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
|
return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async postFranvaroReport$(requestData: FranvaroReportRequest): Promise<void> {
|
public postFranvaroReport$(requestData: FranvaroReportRequest): Observable<void> {
|
||||||
return this.httpClient.post<void>(`${this._apiBaseUrl}/franvaro`, requestData).toPromise();
|
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,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ErrorType } from '@msfa-enums/error-type.enum';
|
||||||
import { environment } from '@msfa-environment';
|
import { environment } from '@msfa-environment';
|
||||||
import { Activity, mapResponseToActivity } from '@msfa-models/activity.model';
|
import { Activity, mapResponseToActivity } from '@msfa-models/activity.model';
|
||||||
import { ActivityResponse } from '@msfa-models/api/activity.response.model';
|
import { ActivityResponse } from '@msfa-models/api/activity.response.model';
|
||||||
@@ -53,8 +54,16 @@ export class GemensamPlaneringApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async postGemensamPlanering$(requestData: GemensamPlaneringPostRequest): Promise<void> {
|
public postGemensamPlanering$(requestData: GemensamPlaneringPostRequest): Observable<void> {
|
||||||
return this.httpClient.post<void>(`${this._apiBaseUrl}`, requestData).toPromise();
|
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) {}
|
constructor(private httpClient: HttpClient, private deltagareApiService: DeltagareApiService) {}
|
||||||
|
|||||||
@@ -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) { }
|
|
||||||
}
|
|
||||||
@@ -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) {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user