feature(Avvikelserapporten): Använd nya dialogen (TV-845)

Merge in TEA/mina-sidor-fa-web from feature/TV-845-ersätt-dialogen-i-Avvikelserapporten-(TV-845) to develop

Squashed commit of the following:

commit 780f93baab0891a2f2ba49bf14d2c3add99e03c8
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Dec 30 11:27:28 2021 +0100

    Update avvikelse-report-form.component.ts

commit bea883f6aedfe2655efbb69d66788c1366e48a43
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Dec 30 11:19:03 2021 +0100

    cleanup

commit ffdf13dfcb3d97960ddb585b09fa5e6446504a68
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:52:37 2021 +0100

    Update app.component.ts

commit 1631cb763bc7023a9e95682272fb63dcbe15d84e
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:52:03 2021 +0100

    fix deltagarelist

commit c6080ac50cb6773aac8d4e45336fc1ba2f053a8d
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:24:44 2021 +0100

    Update avrop.component.html

commit 85057d0860ddceef8309253c983a1674a57291c4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:21:33 2021 +0100

    wip

commit a73164bda8a8ae06c5700e382e197d823bef6767
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:20:04 2021 +0100

    wip

commit 83f3ada5c4c60c9e46d7b01bbbf92053eb1b29ff
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 15:18:30 2021 +0100

    wip

commit 5e184bc0e1a3f7bb6a3040d4da54a9b0e562dad3
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Dec 29 14:18:39 2021 +0100

    wip

commit 30a90ce726dde31974e26c974215f6a5a60b025e
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 15:11:14 2021 +0100

    added scrollbars inside dialog

commit 600dd20f3281b4206c12d851cba91ae0ecf1e21a
Merge: 7af19054 b9434741
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 14:30:49 2021 +0100

    Merge branch 'develop' into feature/TV-845-ersätt-dialogen-i-Avvikelserapporten-(TV-845)

commit 7af190549c0109f71af87157e8099aa0483c879f
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 12:05:22 2021 +0100

    wip

commit 6e47e4a641daf4cef121e96c3855e4ce4944c6c4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 09:38:36 2021 +0100

    wip

commit f9354d04f51425cce29f13a10b32555113edaaa2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Dec 28 09:16:54 2021 +0100

    feature(Nya deltagare): Använd nya dialogen (TV-845)
This commit is contained in:
Daniel Appelgren
2021-12-30 14:18:58 +01:00
parent 7c48ec175e
commit 8909f7c3d1
26 changed files with 359 additions and 142 deletions

View File

