feat(export): Added export deltagare. (TV-872)

Squashed commit of the following:

commit 9c06b7f1d44a4b48d7f31a22b6bfbd233e09d2f7
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 15:07:15 2021 +0100

    Updated api endpoint

commit f9bfbecda6d03b70d87febbada920d0eca798b1f
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 15:01:13 2021 +0100

    Updated libs

commit 9edb413d537288fa0708b8a04eb54f801ebc23a0
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:58:13 2021 +0100

    Added @types/file-saver

commit 624affac55ce771fd58e2770a0d4a98233a8e9ba
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:42:29 2021 +0100

    Updated libs

commit 65dae1d906bbcec474581692b2aced9e47d2484c
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:36:42 2021 +0100

    Added utils lib config

commit 223bd59724663523bdbaf87b5502396156ddb9eb
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:06:13 2021 +0100

    Added validation

commit 166dfcf0448155ac21c0eaa904b4ce1271f73193
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 13:25:35 2021 +0100

    Changed styling and removed some fake data

commit 3906f2793dd52b626b95c13e115495451332c894
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 13:18:52 2021 +0100

    Added digi-ng datepicker

commit de0d51434d15cac5476303d4b417c591da16fd8f
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 12:31:48 2021 +0100

    Added checkbox
This commit is contained in:
Erik Tiekstra
2021-11-03 09:18:25 +01:00
parent e8791556cb
commit c11b0c1463
59 changed files with 1000 additions and 1159 deletions

View File

