Fixed issues with form validation messages and some styling fixes

This commit is contained in:
Erik Tiekstra
2021-10-11 12:07:23 +02:00
parent 8bcd591a08
commit 0e766ccad2
18 changed files with 214 additions and 291 deletions

View File

@@ -30,7 +30,7 @@
afDescription="Fältet accepterar kommaseparerade e-postadresser för att skicka inbjudningar till flera e-postadresser samtidigt." afDescription="Fältet accepterar kommaseparerade e-postadresser för att skicka inbjudningar till flera e-postadresser samtidigt."
[afRequired]="true" [afRequired]="true"
[afDisableValidStyle]="true" [afDisableValidStyle]="true"
[afInvalidMessage]="emailsControl.errors?.message || 'Ogiltig e-postadress'" [afInvalidMessage]="emailsControl.errors?.required || emailsControl.errors?.invalid || 'Ogiltig e-postadress'"
[afInvalid]="emailsControl.invalid && emailsControl.dirty" [afInvalid]="emailsControl.invalid && emailsControl.dirty"
afLabel="E-postadresser" afLabel="E-postadresser"
></digi-ng-form-textarea> ></digi-ng-form-textarea>

View File

@@ -1,9 +1,5 @@
import { FormSelectItem } from '@af/digi-ng/_form/form-select';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { ReportsData } from '@msfa-models/report.model'; import { ReportsData } from '@msfa-models/report.model';
import { RequiredValidator } from '@msfa-utils/validators/required.validator';
@Component({ @Component({
selector: 'msfa-deltagare-tab-reports', selector: 'msfa-deltagare-tab-reports',
@@ -13,53 +9,8 @@ import { RequiredValidator } from '@msfa-utils/validators/required.validator';
}) })
export class DeltagareTabReportsComponent { export class DeltagareTabReportsComponent {
@Input() reportsData: ReportsData; @Input() reportsData: ReportsData;
@Input() genomforandeReferens: number;
@Output() reportsPaginated = new EventEmitter<number>(); @Output() reportsPaginated = new EventEmitter<number>();
readonly reportsFormControlName = 'reports';
reportPickerFormGroup: FormGroup = this.formBuilder.group({
reports: this.formBuilder.control('', [RequiredValidator('Rapporttyp är obligatoriskt')]),
});
selectableReportTypes: Array<FormSelectItem> = [
{ name: 'Avvikelse', value: 'avvikelse' },
{ name: 'Gemensam Planering', value: 'planering' },
// { name: 'Signal om arbete eller studier', value: 'signal' },
];
constructor(private formBuilder: FormBuilder, private router: Router) {}
get reportsFormControl(): AbstractControl | null {
return this.reportPickerFormGroup?.get(this.reportsFormControlName);
}
onFormSubmitted(event: Event, reportType: string): void {
event.preventDefault();
this.reportsFormControl.markAsTouched();
if (this.reportsFormControl.invalid) {
return;
}
switch (reportType) {
case 'planering':
void this.router.navigateByUrl(`/deltagare/${this.genomforandeReferens}/gemensam-planering`);
break;
case 'avvikelse':
void this.router.navigateByUrl(`/deltagare/${this.genomforandeReferens}/avvikelserapport`);
break;
case 'signal':
void this.router.navigate([`/deltagare/${this.genomforandeReferens}/signal`]);
break;
default:
return;
}
if (!this.selectableReportTypes || this.reportPickerFormGroup.invalid) {
return;
}
}
emitNewPage(page: number): void { emitNewPage(page: number): void {
this.reportsPaginated.emit(page); this.reportsPaginated.emit(page);
} }

View File

@@ -34,7 +34,6 @@
<ng-container *ngIf="activeTab === '1'"> <ng-container *ngIf="activeTab === '1'">
<msfa-deltagare-tab-reports <msfa-deltagare-tab-reports
[reportsData]="reportsData$ | async" [reportsData]="reportsData$ | async"
[genomforandeReferens]="currentGenomforandeReferens$ | async"
(reportsPaginated)="setNewPage($event)" (reportsPaginated)="setNewPage($event)"
></msfa-deltagare-tab-reports> ></msfa-deltagare-tab-reports>
</ng-container> </ng-container>

View File

@@ -1,142 +1,147 @@
<msfa-layout> <msfa-layout>
<msfa-report-layout <msfa-report-layout
*ngIf="avrop$ | async as avrop; else skeletonRef"
[avrop]="avrop$ | async" [avrop]="avrop$ | async"
description="Här rapporterar du deltagarens avvikelser. Exempelvis kan du rapportera om tjänsten inte fungerar för deltagaren eller om deltagaren misskött sig under tjänsten." description="Här rapporterar du deltagarens avvikelser. Exempelvis kan du rapportera om tjänsten inte fungerar för deltagaren eller om deltagaren misskött sig under tjänsten."
reportSubTitle="Skapa rapport"
reportTitle="Avvikelserapport (avvikelse)" reportTitle="Avvikelserapport (avvikelse)"
*ngIf="avrop$ | async as avrop; else skeletonRef"
> >
<div *ngIf="submittedDate$ | async as submittedDate; else formRef" class="deltagare-avvikelse__confirmation"> <div class="deltagare-avvikelse">
<digi-notification-alert <div *ngIf="submittedDate$ | async as submittedDate; else formRef" class="deltagare-avvikelse__confirmation">
af-heading="Allt gick bra" <digi-notification-alert
af-heading-level="h3" af-heading="Allt gick bra"
af-variation="success" af-heading-level="h3"
class="deltagare-avvikelse__alert" af-variation="success"
> class="deltagare-avvikelse__alert"
<p> >
Avvikelserapport för deltagare {{avrop.fullName}} är nu inskickad till Arbetsförmedlingen och inväntar <p>
godkännande. Avvikelserapport för deltagare {{avrop.fullName}} är nu inskickad till Arbetsförmedlingen och inväntar
</p> godkännande.
<dl> </p>
<dt>Datum</dt> <dl>
<dd>{{submittedDate | date:'longDate'}} kl {{submittedDate | date:'shortTime'}}</dd> <dt>Datum</dt>
</dl> <dd>{{submittedDate | date:'longDate'}} kl {{submittedDate | date:'shortTime'}}</dd>
</digi-notification-alert> </dl>
<msfa-back-link route="../">Tillbaka till deltagaren</msfa-back-link> </digi-notification-alert>
</div> <msfa-back-link route="../">Tillbaka till deltagaren</msfa-back-link>
</div>
<ng-template #formRef> <ng-template #formRef>
<form [formGroup]="avvikelseFormGroup" (ngSubmit)="openConfirmDialog()"> <form class="deltagare-avvikelse__form" [formGroup]="avvikelseFormGroup" (ngSubmit)="openConfirmDialog()">
<div class="orsaks-form__content"> <div class="deltagare-avvikelse__form-item">
<digi-ng-form-select <digi-ng-form-select
*ngIf="reasonsAsNgDigiFormSelectItems$ | async; let reason; else loadingRef" *ngIf="reasonsAsNgDigiFormSelectItems$ | async; let reason; else loadingRef"
[formControlName]="reasonFormName" [formControlName]="reasonFormName"
[afLabel]=" 'Orsak till avvikelse'" afLabel="Orsak till avvikelse"
[afPlaceholder]="'Välj orsak till avvikelse'" afPlaceholder="Välj orsak till avvikelse"
[afSelectItems]="reason" [afSelectItems]="reason"
[afDisableValidStyle]="true" [afRequired]="true"
[afInvalid]="formControlIsInvalid(reasonFormControl)" [afAnnounceIfOptional]="true"
></digi-ng-form-select> [afDisableValidStyle]="true"
<digi-form-validation-message *ngIf="formControlIsInvalid(reasonFormControl)" af-variation="error" [afInvalid]="formControlIsInvalid(reasonFormControl)"
>{{reasonFormControl.errors?.message}} ></digi-ng-form-select>
</digi-form-validation-message> <div aria-atomic="true" role="alert">
</div> <digi-form-validation-message *ngIf="formControlIsInvalid(reasonFormControl)" af-variation="error"
>{{reasonFormControl.errors?.required}}
<div [formArrayName]="questionsFormName"> </digi-form-validation-message>
<ng-container *ngIf="questionsForChosenReason$ | async; let questions"> </div>
<div *ngFor="let question of questionsFormArray.controls; let i=index"> </div>
<div class="fragor-form__content">
<digi-ng-form-textarea
[formControlName]="i"
[afLabel]="questions[i]?.name"
[afDisableValidStyle]="true"
[afRequired]="questionIsRequired(questions[i])"
[afMaxLength]="2000"
[afInvalid]="formControlIsInvalid(question)"
></digi-ng-form-textarea>
<div
class="deltagare-avvikelse__form-item deltagare-avvikelse__textareas"
[formArrayName]="questionsFormName"
*ngIf="questionsForChosenReason$ | async; let questions"
>
<div
class="deltagare-avvikelse__form-item"
*ngFor="let question of questionsFormArray.controls; let i=index"
>
<digi-ng-form-textarea
[formControlName]="i"
[afLabel]="questions[i]?.name"
[afDisableValidStyle]="true"
[afRequired]="questionIsRequired(questions[i])"
[afAnnounceIfOptional]="true"
[afMaxLength]="2000"
[afInvalid]="formControlIsInvalid(question)"
></digi-ng-form-textarea>
<div aria-atomic="true" role="alert">
<digi-form-validation-message *ngIf="formControlIsInvalid(question)" af-variation="error" <digi-form-validation-message *ngIf="formControlIsInvalid(question)" af-variation="error"
>{{question.errors?.message}} >{{question.errors?.required}}
</digi-form-validation-message> </digi-form-validation-message>
</div> </div>
</div> </div>
</ng-container> </div>
</div>
<ng-container *ngIf="chosenReasonId$ | async"> <div class="deltagare-avvikelse__form-item" *ngIf="chosenReasonId$ | async">
<digi-ng-form-datepicker <digi-ng-form-datepicker
[afDisableValidStyle]="true" [afDisableValidStyle]="true"
[afMinDate]="minDate(avrop)" [afMinDate]="minDate(avrop)"
[afMaxDate]="maxDate" [afMaxDate]="maxDate"
[afInvalid]="formControlIsInvalid(avvikelseDateFormControl)" [afInvalid]="formControlIsInvalid(avvikelseDateFormControl)"
[afLabel]="'Välj dag r avvikelse'" [afValidationMessages]="{required: 'Datum är obligatoriskt'}"
[formControlName]="reportingDateFormName" [afAnnounceIfOptional]="true"
></digi-ng-form-datepicker> [afRequired]="true"
[afLabel]="'Välj dag för avvikelse'"
[formControlName]="reportingDateFormName"
></digi-ng-form-datepicker>
</div>
<!-- NOTE: Other errors (such as formatting) are captured and displayed within digi-ng-form-datepicker --> <div class="deltagare-avvikelse__cta-wrapper">
<digi-form-validation-message <digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button>
*ngIf="avvikelseDateFormControl?.errors?.type === 'required'" <msfa-back-link [showIcon]="false" [asButton]="true" route="../">
af-variation="error" <span>Avbryt</span>
>{{avvikelseDateFormControl.errors?.message}} <span class="msfa__a11y-sr-only">&nbsp;och gå tillbaka till deltagaren</span>
</digi-form-validation-message> </msfa-back-link>
</ng-container> </div>
</form>
<div class="deltagare-avvikelse__cta-wrapper"> <digi-ng-dialog
<digi-button af-type="submit" af-size="m">Förhandsgranska</digi-button> [afActive]="confirmDialogIsOpen$ | async"
<msfa-back-link [showIcon]="false" [asButton]="true" route="../"> (afOnPrimaryClick)="submitAndCloseConfirmDialog()"
<span>Avbryt</span> (afOnInactive)="cancelConfirmDialog()"
<span class="msfa__a11y-sr-only">&nbsp;och gå tillbaka till deltagaren</span> afHeadingLevel="h2"
</msfa-back-link> afPrimaryButtonText="Skicka in"
</div> afSecondaryButtonText="Avbryt"
</form> (afOnSecondaryClick)="cancelConfirmDialog()"
</ng-template> afHeading="Vill du skicka in Avvikelserapport (avvikelse)"
<digi-ng-dialog afAriaLabel="Förhandsgranska och skicka in Avvikelserapport (avvikelse)"
[afActive]="confirmDialogIsOpen$ | async" id="confirmAvvikelserapport"
(afOnPrimaryClick)="submitAndCloseConfirmDialog()" >
(afOnInactive)="cancelConfirmDialog()" <msfa-loader *ngIf="submitIsLoading$ | async" type="absolute"></msfa-loader>
afHeadingLevel="h2" <dl>
afPrimaryButtonText="Skicka in" <dt>Namn</dt>
afSecondaryButtonText="Avbryt" <dd>{{avrop.fullName}}</dd>
(afOnSecondaryClick)="cancelConfirmDialog()" <dt>Personnummer</dt>
afHeading="Vill du skicka in Avvikelserapport (avvikelse)" <dd>
afAriaLabel="Förhandsgranska och skicka in Avvikelserapport (avvikelse)" <msfa-hide-text
id="confirmAvvikelserapport" symbols="********-****"
> [changingText]="avrop.ssn"
<dl> ariaLabelType="Personnummer"
<dt>Namn</dt> ></msfa-hide-text>
<dd>{{avrop.fullName}}</dd> </dd>
<dt>Personnummer</dt> <dt>Tjänst</dt>
<dd> <dd>{{avrop.tjanst}}</dd>
<msfa-hide-text <dt>Startdatum</dt>
symbols="********-****" <dd>
[changingText]="avrop.ssn" <digi-typography-time [afDateTime]="avrop.startDate"></digi-typography-time>
ariaLabelType="Personnummer" </dd>
></msfa-hide-text> <dt>Slutdatum</dt>
</dd> <dd>
<dt>Tjänst</dt> <digi-typography-time [afDateTime]="avrop.endDate"></digi-typography-time>
<dd>{{avrop.tjanst}}</dd> </dd>
<dt>Startdatum</dt> <dt>Orsak till avvikelse:</dt>
<dd> <dd>{{(chosenReason$ | async)?.name }}</dd>
<digi-typography-time [afDateTime]="avrop.startDate"></digi-typography-time> <ng-container *ngIf="avvikelseSubmitData$ | async; let avvikelseSubmitData; else loadingRef">
</dd> <ng-container *ngFor="let question of avvikelseSubmitData.avvikelseAlternativ.frageformular">
<dt>Slutdatum</dt> <dt>{{getCurrentQuestionFromId(question.fraga).name}}</dt>
<dd> <dd>{{question.svar.length === 0 ? 'Inget svar' : question.svar }}</dd>
<digi-typography-time [afDateTime]="avrop.endDate"></digi-typography-time> </ng-container>
</dd> <dt>Dag för avvikelse:</dt>
<dt>Orsak till avvikelse:</dt> <dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
<dd>{{(chosenReason$ | async)?.name }}</dd> </ng-container>
<ng-container *ngIf="avvikelseSubmitData$ | async; let avvikelseSubmitData; else loadingRef"> </dl>
<ng-container *ngFor="let question of avvikelseSubmitData.avvikelseAlternativ.frageformular"> </digi-ng-dialog>
<dt>{{getCurrentQuestionFromId(question.fraga).name}}</dt> </ng-template>
<dd>{{question.svar.length === 0 ? 'Inget svar' : question.svar }}</dd> </div>
</ng-container>
<dt>Dag för avvikelse:</dt>
<dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
</ng-container>
</dl>
<msfa-loader *ngIf="submitIsLoading$ | async"></msfa-loader>
</digi-ng-dialog>
</msfa-report-layout> </msfa-report-layout>
</msfa-layout> </msfa-layout>
<ng-template #skeletonRef> <ng-template #skeletonRef>

View File

@@ -1,61 +1,18 @@
@import 'variables/gutters'; @import 'variables/gutters';
.deltagare-avvikelse { .deltagare-avvikelse {
max-width: var(--digi--typography--text--max-width);
&__confirmation, &__confirmation,
&__textareas,
&__form { &__form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $digi--layout--gutter--l; gap: $digi--layout--gutter--l;
} }
&__deltagare,
&__alternative,
&__description,
&__datepicker,
&__dayOrPartOfDay {
max-width: var(--digi--typography--text--max-width);
margin-bottom: $digi--layout--gutter--xl;
span {
font-weight: var(--digi--typography--font-weight--semibold);
}
p {
margin: var(--digi--layout--gutter--s) 0 0;
}
}
&__alternative-heading {
font-size: var(--digi--typography--font-size--h3);
}
&__step-buttons-wrapper--space-right {
margin-right: var(--digi--layout--gutter--s);
}
&__alert {
max-width: var(--digi--typography--text--max-width);
}
&__cta-wrapper { &__cta-wrapper {
display: flex; display: flex;
gap: var(--digi--layout--gutter); gap: var(--digi--layout--gutter);
} }
}
.fragor-form {
&__content {
max-width: var(--digi--typography--text--max-width);
margin-bottom: $digi--layout--gutter--xl;
}
}
.orsaks-form {
&__content {
max-width: var(--digi--typography--text--max-width);
margin-bottom: $digi--layout--gutter--xl;
}
} }

View File

@@ -39,7 +39,6 @@
(ngSubmit)="openConfirmDialog()" (ngSubmit)="openConfirmDialog()"
id="gemensam-planering-form" id="gemensam-planering-form"
> >
<msfa-loader *ngIf="submitLoading$ | async" type="absolute"></msfa-loader>
<digi-form-fieldset <digi-form-fieldset
af-legend="Deltar arbetssökande på distans?" af-legend="Deltar arbetssökande på distans?"
af-name="distance" af-name="distance"
@@ -70,9 +69,7 @@
></digi-form-checkbox> ></digi-form-checkbox>
</li> </li>
</ul> </ul>
<digi-form-validation-message <digi-form-validation-message *ngIf="formControlIsInvalid('activityIds')" af-variation="error"
*ngIf="shouldValidate && gpFormGroup.errors?.activityIds"
af-variation="error"
>{{gpFormGroup.errors.activityIds}}</digi-form-validation-message >{{gpFormGroup.errors.activityIds}}</digi-form-validation-message
> >
</ng-container> </ng-container>
@@ -97,13 +94,19 @@
</div> </div>
</footer> </footer>
</form> </form>
<msfa-confirm-dialog <digi-ng-dialog
[dialogOpen]="confirmDialogOpen" [afActive]="confirmDialogOpen$ | async"
dialogTitle="Vill du skicka in Gemensam planering?" (afOnPrimaryClick)="submitAndCloseConfirmDialog(genomforandeReferens)"
ariaLabel="Förhandsgranska och skicka in Gemensam planering" (afOnInactive)="cancelConfirmDialog()"
primaryButtonText="Skicka in" afHeadingLevel="h2"
(confirmDialogChanged)="closeConfirmDialogAndProceed($event, genomforandeReferens)" afPrimaryButtonText="Skicka in"
afSecondaryButtonText="Avbryt"
(afOnSecondaryClick)="cancelConfirmDialog()"
afHeading="Vill du skicka in Gemensam planering"
afAriaLabel="Förhandsgranska och skicka in Gemensam planering"
id="confirm-gemensam-planering"
> >
<msfa-loader *ngIf="submitLoading$ | async" type="absolute"></msfa-loader>
<dl> <dl>
<dt>Namn</dt> <dt>Namn</dt>
<dd>{{avrop.fullName}}</dd> <dd>{{avrop.fullName}}</dd>
@@ -140,7 +143,7 @@
</ul> </ul>
</dd> </dd>
</dl> </dl>
</msfa-confirm-dialog> </digi-ng-dialog>
</ng-template> </ng-template>
</ng-template> </ng-template>
</div> </div>

View File

@@ -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 { ConfirmDialog } from '@msfa-enums/confirm-dialog.enum';
import { ErrorType } from '@msfa-enums/error-type.enum'; 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';
@@ -24,16 +23,13 @@ import { GemensamPlaneringValidator } from './gemensam-planering.validator';
}) })
export class DeltagareGemensamPlaneringComponent { export class DeltagareGemensamPlaneringComponent {
obligatoryActivityIds = [165, 188]; obligatoryActivityIds = [165, 188];
shouldValidate = false; shouldValidate$ = new BehaviorSubject<boolean>(false);
RadiobuttonGroupDirection = RadiobuttonGroupDirection; RadiobuttonGroupDirection = RadiobuttonGroupDirection;
confirmDialogOpen = false; confirmDialogOpen$ = new BehaviorSubject<boolean>(false);
today = new Date(); today = new Date();
private _error$ = new BehaviorSubject<CustomError>(null); error$ = new BehaviorSubject<CustomError>(null);
error$: Observable<CustomError> = this._error$.asObservable(); lastSubmittedGP$ = new BehaviorSubject<Date>(null);
private _lastSubmittedGP$ = new BehaviorSubject<Date>(null); submitLoading$ = new BehaviorSubject<boolean>(false);
lastSubmittedGP$: Observable<Date> = this._lastSubmittedGP$.asObservable();
private _submitLoading$ = new BehaviorSubject<boolean>(false);
submitLoading$: Observable<boolean> = this._submitLoading$.asObservable();
activities$: Observable<Activity[]> = this.gemensamPlaneringService.activities$; activities$: Observable<Activity[]> = this.gemensamPlaneringService.activities$;
currentGenomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe( currentGenomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
@@ -82,7 +78,7 @@ export class DeltagareGemensamPlaneringComponent {
} }
showActivityAsInvalid(id: number): boolean { showActivityAsInvalid(id: number): boolean {
if (this.shouldValidate) { if (this.shouldValidate$.getValue()) {
if (this.isActivityObligatory(id) && !this.isActivityChecked(id)) { if (this.isActivityObligatory(id) && !this.isActivityChecked(id)) {
return true; return true;
} }
@@ -94,6 +90,10 @@ export class DeltagareGemensamPlaneringComponent {
return false; return false;
} }
formControlIsInvalid(formControlName: string): boolean {
return this.gpFormGroup.errors && this.gpFormGroup.errors[formControlName] && this.shouldValidate$.getValue();
}
constructor(private gemensamPlaneringService: GemensamPlaneringService, private activatedRoute: ActivatedRoute) {} constructor(private gemensamPlaneringService: GemensamPlaneringService, private activatedRoute: ActivatedRoute) {}
updateActivityIds(activityId: string, checked: boolean): void { updateActivityIds(activityId: string, checked: boolean): void {
@@ -109,36 +109,40 @@ export class DeltagareGemensamPlaneringComponent {
} }
openConfirmDialog(): void { openConfirmDialog(): void {
this.shouldValidate = true; this.shouldValidate$.next(true);
if (this.gpFormGroup.invalid) { if (this.gpFormGroup.invalid) {
return; return;
} }
this.confirmDialogOpen = true; this.confirmDialogOpen$.next(true);
} }
closeConfirmDialogAndProceed(confirmDialogAnswer: ConfirmDialog, genomforandeReferens: number): void { cancelConfirmDialog(): void {
this.confirmDialogOpen = false; this.confirmDialogOpen$.next(false);
if (confirmDialogAnswer === ConfirmDialog.ACCEPTED) {
const distance = this.gpFormGroup.get('distance').value as boolean;
const activityIds = this.gpFormGroup.get('activityIds').value as number[];
void this.postGemensamPlanering({ distance, activityIds, genomforandeReferens });
}
} }
async postGemensamPlanering(postRequest: GemensamPlanering): Promise<void> { async submitAndCloseConfirmDialog(genomforandeReferens: number): Promise<void> {
this._submitLoading$.next(true); this.submitLoading$.next(true);
const { distance, activityIds } = this.gpFormGroup.value as GemensamPlanering;
const postRequest = {
distance,
activityIds,
genomforandeReferens,
};
return this.gemensamPlaneringService return this.gemensamPlaneringService
.postGemensamPlanering(mapGemensamPlaneringToGemensamPlaneringPostRequest(postRequest)) .postGemensamPlanering(mapGemensamPlaneringToGemensamPlaneringPostRequest(postRequest))
.then(() => { .then(() => {
this._lastSubmittedGP$.next(new Date()); this.lastSubmittedGP$.next(new Date());
}) })
.catch((error: Error) => { .catch((error: Error) => {
this._error$.next(new CustomError({ error, message: error.message, type: ErrorType.API })); this.error$.next(new CustomError({ error, message: error.message, type: ErrorType.API }));
}) })
.finally(() => { .finally(() => {
this._submitLoading$.next(false); this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
}); });
} }
} }

View File

@@ -1,3 +1,4 @@
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox'; import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group'; import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar'; import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar';
@@ -7,7 +8,6 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module'; import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
import { ConfirmDialogModule } from '@msfa-shared/components/confirm-dialog/confirm-dialog.module';
import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module'; import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
import { LayoutModule } from '@msfa-shared/components/layout/layout.module'; import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
import { LoaderModule } from '@msfa-shared/components/loader/loader.module'; import { LoaderModule } from '@msfa-shared/components/loader/loader.module';
@@ -25,12 +25,12 @@ import { DeltagareGemensamPlaneringComponent } from './deltagare-gemensam-planer
DigiNgFormRadiobuttonGroupModule, DigiNgFormRadiobuttonGroupModule,
ReactiveFormsModule, ReactiveFormsModule,
ReportLayoutModule, ReportLayoutModule,
ConfirmDialogModule,
BackLinkModule, BackLinkModule,
LoaderModule, LoaderModule,
HideTextModule, HideTextModule,
DigiNgSkeletonBaseModule, DigiNgSkeletonBaseModule,
DigiNgFormCheckboxModule, DigiNgFormCheckboxModule,
DigiNgDialogModule,
], ],
exports: [DeltagareGemensamPlaneringComponent], exports: [DeltagareGemensamPlaneringComponent],
}) })