@@ -33,6 +33,7 @@ export class AppComponent extends UnsubscribeDirective implements OnInit {
private _dialogRef: UiDialogRef; private _dialogRef: UiDialogRef;
private _userIsIdle$: Observable<boolean> = this.idleService.isIdle$; private _userIsIdle$: Observable<boolean> = this.idleService.isIdle$;
private _idleDialogConfig: UiDialogConfig = { private _idleDialogConfig: UiDialogConfig = {
includeBasicFooter: true,
primaryButtonText: 'Fortsätt sessionen', primaryButtonText: 'Fortsätt sessionen',
primaryAction: () => this.setUserAsActive(), primaryAction: () => this.setUserAsActive(),
secondaryButtonText: 'Logga ut', secondaryButtonText: 'Logga ut',

View File

@@ -1,32 +1,32 @@
<ng-container *ngIf="deleteEmployeeData$ | async as deleteEmployeeData"> <ng-container *ngIf="deleteEmployeeData$ | async as deleteEmployeeData">
<digi-ng-dialog <!-- <digi-ng-dialog-->
*ngIf="deleteEmployeeData.toDelete" <!-- *ngIf="deleteEmployeeData.toDelete"-->
[afActive]="deleteEmployeeData.toDelete" <!-- [afActive]="deleteEmployeeData.toDelete"-->
(afOnPrimaryClick)="deleteEmployeeModelPrimaryClick(deleteEmployeeData)" <!-- (afOnPrimaryClick)="deleteEmployeeModelPrimaryClick(deleteEmployeeData)"-->
(afOnSecondaryClick)="closeDeleteEmployeeModal()" <!-- (afOnSecondaryClick)="closeDeleteEmployeeModal()"-->
(afOnInactive)="closeDeleteEmployeeModal()" <!-- (afOnInactive)="closeDeleteEmployeeModal()"-->
afHeading="Ta bort personalkonto" <!-- afHeading="Ta bort personalkonto"-->
afHeadingLevel="h2" <!-- afHeadingLevel="h2"-->
[afPrimaryButtonText]="getPrimaryButtonText(deleteEmployeeData.lastDeleted)" <!-- [afPrimaryButtonText]="getPrimaryButtonText(deleteEmployeeData.lastDeleted)"-->
[afSecondaryButtonText]="getSecondaryButtonText(deleteEmployeeData.lastDeleted)" <!-- [afSecondaryButtonText]="getSecondaryButtonText(deleteEmployeeData.lastDeleted)"-->
> <!-- >-->
<ng-container *ngIf="deleteEmployeeData.lastDeleted; else deletionWarning"> <!-- <ng-container *ngIf="deleteEmployeeData.lastDeleted; else deletionWarning">-->
<digi-notification-alert af-variation="success" af-heading="Allt gick bra" af-heading-level="h3"> <!-- <digi-notification-alert af-variation="success" af-heading="Allt gick bra" af-heading-level="h3">-->
<p>Personalkonto för {{deleteEmployeeData.lastDeleted.fullName}} är borttaget.</p> <!-- <p>Personalkonto för {{deleteEmployeeData.lastDeleted.fullName}} är borttaget.</p>-->
</digi-notification-alert> <!-- </digi-notification-alert>-->
</ng-container> <!-- </ng-container>-->
<ng-template #deletionWarning> <!-- <ng-template #deletionWarning>-->
<p>Är du säker på att du vill ta bort personalkontot för {{deleteEmployeeData.toDelete.fullName}}?</p> <!-- <p>Är du säker på att du vill ta bort personalkontot för {{deleteEmployeeData.toDelete.fullName}}?</p>-->
</ng-template> <!-- </ng-template>-->
<ng-container *ngIf="errorDuringDeletion$ | async as error"> <!-- <ng-container *ngIf="errorDuringDeletion$ | async as error">-->
<digi-notification-alert af-variation="danger" af-heading="Någonting gick fel" af-heading-level="h3"> <!-- <digi-notification-alert af-variation="danger" af-heading="Någonting gick fel" af-heading-level="h3">-->
<p> <!-- <p>-->
Vi kunde inte radera personalkontot för {{deleteEmployeeData.toDelete.fullName}}. Ladda om sidan och försök <!-- Vi kunde inte radera personalkontot för {{deleteEmployeeData.toDelete.fullName}}. Ladda om sidan och försök-->
igen. <!-- igen.-->
</p> <!-- </p>-->
<p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p> <!-- <p class="msfa__small-text" *ngIf="error.message">{{error.message}}</p>-->
</digi-notification-alert> <!-- </digi-notification-alert>-->
</ng-container> <!-- </ng-container>-->
</digi-ng-dialog> <!-- </digi-ng-dialog>-->
</ng-container> </ng-container>

View File

@@ -1,4 +1,3 @@
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@@ -7,7 +6,11 @@ import { EmployeeDeleteComponent } from './employee-delete.component';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [EmployeeDeleteComponent], declarations: [EmployeeDeleteComponent],
imports: [CommonModule, RouterModule, DigiNgDialogModule], imports: [
CommonModule,
RouterModule,
// DigiNgDialogModule
],
exports: [EmployeeDeleteComponent], exports: [EmployeeDeleteComponent],
}) })
export class EmployeeDeleteModule {} export class EmployeeDeleteModule {}

View File

@@ -86,8 +86,6 @@
</digi-button> </digi-button>
<ng-template #informationDialog> <ng-template #informationDialog>
<h2>Information</h2>
<h3>Genomförandereferens</h3>
<p> <p>
Genomförandereferens är det referensnummer du ska använda dig av i kontakten med Arbetsförmedlingen. Genomförandereferens är det referensnummer du ska använda dig av i kontakten med Arbetsförmedlingen.
Du kan också använda genomförandereferensen till att leta fram en order i leverantörsportalen. Du kan också använda genomförandereferensen till att leta fram en order i leverantörsportalen.
@@ -109,6 +107,7 @@
språkstöd som ingår i upphandlingen av olika tjänster och utbildningar. Du hittar mer information om språkstöd som ingår i upphandlingen av olika tjänster och utbildningar. Du hittar mer information om
språkstöd och tolk i förfrågningsunderlaget för specifik upphandling. språkstöd och tolk i förfrågningsunderlaget för specifik upphandling.
</p> </p>
<!-- </ui-dialog-layout>-->
</ng-template> </ng-template>
</div> </div>
<msfa-avrop-list <msfa-avrop-list

View File

@@ -4,6 +4,7 @@ import { Handledare } from '@msfa-models/handledare.model';
import { AvropService } from '@msfa-services/avrop.service'; import { AvropService } from '@msfa-services/avrop.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { UiDialog } from '@ui/dialog/ui-dialog.service'; import { UiDialog } from '@ui/dialog/ui-dialog.service';
import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
@Component({ @Component({
selector: 'msfa-avrop', selector: 'msfa-avrop',
@@ -13,6 +14,7 @@ import { UiDialog } from '@ui/dialog/ui-dialog.service';
}) })
export class AvropComponent implements OnDestroy { export class AvropComponent implements OnDestroy {
@ViewChild('informationDialog') informationDialog: TemplateRef<unknown>; @ViewChild('informationDialog') informationDialog: TemplateRef<unknown>;
uiDialogRef: UiDialogRef;
readonly totalAmountOfSteps = 3; readonly totalAmountOfSteps = 3;
currentStep$: Observable<number> = this.avropService.currentStep$; currentStep$: Observable<number> = this.avropService.currentStep$;
error$: Observable<string> = this.avropService.error$; error$: Observable<string> = this.avropService.error$;
@@ -77,6 +79,6 @@ export class AvropComponent implements OnDestroy {
} }
openAvropDialog(): void { openAvropDialog(): void {
this.uiDialog.open(this.informationDialog); this.uiDialogRef = this.uiDialog.open(this.informationDialog, { heading: 'Information', includeBasicFooter: true });
} }
} }