@@ -10,7 +10,7 @@ import {
OnInit,
Output,
SimpleChanges,
ViewChild
ViewChild,
} from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { RoleEnum } from '@msfa-enums/role.enum';
@@ -23,8 +23,8 @@ import { UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model';
import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service';
import { ValidationErrorLink } from '@msfa-shared/components/error-list/error-list.component';
import { TreeNodesSelectorService } from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service';
import { uuid } from '@msfa-utils/uuid.util';
import { EmployeeValidator } from '@msfa-utils/validators/employee.validator';
import { uuid } from '@utils/uuid.util';
import { EmployeeFormService } from '../services/employee-form.service';
@Component({

View File

@@ -1,9 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ExportsComponent } from './exports.component';
const routes: Routes = [
{ path: '', data: { title: 'Exporter' }, component: ExportsComponent },
{
path: '',
redirectTo: 'deltagare',
pathMatch: 'full',
},
{
path: 'deltagare',
data: { title: 'Skapa export för deltagare' },

View File

@@ -3,8 +3,11 @@
<digi-typography>
<header class="exports__header">
<h1>Exporter</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut corporis voluptatum magnam cumque fugiat, neque, totam explicabo est reprehenderit perspiciatis ratione error ad eveniet inventore, tempora facilis nostrum earum! Unde!</p>
</header>
<main class="exports__contents"></main>
<main class="exports__contents">
</main>
</digi-typography>
</section>
</msfa-layout>

View File

@@ -1,12 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
import { ExportsRoutingModule } from './exports-routing.module';
import { ExportsComponent } from './exports.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [ExportsComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: ExportsComponent }]), LayoutModule],
imports: [CommonModule, ExportsRoutingModule, LayoutModule, ReactiveFormsModule],
})
export class ExportsModule {}

View File

@@ -1,10 +1,62 @@
<msfa-layout>
<section class="deltagare-export">
<digi-typography>
<digi-typography>
<header class="deltagare-export__header">
<h1>Deltagare export</h1>
<h1>Exportera information om deltagare</h1>
<p>
Här kan du exportera deltagarinformation. Informationen laddas ner till din dator i CSV-format. Du kan endast
exportera deltagare som du har behörighet att se. Detta innebär att du bara kan få ut de deltagare som är
kopplade till samma utförande verksamhet och adress som du är inloggad på. Du kan även välja att inkludera
deltagare som exporterats vid tidigare tillfälle. Deltagare som exporterats tidigare markeras i filen.
</p>
</header>
<main class="deltagare-export__contents"></main>
<form class="deltagare-export__form" [formGroup]="formGroup" (ngSubmit)="fetchExportFile()">
<div
class="deltagare-export__period-wrapper"
[ngClass]="{'deltagare-export__period-wrapper--error': showPeriodError}"
>
<div class="deltagare-export__period-inputs">
<digi-ng-form-datepicker
[afDisableValidStyle]="true"
[afMaxDate]="maxDate"
[afInvalid]="showPeriodError"
afLabel="Från datum"
[formControl]="startDateFormControl"
></digi-ng-form-datepicker>
<digi-ng-form-datepicker
[afDisableValidStyle]="true"
[afMaxDate]="maxDate"
[afInvalid]="showPeriodError"
afLabel="Till och med datum"
[formControl]="endDateFormControl"
></digi-ng-form-datepicker>
</div>
<div aria-atomic="true" role="alert">
<digi-form-validation-message *ngIf="showPeriodError" af-variation="error"
>{{formErrors.datesMismatch}}</digi-form-validation-message
>
</div>
</div>
<ui-checkbox
[formControl]="includeExportedDeltagareFormControl"
uiLabel="Inkludera redan exporterade deltagare"
></ui-checkbox>
<footer class="deltagare-export__footer">
<digi-notification-alert
*ngIf="fetchError$ | async as error"
class="deltagare-export__alert"
af-variation="danger"
af-heading="Någonting gick fel"
>
<p>Kunde inte hämta exportfilen för deltagare. 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="deltagare-export__cta-wrapper">
<digi-button af-type="submit" af-size="m">Exportera deltagare</digi-button>
</div>
</footer>
</form>
</digi-typography>
</section>
<ui-loader *ngIf="fetchIsLoading$ | async" uiType="absolute"></ui-loader>
</section>
</msfa-layout>

View File

@@ -1,10 +1,26 @@
@import 'variables/gutters';
@import 'variables/z-index';
.deltagare-export {
&__contents {
max-width: var(--digi--typography--text--max-width);
position: relative;
z-index: $msfa__z-index-default;
&__form {
display: flex;
flex-direction: column;
gap: $digi--layout--gutter--l;
margin-top: $digi--layout--gutter--l;
}
&__period-inputs {
display: flex;
gap: $digi--layout--gutter--xl;
}
&__footer {
display: flex;
flex-direction: column;
gap: var(--digi--layout--gutter);
}
}

View File

@@ -1,8 +1,12 @@
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { UiCheckboxModule } from '@ui/checkbox/checkbox.module';
import { DeltagareExportComponent } from './deltagare-export.component';
import { DeltagareExportService } from './deltagare-export.service';
describe('DeltagareExportComponent', () => {
let component: DeltagareExportComponent;
@@ -13,7 +17,15 @@ describe('DeltagareExportComponent', () => {
void TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareExportComponent],
imports: [HttpClientTestingModule, RouterTestingModule],
imports: [
HttpClientTestingModule,
RouterTestingModule,
FormsModule,
ReactiveFormsModule,
DigiNgFormDatepickerModule,
UiCheckboxModule,
],
providers: [DeltagareExportService],
}).compileComponents();
})
);

View File

@@ -1,4 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { DeltagareExportRequest } from '@msfa-models/api/deltagare-export.request.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { saveAs } from 'file-saver';
import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { DeltagareExportFormData, DeltagareExportFormErrors } from './deltagare-export.model';
import { DeltagareExportService } from './deltagare-export.service';
import { DeltagareExportValidator } from './deltagare-export.validator';
@Component({
selector: 'msfa-deltagare-export',
@@ -6,4 +15,80 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
styleUrls: ['./deltagare-export.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeltagareExportComponent {}
export class DeltagareExportComponent {
maxDate = new Date();
shouldValidate$ = new BehaviorSubject<boolean>(false);
fetchIsLoading$ = new BehaviorSubject<boolean>(false);
fetchError$ = new BehaviorSubject<CustomError>(null);
formGroup = new FormGroup(
{
startDate: new FormControl(null),
endDate: new FormControl(null),
includeExportedDeltagare: new FormControl(false),
},
[DeltagareExportValidator.isDeltagareExportValid()]
);
constructor(private deltagareExportService: DeltagareExportService) {}
get startDateFormControl(): FormControl {
return this.formGroup.get('startDate') as FormControl;
}
get endDateFormControl(): FormControl {
return this.formGroup.get('endDate') as FormControl;
}
get includeExportedDeltagareFormControl(): FormControl {
return this.formGroup.get('includeExportedDeltagare') as FormControl;
}
get deltagagareExportFormData(): DeltagareExportFormData {
return this.formGroup.value as DeltagareExportFormData;
}
get formErrors(): DeltagareExportFormErrors {
return this.formGroup.errors as DeltagareExportFormErrors;
}
get showPeriodError(): boolean {
return this.formErrors?.datesMismatch && this.shouldValidate$.getValue();
}
fetchExportFile(): void {
if (this.formGroup.invalid) {
this.shouldValidate$.next(true);
return;
}
const requestData = this._formDataToRequestData(this.deltagagareExportFormData);
this.fetchIsLoading$.next(true);
this.deltagareExportService
.fetchExportFile$(requestData)
.pipe(take(1))
.subscribe({
next: response => {
const blob = new Blob([response], { type: 'application/csv' });
saveAs(blob, 'deltagare-export.csv');
this.fetchIsLoading$.next(false);
},
error: (customError: CustomError) => {
this.fetchError$.next({ ...customError, message: customError.error.message });
this.fetchIsLoading$.next(false);
throw { ...customError, avoidToast: true };
},
});
}
private _formDataToRequestData(formData: DeltagareExportFormData): DeltagareExportRequest {
const { startDate, endDate, includeExportedDeltagare } = formData;
const requestData: DeltagareExportRequest = {
includeExportedDeltagare,
};
if (startDate) {
requestData.startDate = startDate;
}
if (endDate) {
requestData.endDate = endDate;
}
return requestData;
}
}

View File

@@ -0,0 +1,11 @@
export interface DeltagareExportFormData {
startDate: string;
endDate: string;
includeExportedDeltagare: boolean;
}
export type DeltagareExportFormKeys = keyof DeltagareExportFormData;
export interface DeltagareExportFormErrors {
datesMismatch?: string;
}

View File

@@ -1,12 +1,27 @@
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
import { UiCheckboxModule } from '@ui/checkbox/checkbox.module';
import { UiLoaderModule } from '@ui/loader/loader.module';
import { DeltagareExportComponent } from './deltagare-export.component';
import { DeltagareExportService } from './deltagare-export.service';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [DeltagareExportComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: DeltagareExportComponent }]), LayoutModule],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: DeltagareExportComponent }]),
LayoutModule,
FormsModule,
ReactiveFormsModule,
UiCheckboxModule,
UiLoaderModule,
DigiNgFormDatepickerModule,
],
providers: [DeltagareExportService],
})
export class DeltagareExportModule {}

View File

@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
import { DeltagareExportRequest } from '@msfa-models/api/deltagare-export.request.model';
import { ExportApiService } from '@msfa-services/api/export.api.service';
import { Observable } from 'rxjs';
@Injectable()
export class DeltagareExportService {
constructor(private exportApiService: ExportApiService) {}
public fetchExportFile$(requestData: DeltagareExportRequest): Observable<Blob> {
return this.exportApiService.fetchDeltagareExportFile$(requestData);
}
}

View File

@@ -0,0 +1,19 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { DeltagareExportFormData, DeltagareExportFormErrors } from './deltagare-export.model';
export class DeltagareExportValidator {
static isDeltagareExportValid(): ValidatorFn {
return (c: AbstractControl): DeltagareExportFormErrors => {
let errors: DeltagareExportFormErrors = null;
const { startDate, endDate } = c.value as DeltagareExportFormData;
if (startDate && endDate && startDate > endDate) {
errors = {
...errors,
datesMismatch: 'Från datum får inte vara senare än till och med datum',
};
}
return errors;
};
}
}