View File

@@ -39,8 +39,8 @@ export class DeltagarePeriodiskRedovisningComponent {
initializePeriodiskRedovisningFormGroup(activitiesList: Activity[]): void { initializePeriodiskRedovisningFormGroup(activitiesList: Activity[]): void {
this.periodiskRedovisningFormGroup = new FormGroup({ this.periodiskRedovisningFormGroup = new FormGroup({
lamnatJobbForslag: new FormControl(null, [RequiredValidator('lamnatJobbForslag är obligatoriskt')]), lamnatJobbForslag: new FormControl(null, [RequiredValidator()]),
providedSprakStod: new FormControl(null, [RequiredValidator('providedSprakStod är obligatoriskt')]), providedSprakStod: new FormControl(null, [RequiredValidator()]),
activities: new FormArray([]), activities: new FormArray([]),
}); });
this.getActivitesFormArray(activitiesList); this.getActivitesFormArray(activitiesList);

View File

@@ -37,7 +37,6 @@
(ngSubmit)="openConfirmDialog()" (ngSubmit)="openConfirmDialog()"
id="franvaro-report-form" id="franvaro-report-form"
> >
<msfa-loader *ngIf="submitLoading$ | async" type="absolute"></msfa-loader>
<div class="franvaro-report__form-item"> <div class="franvaro-report__form-item">
<digi-ng-form-select <digi-ng-form-select
*ngIf="reasons$ | async as reasons; else loadingRef" *ngIf="reasons$ | async as reasons; else loadingRef"
@@ -130,7 +129,7 @@
</div> </div>
</div> </div>
<div> <div class="franvaro-report__form-item">
<h2>Tiden deltagaren var frånvarande</h2> <h2>Tiden deltagaren var frånvarande</h2>
<digi-form-fieldset <digi-form-fieldset
af-legend="Heldag eller del av dag" af-legend="Heldag eller del av dag"
@@ -268,13 +267,19 @@
</div> </div>
</footer> </footer>
</form> </form>
<msfa-confirm-dialog <digi-ng-dialog
[dialogOpen]="confirmDialogOpen$ | async" [afActive]="confirmDialogOpen$ | async"
dialogTitle="Vill du skicka in Avvikelserapport (frånvaro)?" (afOnPrimaryClick)="submitAndCloseConfirmDialog(genomforandeReferens)"
ariaLabel="Förhandsgranska och skicka in Avvikelserapport (frånvaro)" (afOnInactive)="cancelConfirmDialog()"
primaryButtonText="Skicka in" afHeadingLevel="h2"
(confirmDialogChanged)="closeConfirmDialogAndProceed($event, genomforandeReferens)" afPrimaryButtonText="Skicka in"
afSecondaryButtonText="Avbryt"
(afOnSecondaryClick)="cancelConfirmDialog()"
afHeading="Vill du skicka in Avvikelserapport (frånvaro)"
afAriaLabel="Förhandsgranska och skicka in Avvikelserapport (frånvaro)"
id="confirm-franvaro-report"
> >
<msfa-loader *ngIf="submitLoading$ | async" type="absolute"></msfa-loader>
<dl *ngIf="reasons$ | async as reasons"> <dl *ngIf="reasons$ | async as reasons">
<dt>Namn</dt> <dt>Namn</dt>
<dd>{{avrop.fullName}}</dd> <dd>{{avrop.fullName}}</dd>
@@ -321,7 +326,7 @@
<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>
</msfa-confirm-dialog> </digi-ng-dialog>
</ng-template> </ng-template>
</ng-template> </ng-template>
</div> </div>

View File

@@ -14,6 +14,10 @@
z-index: $msfa__z-index-default; z-index: $msfa__z-index-default;
} }
&__dialog-contents {
position: relative;
}
&__time-pickers { &__time-pickers {
display: flex; display: flex;
gap: var(--digi--layout--gutter); gap: var(--digi--layout--gutter);

View File

@@ -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 { ConfirmDialog } from '@msfa-enums/confirm-dialog.enum';
import { ErrorType } from '@msfa-enums/error-type.enum'; import { ErrorType } from '@msfa-enums/error-type.enum';
import { Avrop } from '@msfa-models/avrop.model'; import { Avrop } from '@msfa-models/avrop.model';
import { FranvaroRequestData } from '@msfa-models/avvikelse.model'; import { FranvaroRequestData } from '@msfa-models/avvikelse.model';
@@ -136,15 +135,11 @@ export class FranvaroReportComponent {
this.confirmDialogOpen$.next(true); this.confirmDialogOpen$.next(true);
} }
closeConfirmDialogAndProceed(confirmDialogAnswer: ConfirmDialog, genomforandeReferens: number): void { cancelConfirmDialog(): void {
this.confirmDialogOpen$.next(false); this.confirmDialogOpen$.next(false);
if (confirmDialogAnswer === ConfirmDialog.ACCEPTED) {
void this.postFranvaroReport(genomforandeReferens);
}
} }
async postFranvaroReport(genomforandeReferens: number): Promise<void> { async submitAndCloseConfirmDialog(genomforandeReferens: number): Promise<void> {
this.submitLoading$.next(true); this.submitLoading$.next(true);
const { const {
@@ -190,6 +185,7 @@ export class FranvaroReportComponent {
}) })
.finally(() => { .finally(() => {
this.submitLoading$.next(false); this.submitLoading$.next(false);
this.confirmDialogOpen$.next(false);
}); });
} }
} }