View File

@@ -9,7 +9,7 @@ import { UiSkeletonModule } from '@ui/skeleton/skeleton.module';
import { AvropComponent } from './avrop.component'; import { AvropComponent } from './avrop.component';
import { AvropFiltersModule } from './components/avrop-filters/avrop-filters.module'; import { AvropFiltersModule } from './components/avrop-filters/avrop-filters.module';
import { AvropListModule } from './components/avrop-list/avrop-list.module'; import { AvropListModule } from './components/avrop-list/avrop-list.module';
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog'; import { UiDialogModule } from '@ui/dialog/ui-dialog.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -24,7 +24,7 @@ import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
UiLoaderModule, UiLoaderModule,
HandledarePickerFormModule, HandledarePickerFormModule,
UnauthorizedAlertModule, UnauthorizedAlertModule,
DigiNgDialogModule, UiDialogModule,
], ],
}) })
export class AvropModule {} export class AvropModule {}

View File

@@ -0,0 +1,30 @@
<ui-dialog-layout [isLoading]="submitIsLoading$ | async">
<h2 uiDialogHeading>Vill du skicka in Avvikelserapport (avvikelse)</h2>
<msfa-report-description-list [avrop]="data.avrop">
<dt>Orsak till avvikelse:</dt>
<dd>{{data.chosenReason.name }}</dd>
<ng-container *ngFor="let question of data.avvikelseSubmitData.avvikelseAlternativ.frageformular">
<dd>{{question.svar.length === 0 ? 'Inget svar' : question.svar }}</dd>
</ng-container>
<dt>Dag för avvikelse:</dt>
<dd>{{data.avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
</msfa-report-description-list>
<digi-notification-alert
*ngIf="submitError$ | 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>
<ng-container uiDialogFooter>
<digi-button af-type="button" (click)="submitAndCloseConfirmDialog(data.avvikelseSubmitData)"
>Skicka in</digi-button
>
<digi-button af-type="button" af-variation="secondary" (click)="close()"> Avbryt </digi-button>
</ng-container>
</ui-dialog-layout>

View File

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

View File

@@ -0,0 +1,49 @@
import { Component } from '@angular/core';
import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
import { DeltagareAvrop } from '@msfa-models/avrop.model';
import { AvvikelseReason } from '@msfa-models/avvikelse-reason.model';
import { AvvikelseReportRequest } from '@msfa-models/api/avvikelse-request.model';
import { AvvikelseReportFormService } from '../avvikelse-report-form.service';
import { CustomError } from '@msfa-models/error/custom-error';
import { BehaviorSubject } from 'rxjs';
export interface AvvikelseConfirmDialogData {
avrop: DeltagareAvrop;
chosenReason: AvvikelseReason;
avvikelseSubmitData: AvvikelseReportRequest;
}
@Component({
selector: 'msfa-avvikelse-confirm-dialog',
templateUrl: './avvikelse-confirm-dialog.component.html',
styleUrls: ['./avvikelse-confirm-dialog.component.scss'],
})
export class AvvikelseConfirmDialogComponent {
submitIsLoading$ = new BehaviorSubject<boolean>(false);
submitError$ = new BehaviorSubject<CustomError>(null);
constructor(public uiDialogRef: UiDialogRef, private avvikelseReportFormService: AvvikelseReportFormService) {}
get data(): AvvikelseConfirmDialogData {
return this.uiDialogRef.config.data as AvvikelseConfirmDialogData;
}
close(): void {
this.uiDialogRef.close();
}
submitAndCloseConfirmDialog(avvikelseSubmitData: AvvikelseReportRequest): void {
this.submitIsLoading$.next(true);
this.avvikelseReportFormService.createAvvikelse$(avvikelseSubmitData).subscribe({
next: () => {
this.submitIsLoading$.next(false);
this.uiDialogRef.close({ submitted: new Date() });
},
error: (customError: CustomError) => {
this.submitError$.next({ ...customError, message: customError.error.message });
this.submitIsLoading$.next(false);
throw { ...customError, avoidToast: true };
},
});
}
}

View File

@@ -33,7 +33,11 @@
</div> </div>
<ng-template #formRef> <ng-template #formRef>
<form class="avvikelse-report-form__form" [formGroup]="avvikelseFormGroup" (ngSubmit)="openConfirmDialog()"> <form
class="avvikelse-report-form__form"
[formGroup]="avvikelseFormGroup"
(ngSubmit)="openConfirmDialog(avrop)"
>
<div class="avvikelse-report-form__form-item"> <div class="avvikelse-report-form__form-item">
<ui-select <ui-select
*ngIf="reasonsAsUiSelectOptions$ | async as reason; else loadingRef" *ngIf="reasonsAsUiSelectOptions$ | async as reason; else loadingRef"
@@ -90,40 +94,15 @@
</ui-link-button> </ui-link-button>
</div> </div>
</form> </form>
<digi-ng-dialog
[afActive]="confirmDialogIsOpen$ | async" <digi-notification-alert
(afOnPrimaryClick)="submitAndCloseConfirmDialog()" *ngIf="submitError$ | async as error"
(afOnInactive)="cancelConfirmDialog()" af-variation="danger"
afHeadingLevel="h2" af-heading="Någonting gick fel"
afPrimaryButtonText="Skicka in"
afSecondaryButtonText="Avbryt"
(afOnSecondaryClick)="cancelConfirmDialog()"
afHeading="Vill du skicka in Avvikelserapport (avvikelse)"
afAriaLabel="Förhandsgranska och skicka in Avvikelserapport (avvikelse)"
id="confirmAvvikelserapport"
> >
<ui-loader *ngIf="submitIsLoading$ | async" uiType="absolute"></ui-loader> <p>Kunde inte spara Avvikelserapport (avvikelse). Försök igen om en stund.</p>
<msfa-report-description-list [avrop]="avrop"> <p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
<dt>Orsak till avvikelse:</dt> </digi-notification-alert>
<dd>{{(chosenReason$ | async)?.name }}</dd>
<ng-container *ngIf="avvikelseSubmitData$ | async as avvikelseSubmitData; else loadingRef">
<ng-container *ngFor="let question of avvikelseSubmitData.avvikelseAlternativ.frageformular">
<dt>{{getCurrentQuestionFromId(question.fraga).name}}</dt>
<dd>{{question.svar.length === 0 ? 'Inget svar' : question.svar }}</dd>
</ng-container>
<dt>Dag för avvikelse:</dt>
<dd>{{avvikelseSubmitData.avvikelseAlternativ.rapporteringsdatum }}</dd>
</ng-container>
</msfa-report-description-list>
<digi-notification-alert
*ngIf="submitError$ | async as error"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte spara Avvikelserapport (avvikelse). Ladda om sidan och försök igen.</p>
<p *ngIf="error.message" class="msfa__small-text">{{error.message}}</p>
</digi-notification-alert>
</digi-ng-dialog>
</ng-template> </ng-template>
</ng-template> </ng-template>
</div> </div>

View File

@@ -12,8 +12,14 @@ import { RegexValidator } from '@msfa-utils/validators/regex.validator';
import { RequiredValidator } from '@msfa-validators/required.validator'; import { RequiredValidator } from '@msfa-validators/required.validator';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators'; import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { AvvikelseReportFormService } from './avvikelse-report-form.service'; import { AvvikelseReportFormService } from './avvikelse-report-form.service';
import { UiDialog } from '@ui/dialog/ui-dialog.service';
import { UiDialogRef } from '@ui/dialog/ui-dialog-ref';
import {
AvvikelseConfirmDialogComponent,
AvvikelseConfirmDialogData,
} from './avvikelse-confirm-dialog/avvikelse-confirm-dialog.component';
interface Params { interface Params {
genomforandeReferens: string; genomforandeReferens: string;
@@ -34,20 +40,22 @@ type AvvikelseFormKeys = keyof AvvikelseFormData;
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AvvikelseReportFormComponent implements OnInit, OnDestroy { export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
confirmDialogRef: UiDialogRef;
shouldValidate$ = new BehaviorSubject<boolean>(false); shouldValidate$ = new BehaviorSubject<boolean>(false);
reasonFormName: AvvikelseFormKeys = 'reason'; reasonFormName: AvvikelseFormKeys = 'reason';
questionsFormName: AvvikelseFormKeys = 'questions'; questionsFormName: AvvikelseFormKeys = 'questions';
reportingDateFormName: AvvikelseFormKeys = 'reportingDate'; reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
submitIsLoading$ = new BehaviorSubject<boolean>(false);
submitError$ = new BehaviorSubject<CustomError>(null); submitError$ = 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)
); );
avrop: DeltagareAvrop;
avrop$: Observable<DeltagareAvrop> = this.genomforandeReferens$.pipe( avrop$: Observable<DeltagareAvrop> = this.genomforandeReferens$.pipe(
switchMap(genomforandeReferens => this.avvikelseReportFormService.fetchAvropInformation$(genomforandeReferens)), switchMap(genomforandeReferens => this.avvikelseReportFormService.fetchAvropInformation$(genomforandeReferens)),
tap(avrop => (this.avrop = avrop)),
shareReplay(1) shareReplay(1)
); );
@@ -60,8 +68,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
chosenReasonId$: Observable<string>; chosenReasonId$: Observable<string>;
chosenReason$: Observable<AvvikelseReason>; chosenReason$: Observable<AvvikelseReason>;
questionsForChosenReason$: Observable<AvvikelseQuestion[]>; questionsForChosenReason$: Observable<AvvikelseQuestion[]>;
avvikelseSubmitData$: Observable<AvvikelseReportRequest>;
confirmDialogIsOpen$ = new BehaviorSubject<boolean>(false);
submittedDate$ = new BehaviorSubject<Date | null>(null); submittedDate$ = new BehaviorSubject<Date | null>(null);
private subscriptions: Subscription[] = []; private subscriptions: Subscription[] = [];
private todayDateISO = new Date().toISOString().slice(0, 10); private todayDateISO = new Date().toISOString().slice(0, 10);
@@ -70,11 +76,19 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
[this.reportingDateFormName]: new FormControl(this.todayDateISO, [RequiredValidator('Datum är obligatoriskt')]), [this.reportingDateFormName]: new FormControl(this.todayDateISO, [RequiredValidator('Datum är obligatoriskt')]),
[this.questionsFormName]: new FormArray([]), [this.questionsFormName]: new FormArray([]),
}); });
private formData$: Observable<AvvikelseFormData> = this.avvikelseFormGroup
.valueChanges as Observable<AvvikelseFormData>;
private currentQuestions: AvvikelseQuestion[]; private currentQuestions: AvvikelseQuestion[];
constructor(private avvikelseReportFormService: AvvikelseReportFormService, private activatedRoute: ActivatedRoute) {} constructor(
private avvikelseReportFormService: AvvikelseReportFormService,
private activatedRoute: ActivatedRoute,
private uiDialog: UiDialog
) {
this.chosenReasonId$ = this.reasonFormControl.valueChanges as Observable<string>;
this.chosenReason$ = combineLatest([this.chosenReasonId$, this.reasons$]).pipe(
map(([chosenReasonId, reasons]) => reasons.find(reason => reason.id.toString() === chosenReasonId)),
shareReplay(1)
);
}
get reasonFormControl(): AbstractControl | undefined { get reasonFormControl(): AbstractControl | undefined {
return this.avvikelseFormGroup.get(this.reasonFormName); return this.avvikelseFormGroup.get(this.reasonFormName);
@@ -93,11 +107,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.chosenReasonId$ = this.reasonFormControl.valueChanges as Observable<string>;
this.chosenReason$ = combineLatest([this.chosenReasonId$, this.reasons$]).pipe(
map(([chosenReasonId, reasons]) => reasons.find(reason => reason.id.toString() === chosenReasonId))
);
this.questionsForChosenReason$ = combineLatest([this.chosenReasonId$, this.allAvvikelseQuestions$]).pipe( this.questionsForChosenReason$ = combineLatest([this.chosenReasonId$, this.allAvvikelseQuestions$]).pipe(
map(([chosenOrsak, allAvvikelseQuestions]) => { map(([chosenOrsak, allAvvikelseQuestions]) => {
return allAvvikelseQuestions.filter(question => question.id.startsWith(chosenOrsak.toString() + '_')); return allAvvikelseQuestions.filter(question => question.id.startsWith(chosenOrsak.toString() + '_'));
@@ -105,21 +114,15 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
); );
this.subscriptions.push( this.subscriptions.push(
this.chosenReason$.subscribe(() => { this.chosenReason$.subscribe(chosenReason => {
this.shouldValidate$.next(false); this.shouldValidate$.next(false);
}), }),
this.questionsForChosenReason$.subscribe(questions => { this.questionsForChosenReason$.subscribe(questions => {
this.clearQuestions(); this.clearQuestions();
questions.forEach(question => this.addQuestionToForm(question)); questions.forEach(question => this.addQuestionToForm(question));
}) })
); );
this.avvikelseSubmitData$ = combineLatest([this.genomforandeReferens$, this.chosenReasonId$, this.formData$]).pipe(
map(([genomforandeReferens, chosenReason, formData]) =>
this.makeAvvikelseSubmitData(genomforandeReferens, chosenReason, formData)
),
shareReplay(1)
);
} }
questionIsRequired(question: AvvikelseQuestion): boolean { questionIsRequired(question: AvvikelseQuestion): boolean {
@@ -138,6 +141,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
private _isAfterStartDate(startDate: Date): boolean { private _isAfterStartDate(startDate: Date): boolean {
return new Date() > startDate; return new Date() > startDate;
} }
private _isBeforeLastPossibleReportDay(endDate: Date): boolean { private _isBeforeLastPossibleReportDay(endDate: Date): boolean {
// Reporting is allowed at latest 5 days past avrop end date. // Reporting is allowed at latest 5 days past avrop end date.
// Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future // Because it's workdays and not calendar days we temporarily set this to much more. This date should be fetched from API in the future
@@ -158,44 +162,41 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
} }
} }
openConfirmDialog(): void { openConfirmDialog(avrop: DeltagareAvrop): void {
this.shouldValidate$.next(true); this.shouldValidate$.next(true);
markControlsAsDirty(Object.values(this.avvikelseFormGroup.controls)); markControlsAsDirty(Object.values(this.avvikelseFormGroup.controls));
this.avvikelseFormGroup.markAllAsTouched(); this.avvikelseFormGroup.markAllAsTouched();
if (this.avvikelseFormGroup.valid) { if (this.avvikelseFormGroup.valid) {
this.confirmDialogIsOpen$.next(true); combineLatest([this.chosenReason$, this.genomforandeReferens$])
.pipe(
first(),
switchMap(([chosenReason, genomforandeReferens]) => {
const avvikelseSubmitData: AvvikelseReportRequest = this._makeAvvikelseSubmitData(
genomforandeReferens,
chosenReason.id.toString(),
this.avvikelseFormGroup.value as AvvikelseFormData
);
const data: AvvikelseConfirmDialogData = { chosenReason, avvikelseSubmitData, avrop };
return this.uiDialog.open<{ submitted: Date }>(AvvikelseConfirmDialogComponent, { data }).afterClosed$;
})
)
.subscribe(closedResult => {
this.submitError$.next(null);
if (closedResult.data?.submitted) {
this.submittedDate$.next(closedResult.data.submitted);
}
});
} }
} }
submitAndCloseConfirmDialog(): void {
this.submitIsLoading$.next(true);
this.avvikelseSubmitData$.pipe(take(1)).subscribe(avvikelseSubmitData =>
this.avvikelseReportFormService.createAvvikelse$(avvikelseSubmitData).subscribe({
next: () => {
this.submitIsLoading$.next(false);
this.submittedDate$.next(new Date());
this.confirmDialogIsOpen$.next(false);
},
error: (customError: CustomError) => {
this.submitError$.next({ ...customError, message: customError.error.message });
this.submitIsLoading$.next(false);
throw { ...customError, avoidToast: true };
},
})
);
}
cancelConfirmDialog(): void {
this.confirmDialogIsOpen$.next(false);
this.submitError$.next(null);
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscriptions.forEach(subscription => subscription.unsubscribe()); this.subscriptions.forEach(subscription => subscription.unsubscribe());
} }
private makeAvvikelseSubmitData( private _makeAvvikelseSubmitData(
genomforandeReferens: number, genomforandeReferens: number,
chosenReason: string, chosenReason: string,
formData: AvvikelseFormData formData: AvvikelseFormData
@@ -219,6 +220,8 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
private addQuestionToForm(question: AvvikelseQuestion): void { private addQuestionToForm(question: AvvikelseQuestion): void {
// FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit // FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit
// TODO we can actually just put id in the formgroup, as we do in slutredovisning-form-step1.component.ts. That would simplify this file.
this.currentQuestions.push(question); this.currentQuestions.push(question);
this.questionsFormArray.push( this.questionsFormArray.push(

View File

@@ -1,4 +1,3 @@
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 { 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';
@@ -17,10 +16,12 @@ import { ReportDescriptionListModule } from '../../../components/report-descript
import { ReportLayoutModule } from '../../../components/report-layout/report-layout.module'; import { ReportLayoutModule } from '../../../components/report-layout/report-layout.module';
import { AvvikelseReportFormComponent } from './avvikelse-report-form.component'; import { AvvikelseReportFormComponent } from './avvikelse-report-form.component';
import { AvvikelseReportFormService } from './avvikelse-report-form.service'; import { AvvikelseReportFormService } from './avvikelse-report-form.service';
import { AvvikelseConfirmDialogComponent } from './avvikelse-confirm-dialog/avvikelse-confirm-dialog.component';
import { UiDialogModule } from '@ui/dialog/ui-dialog.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvvikelseReportFormComponent], declarations: [AvvikelseReportFormComponent, AvvikelseConfirmDialogComponent],
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: AvvikelseReportFormComponent }]), RouterModule.forChild([{ path: '', component: AvvikelseReportFormComponent }]),
@@ -34,10 +35,10 @@ import { AvvikelseReportFormService } from './avvikelse-report-form.service';
UiLoaderModule, UiLoaderModule,
UiSelectModule, UiSelectModule,
ReportDescriptionListModule, ReportDescriptionListModule,
DigiNgDialogModule,
UiTextareaModule, UiTextareaModule,
UiLinkButtonModule, UiLinkButtonModule,
PreventDoubleSubmitModule, PreventDoubleSubmitModule,
UiDialogModule,
], ],
providers: [AvvikelseReportFormService], providers: [AvvikelseReportFormService],
exports: [AvvikelseReportFormComponent], exports: [AvvikelseReportFormComponent],

View File

@@ -1,5 +1,3 @@
<h2>Händelser för {{deltagare.fullName}}</h2>
<div class="deltagare-list-handelser" *ngIf="deltagare"> <div class="deltagare-list-handelser" *ngIf="deltagare">
<h3 *ngIf="activeHandelseMotivation" class="deltagare-list-handelser__sub-heading">{{deltagare.fullName}}</h3> <h3 *ngIf="activeHandelseMotivation" class="deltagare-list-handelser__sub-heading">{{deltagare.fullName}}</h3>
<p>Genomförandereferens: <strong>{{deltagare.genomforandeReferens}}</strong></p> <p>Genomförandereferens: <strong>{{deltagare.genomforandeReferens}}</strong></p>

View File

@@ -85,7 +85,9 @@ export class DeltagareListTableComponent {
openHandelser(singleDeltagare: DeltagareCompact): void { openHandelser(singleDeltagare: DeltagareCompact): void {
this.uiDialog.open(this.handelserDialogComponent, { this.uiDialog.open(this.handelserDialogComponent, {
data: singleDeltagare, data: singleDeltagare,
heading: 'Händelser för ' + singleDeltagare.fullName,
primaryButtonText: 'Stäng', primaryButtonText: 'Stäng',
includeBasicFooter: true,
}); });
} }

View File

@@ -0,0 +1,14 @@
<div class="ui-dialog-layout">
<ui-loader *ngIf="isLoading" uiType="absolute"></ui-loader>
<div class="ui-dialog-layout__heading">
<ng-content select="[uiDialogHeading]"></ng-content>
</div>
<div class="ui-dialog-layout__scrollable-content">
<ng-content></ng-content>
</div>
<footer class="ui-dialog-layout__footer">
<ng-content select="[uiDialogFooter]"></ng-content>
</footer>
</div>

View File

@@ -0,0 +1,40 @@
@import 'variables/shadows';
@import 'variables/gutters';
.ui-dialog-layout {
padding-top: $digi--layout--gutter--s;
display: flex;
flex-direction: column;
gap: $digi--layout--gutter--s;
max-height: 90vh;
&__heading {
flex: 1 1 2rem;
}
&__scrollable-content {
overflow: auto;
flex-grow: 1;
}
&__close-button {
position: absolute;
top: var(--digi--layout--gutter);
right: var(--digi--layout--gutter--s);
background: transparent;
border: none;
display: flex;
justify-content: center;
align-items: center;
}
&__close-button-text {
font-size: var(--digi--typography--font-size--s);
}
&__footer {
flex: 1 0 4rem;
min-height: var(--digi--layout--gutter);
display: flex;
margin-top: $digi--layout--gutter--l;
gap: var(--digi--layout--gutter);
}
}

View File

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

View File

@@ -0,0 +1,11 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'ui-dialog-layout',
templateUrl: './ui-dialog-layout.component.html',
styleUrls: ['./ui-dialog-layout.component.scss'],
})
export class UiDialogLayoutComponent {
@Input() uiHeading: string;
@Input() isLoading = false;
}

View File

@@ -21,12 +21,18 @@ export class UiDialogRef<CloseResponseData = unknown, InputDataType = unknown> {
overlay.backdropClick().subscribe(() => this._close('backdropClick', null)); overlay.backdropClick().subscribe(() => this._close('backdropClick', null));
} }
get includeBasicFooter(): boolean {
return this.config.includeBasicFooter;
}
get primaryButtonText(): string { get primaryButtonText(): string {
return this.config.primaryButtonText; return this.config.primaryButtonText;
} }
get secondaryButtonText(): string { get secondaryButtonText(): string {
return this.config.secondaryButtonText; return this.config.secondaryButtonText;
} }
get heading(): string {
return this.config.heading;
}
close(data?: CloseResponseData): void { close(data?: CloseResponseData): void {
this._close('close', data); this._close('close', data);

View File

@@ -1,21 +1,25 @@
<div class="ui-dialog"> <div class="ui-dialog">
<ng-container [ngSwitch]="contentType"> <h2 class="ui-dialog__heading" *ngIf="heading">{{heading}}</h2>
<ng-container *ngSwitchCase="'string'"> <div [ngClass]="{'ui-dialog__scrollable-content': includeBasicFooter}">
<div class="box"> <ng-container [ngSwitch]="contentType">
<ng-container *ngSwitchCase="'string'">
<div [innerHTML]="content"></div> <div [innerHTML]="content"></div>
</div> <footer class="ui-dialog__footer">
</ng-container> <digi-button af-type="button" (click)="close()">Stäng</digi-button>
</footer>
</ng-container>
<ng-container *ngSwitchCase="'template'"> <ng-container *ngSwitchCase="'template'">
<ng-container *ngTemplateOutlet="content; context: context"></ng-container> <ng-container *ngTemplateOutlet="content; context: context"></ng-container>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'component'"> <ng-container *ngSwitchCase="'component'">
<ng-container *ngComponentOutlet="content"></ng-container> <ng-container *ngComponentOutlet="content"></ng-container>
</ng-container>
</ng-container> </ng-container>
</ng-container> </div>
<footer class="ui-dialog__footer"> <footer class="ui-dialog__footer" *ngIf="includeBasicFooter">
<digi-button af-type="button" (click)="primaryAction()">{{primaryButtonText}}</digi-button> <digi-button af-type="button" (click)="primaryAction()">{{primaryButtonText}}</digi-button>
<digi-button af-type="button" af-variation="secondary" *ngIf="secondaryButtonText" (click)="secondaryAction()"> <digi-button af-type="button" af-variation="secondary" *ngIf="secondaryButtonText" (click)="secondaryAction()">
{{secondaryButtonText}} {{secondaryButtonText}}

View File

@@ -7,6 +7,18 @@
box-shadow: $msfa__shadow; box-shadow: $msfa__shadow;
padding: $digi--layout--gutter--s $digi--layout--gutter--xl $digi--layout--gutter--l; padding: $digi--layout--gutter--s $digi--layout--gutter--xl $digi--layout--gutter--l;
position: relative; position: relative;
max-height: 90vh;
display: flex;
flex-direction: column;
&__heading {
flex: 1 1 2rem;
}
&__scrollable-content {
overflow: auto;
flex-grow: 1;
}
&__close-button { &__close-button {
position: absolute; position: absolute;

View File

@@ -28,11 +28,18 @@ export class UiDialogComponent implements OnInit {
get secondaryButtonText(): string { get secondaryButtonText(): string {
return this.uiDialogRef.secondaryButtonText; return this.uiDialogRef.secondaryButtonText;
} }
get heading(): string {
return this.uiDialogRef.heading;
}
primaryAction(): void { primaryAction(): void {
this.uiDialogRef.primaryAction(); this.uiDialogRef.primaryAction();
} }
get includeBasicFooter(): boolean {
return this.uiDialogRef.includeBasicFooter;
}
secondaryAction(): void { secondaryAction(): void {
this.uiDialogRef.secondaryAction(); this.uiDialogRef.secondaryAction();
} }

View File

@@ -9,9 +9,12 @@ export interface UiDialogConfig<DialogInputData = unknown> {
minHeight?: string; minHeight?: string;
maxHeight?: string; maxHeight?: string;
heading?: string;
/** /**
* primaryButtonText defaults to 'Stäng' * primaryButtonText defaults to 'Stäng'
*/ */
includeBasicFooter?: boolean;
primaryButtonText?: string; primaryButtonText?: string;
/** /**

View File

@@ -3,11 +3,14 @@ import { CommonModule } from '@angular/common';
import { UiDialogComponent } from './ui-dialog.component'; import { UiDialogComponent } from './ui-dialog.component';
import { UiIconModule } from '@ui/icon/icon.module'; import { UiIconModule } from '@ui/icon/icon.module';
import { UiDialog } from '@ui/dialog/ui-dialog.service'; import { UiDialog } from '@ui/dialog/ui-dialog.service';
import { UiDialogLayoutComponent } from './ui-dialog-layout/ui-dialog-layout.component';
import { UiLoaderModule } from '@ui/loader/loader.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [UiDialogComponent], declarations: [UiDialogComponent, UiDialogLayoutComponent],
imports: [CommonModule, UiIconModule], imports: [CommonModule, UiIconModule, UiLoaderModule],
providers: [UiDialog], providers: [UiDialog],
exports: [UiDialogLayoutComponent],
}) })
export class UiDialogModule {} export class UiDialogModule {}

View File

@@ -19,24 +19,24 @@ export class UiDialog {
}); });
} }
open<DialogContent = unknown, T = unknown>( open<CloseResponseData = unknown, InputDataType = unknown>(
content: string | TemplateRef<unknown> | Type<unknown>, content: string | TemplateRef<unknown> | Type<unknown>,
config: UiDialogConfig<T> = {} config: UiDialogConfig<InputDataType> = { includeBasicFooter: false }
): UiDialogRef<DialogContent> { ): UiDialogRef<CloseResponseData> {
const positionStrategy = this.overlay.position().global().centerHorizontally().centerVertically(); const positionStrategy = this.overlay.position().global().centerHorizontally().centerVertically();
const configs = new OverlayConfig({ const configs = new OverlayConfig({
positionStrategy, positionStrategy,
minWidth: config.minWidth ?? '40rem', minWidth: config.minWidth ?? '20rem',
minHeight: config.minHeight ?? '40rem', minHeight: config.minHeight ?? '10rem',
maxWidth: config.maxWidth ?? '60rem', maxWidth: config.maxWidth ?? '60rem',
maxHeight: config.maxHeight ?? '60rem', maxHeight: config.maxHeight ?? '60rem',
hasBackdrop: true, hasBackdrop: true,
scrollStrategy: this.overlay.scrollStrategies.close(), scrollStrategy: this.overlay.scrollStrategies.block(),
backdropClass: 'cdk-overlay-dark-backdrop', backdropClass: 'cdk-overlay-dark-backdrop',
}); });
const overlayRef = this.overlay.create(configs); const overlayRef = this.overlay.create(configs);
const uiDialogRef = new UiDialogRef<DialogContent, T>(overlayRef, content, config); const uiDialogRef = new UiDialogRef<CloseResponseData, InputDataType>(overlayRef, content, config);
const injector = UiDialog._createInjector(uiDialogRef, this.injector, config); const injector = UiDialog._createInjector(uiDialogRef, this.injector, config);
overlayRef.attach(new ComponentPortal(UiDialogComponent, null, injector)); overlayRef.attach(new ComponentPortal(UiDialogComponent, null, injector));