View File

@@ -1,3 +1,4 @@
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 { 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';
@@ -10,7 +11,6 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module'; import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
import { ConfirmDialogModule } from '@msfa-shared/components/confirm-dialog/confirm-dialog.module';
import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module'; import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
import { LayoutModule } from '@msfa-shared/components/layout/layout.module'; import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
import { LoaderModule } from '@msfa-shared/components/loader/loader.module'; import { LoaderModule } from '@msfa-shared/components/loader/loader.module';
@@ -29,7 +29,6 @@ import { FranvaroReportService } from './franvaro-report.service';
ReportLayoutModule, ReportLayoutModule,
LoaderModule, LoaderModule,
BackLinkModule, BackLinkModule,
ConfirmDialogModule,
HideTextModule, HideTextModule,
DigiNgFormSelectModule, DigiNgFormSelectModule,
DigiNgFormDatepickerModule, DigiNgFormDatepickerModule,
@@ -38,6 +37,7 @@ import { FranvaroReportService } from './franvaro-report.service';
DigiNgFormTextareaModule, DigiNgFormTextareaModule,
DigiNgFormInputModule, DigiNgFormInputModule,
DigiNgFormValidationMessageModule, DigiNgFormValidationMessageModule,
DigiNgDialogModule,
], ],
providers: [FranvaroReportService], providers: [FranvaroReportService],
exports: [FranvaroReportComponent], exports: [FranvaroReportComponent],

View File

@@ -1,4 +1,3 @@
export interface ValidationError { export interface ValidationError {
type: string; [key: string]: string;
message: string;
} }

View File

@@ -12,7 +12,7 @@ export function isoDateWithoutTimeValidator(): ValidatorFn {
const value: string = control.value as string; const value: string = control.value as string;
if (!isoDateIsValid(value)) { if (!isoDateIsValid(value)) {
return { type: 'invalid', message: `Ogiltigt datum, vänligen ange YYYY-MM-DD` }; return { invalid: `Ogiltigt datum, vänligen ange YYYY-MM-DD` };
} }
} }

View File

@@ -12,7 +12,7 @@ export function EmailValidator(label = 'Fältet'): ValidatorFn {
const value: string = control.value as string; const value: string = control.value as string;
if (!emailIsValid(value)) { if (!emailIsValid(value)) {
return { type: 'invalid', message: `Ogiltig ${label}` }; return { invalid: `Ogiltig ${label}` };
} }
} }
@@ -38,7 +38,7 @@ export function CommaSeparatedEmailValidator(): ValidatorFn {
if (invalidEmailaddresses.length) { if (invalidEmailaddresses.length) {
const messagePrepend = const messagePrepend =
invalidEmailaddresses.length > 1 ? 'Ogiltiga e-postadresser: ' : 'Ogiltig e-postadress: '; invalidEmailaddresses.length > 1 ? 'Ogiltiga e-postadresser: ' : 'Ogiltig e-postadress: ';
return { type: 'invalid', message: `${messagePrepend}${invalidEmailaddresses.join(', ')}` }; return { invalid: `${messagePrepend}${invalidEmailaddresses.join(', ')}` };
} }
} }

View File

@@ -5,7 +5,7 @@ export function RequiredValidator(message = 'Fältet är obligatoriskt'): Valida
return (control: AbstractControl): ValidationError => { return (control: AbstractControl): ValidationError => {
if (control) { if (control) {
if (!control.value || (Array.isArray(control.value) && !control.value.length)) { if (!control.value || (Array.isArray(control.value) && !control.value.length)) {
return { type: 'required', message }; return { required: message };
} }
} }

View File

@@ -10,7 +10,7 @@ export function SocialSecurityNumberValidator(): ValidatorFn {
const ssn = control.value as string; const ssn = control.value as string;
if (/[^0-9-]/g.test(ssn)) { if (/[^0-9-]/g.test(ssn)) {
return { type: 'ssnInvalid', message: 'Inkorrekt personnummer' }; return { ssnInvalid: 'Inkorrekt personnummer' };
} }
let strippedSsn = ssn.replace(/[^0-9]/g, ''); let strippedSsn = ssn.replace(/[^0-9]/g, '');
@@ -22,12 +22,12 @@ export function SocialSecurityNumberValidator(): ValidatorFn {
// Check length // Check length
if (strippedSsn.length !== 10) { if (strippedSsn.length !== 10) {
return { type: 'ssnNotComplete', message: 'Personnummret är inte fullständigt' }; return { ssnNotComplete: 'Personnummret är inte fullständigt' };
} }
// Check month // Check month
if (+strippedSsn.substr(2, 2) > 12 || strippedSsn.substr(2, 2) === '00') { if (+strippedSsn.substr(2, 2) > 12 || strippedSsn.substr(2, 2) === '00') {
return { type: 'ssnInvalid', message: 'Inkorrekt personnummer' }; return { ssnInvalid: 'Inkorrekt personnummer' };
} }
// Check date (valid date + 60 is also apporved because of co-ordination number) // Check date (valid date + 60 is also apporved because of co-ordination number)
@@ -35,10 +35,10 @@ export function SocialSecurityNumberValidator(): ValidatorFn {
(+strippedSsn.substr(4, 2) > 31 || strippedSsn.substr(4, 2) === '00') && (+strippedSsn.substr(4, 2) > 31 || strippedSsn.substr(4, 2) === '00') &&
(+strippedSsn.substr(4, 2) > 91 || +strippedSsn.substr(4, 2) <= 60) (+strippedSsn.substr(4, 2) > 91 || +strippedSsn.substr(4, 2) <= 60)
) { ) {
return { type: 'ssnInvalid', message: 'Inkorrekt personnummer' }; return { ssnInvalid: 'Inkorrekt personnummer' };
} }
return isControlDigitLegit(strippedSsn) ? null : { type: 'ssnInvalid', message: 'Inkorrekt personnummer' }; return isControlDigitLegit(strippedSsn) ? null : { ssnInvalid: 'Inkorrekt personnummer' };
}; };
} }