feature(Slutredovisning): Formulär för att skapa och skicka in slutredovisning (TV-533)

Squashed commit of the following:

commit 5b427cdd62b881cc32d408beaf92f8e3c22a9bd4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Fri Nov 12 09:24:18 2021 +0100

    refactor

commit b51af1b25b573a3203f3dd7e8e1c3e1401f0d228
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Fri Nov 12 09:20:58 2021 +0100

    add yrke names

commit 1ed3a33fb997669bf1af0be52347b371af0177ba
Merge: 4d9d2522 0235f1f9
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Fri Nov 12 07:40:48 2021 +0100

    Merge branch 'develop' into feature/tv-533-Skapa-slutredovisning-2

    # Conflicts:
    #	apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts

commit 4d9d25226fc3b91386832b1367edcf016d336c03
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Nov 11 16:34:24 2021 +0100

    Fixed switch functionality inside slutredovisning model

commit cce93c169e39bba681e4e25f511fa42686134f97
Merge: b56ce52f 51ed2e84
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Thu Nov 11 16:15:46 2021 +0100

    Fixed conflicts and added deltid percent

commit 51ed2e841e00e8d7cea9225bd9cfeab8b783efbd
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 14:49:40 2021 +0100

    hide description on step 1 and 2

commit 269708be2f7fec84815b3e7f91e33d4c305b0846
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 14:04:59 2021 +0100

    cleanup

commit 05df3acf97f6115ec15cd295c1ed12721592f867
Merge: 3c803f38 c6adc71f
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 13:47:22 2021 +0100

    Merge branch 'develop' into feature/tv-533-Skapa-slutredovisning-2

commit 3c803f3883b23065c2e0c15f6946213e5972576b
Merge: c2724aa6 f332dd41
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 12:38:53 2021 +0100

    Merge branch 'develop' into feature/tv-533-Skapa-slutredovisning-2

    # Conflicts:
    #	apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/components/deltagare-tab-reports/components/reports-list/reports-list.component.ts
    #	apps/mina-sidor-fa/src/app/shared/enums/report-type.enum.ts

commit c2724aa654483001d48ca1c2d0a5bfc1b2552c89
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 12:22:05 2021 +0100

    add loader

commit b97b45fb22260dbdc9f9792e027b7bda68150ead
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 12:09:09 2021 +0100

    move formgroups to parent to persist stat

commit 37feaab19309777ef96fb70be62ede9e26498b89
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 10:43:53 2021 +0100

    fix radiobuttons

commit 644b9fd6288589cc3a66ceb8beefb623345e4308
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 09:30:31 2021 +0100

    .

commit 28875f21a381a966054523ff7ccb7df126b30120
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 08:20:07 2021 +0100

    move submit logic to step3

commit eef52c39dd970abbedc413b4c348098e07abe036
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Thu Nov 11 07:40:56 2021 +0100

    submit button

commit 4d732240e0ff6196d827572ff865392d6aaa0a9c
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Nov 10 12:47:43 2021 +0100

    fix some bugs

commit 642cc2b24fd3a5def8abb9ae45595d101ccdcfb7
Merge: c4d53407 2dc56685
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Nov 10 12:24:18 2021 +0100

    Merge branch 'develop' into feature/tv-533-Skapa-slutredovisning-2

commit c4d53407399d695f58e5a0be074d81bbc1b1cb21
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Nov 10 12:24:09 2021 +0100

    wip

commit 808810699850e611ead2ee7ed1b728acc3d93dd5
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Nov 10 12:02:01 2021 +0100

    slutredovisning

commit fa446717acc66e779ecdb87ba7a5b2224eeca9fe
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Nov 10 11:00:44 2021 +0100

    view for utbildning done

... and 64 more commits
This commit is contained in:
Daniel Appelgren
2021-11-12 09:28:42 +01:00
parent 0235f1f912
commit e947ab4b39
105 changed files with 5624 additions and 44 deletions

View File

@@ -0,0 +1,4 @@
export interface ErrorLink {
elementId: string;
text: string;
}

View File

@@ -0,0 +1,11 @@
<digi-form-error-list
*ngIf="uiErrorLinks && uiErrorLinks.length"
[afHeading]="uiHeadingText"
[afDescription]="uiDescription"
[afOverrideLink]="true"
(afOnClickLink)="setFocusOnFormElement($event.detail.detail.target)"
>
<a *ngFor="let validationErrorLink of uiErrorLinks" [attr.href]="'#' + validationErrorLink.elementId">
{{ validationErrorLink.text }}
</a>
</digi-form-error-list>

View File

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

View File

@@ -0,0 +1,33 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ErrorLink } from './error-link.model';
@Component({
selector: 'ui-error-list',
templateUrl: './error-list.component.html',
styleUrls: ['./error-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ErrorListComponent {
@Input() uiErrorLinks: ErrorLink[] = [];
@Input() uiHeadingText: string;
@Input() uiDescription: string;
setFocusOnFormElement(element: HTMLElement): void {
const id = element.getAttribute('href').replace('#', '');
const target = document.getElementById(id);
if (target) {
const targetPosition: DOMRect = target.getBoundingClientRect();
const absoluteElementPosition = targetPosition.top + window.scrollY;
const newPosition = absoluteElementPosition - window.innerHeight / 2;
if (target.focus) {
console.log(target);
target.tabIndex = -1;
target.focus();
window.scrollTo(0, newPosition);
target.tabIndex = 0;
}
}
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { ErrorListComponent } from './error-list.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [ErrorListComponent],
imports: [CommonModule],
exports: [ErrorListComponent],
})
export class UiErrorListModule {}

View File

@@ -0,0 +1,16 @@
export enum UiInputType {
COLOR = 'color',
DATE = 'date',
DATETIME_LOCAL = 'datetime-local',
EMAIL = 'email',
HIDDEN = 'hidden',
MONTH = 'month',
NUMBER = 'number',
PASSWORD = 'password',
SEARCH = 'search',
TEL = 'tel',
TEXT = 'text',
TIME = 'time',
URL = 'url',
WEEK = 'week',
}

View File

@@ -0,0 +1,5 @@
export enum UiInputVariation {
S = 's',
M = 'm',
L = 'l',
}

View File

@@ -0,0 +1,23 @@
<div class="ui-input" [ngClass]="{'ui-input--invalid': uiInvalid && uiValidationMessage}">
<digi-form-input
class="ui-input__input"
[afId]="uiId"
[afName]="uiName"
[afLabel]="uiLabel"
[afLabelDescription]="uiDescription"
[afType]="uiType"
[afVariation]="uiVariation"
[afRequired]="uiRequired"
[afAnnounceIfOptional]="uiAnnounceIfOptional"
[afMin]="uiMin"
[afMax]="uiMax"
[afValue]="currentValue"
[afValidation]="uiInvalid ? 'error' : 'neutral'"
(afOnInput)="checkForChange($event.detail.target.value)"
></digi-form-input>
<div class="ui-input__validation" aria-atomic="true" role="alert">
<digi-form-validation-message *ngIf="uiInvalid && uiValidationMessage" af-variation="error"
>{{uiValidationMessage}}</digi-form-validation-message
>
</div>
</div>

View File

@@ -0,0 +1,8 @@
.ui-input {
display: flex;
flex-direction: column;
&--invalid {
gap: var(--digi--layout--gutter--s);
}
}

View File

@@ -0,0 +1,23 @@
/* tslint:disable:no-unused-variable */
import { InputComponent } from './input.component';
export class MockInjector {
get = jest.fn();
}
// tslint:disable-next-line: max-classes-per-file
export class MockChangeDetectorRef {
markForCheck = jest.fn();
detach = jest.fn();
detectChanges = jest.fn();
reattach = jest.fn();
checkNoChanges = jest.fn();
}
describe('InputComponent', () => {
let component: InputComponent;
it('should create', () => {
component = new InputComponent(new MockInjector(), new MockChangeDetectorRef());
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,16 @@
import { ReactiveFormsModule } from '@angular/forms';
import { InputComponent } from './input.component';
import { UiInputModule } from './input.module';
export default { title: 'Input', component: InputComponent };
const componentModule = {
moduleMetadata: {
imports: [ReactiveFormsModule, UiInputModule],
},
};
export const standard = () => ({
...componentModule,
template: '<ui-input></ui-input>',
});

View File

@@ -0,0 +1,100 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Injector,
Input,
OnChanges,
Output,
} from '@angular/core';
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { uuid } from '@utils/uuid.util';
import { UiInputType } from './input-type.enum';
import { UiInputVariation } from './input-variation.enum';
/**
* A input input. Implemented with control value accessor
*
* ## Usage
* ``import {UiInputModule} from '@ui/input/input.module';``
*/
@Component({
selector: 'ui-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: InputComponent,
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InputComponent implements AfterViewInit, ControlValueAccessor, OnChanges {
@Input() uiId: string = uuid();
@Input() uiName: string;
@Input() uiLabel: string = '';
@Input() uiDescription: string;
@Input() uiVariation: UiInputVariation = UiInputVariation.M;
@Input() uiRequired: boolean;
@Input() uiAnnounceIfOptional: boolean = false;
@Input() uiMin: number;
@Input() uiMax: number;
@Input() uiType: UiInputType = UiInputType.TEXT;
@Input() uiInvalid: boolean;
@Input() uiValidationMessage: string;
@Output() uiOnChange: EventEmitter<any> = new EventEmitter();
name: string | number;
onTouched: () => {};
private onChange: (value: any) => {};
private _value: any;
constructor(private injector: Injector, private changeDetectorRef: ChangeDetectorRef) {}
get currentValue(): string {
return this._value || '';
}
ngAfterViewInit(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = this.uiName || ngControl.name;
}
}
ngOnChanges(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = this.uiName || ngControl.name;
}
}
checkForChange(rawValue: string): void {
const value = this.uiType === UiInputType.NUMBER ? +rawValue : rawValue;
if (this._value !== value) {
if (this.onChange) {
this.onChange(value);
}
this._value = value;
this.uiOnChange.emit(value);
}
}
writeValue(value: any): void {
this._value = value;
this.changeDetectorRef.detectChanges();
}
registerOnChange(fn: (value: string) => {}) {
this.onChange = fn;
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn;
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { InputComponent } from './input.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [CommonModule],
declarations: [InputComponent],
exports: [InputComponent],
})
export class UiInputModule {}

View File

@@ -1,8 +1,9 @@
<div class="ui-radiobutton-group" [ngClass]="{'ui-radiobutton-group--invalid': uiInvalid}">
<div class="ui-radiobutton-group__radiobuttons" [ngClass]="radiobuttonsModifierClass">
<digi-form-radiobutton
*ngFor="let item of uiRadiobuttons"
*ngFor="let item of uiRadiobuttons; let first = first; let index = index"
class="ui-radiobutton-group__radiobutton"
[afId]="first ? uiId : uiId + '-' + index"
[afLabel]="getLabelText(item.label)"
[afValue]="item.value"
[afChecked]="currentValue === item.value"

View File

@@ -10,6 +10,7 @@ import {
Output,
} from '@angular/core';
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { uuid } from '@utils/uuid.util';
import { RadiobuttonGroupDirection } from './radiobutton-group-direction.enum';
import { Radiobutton } from './radiobutton.model';
@@ -41,6 +42,7 @@ export class RadiobuttonGroupComponent implements ControlValueAccessor, AfterVie
@Input() uiSecondary: boolean;
@Input() uiRequired: boolean;
@Input() uiName: string;
@Input() uiId: string = uuid();
@Input() uiAnnounceIfOptional: boolean;
@Output() uiOnChange = new EventEmitter<any>();
@@ -85,19 +87,20 @@ export class RadiobuttonGroupComponent implements ControlValueAccessor, AfterVie
ngAfterViewInit(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = ngControl.name || this.uiName;
this.name = this.uiName || ngControl.name;
}
}
ngOnChanges(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = ngControl.name || this.uiName;
this.name = this.uiName || ngControl.name;
}
}
checkForChange(rawValue: any): void {
const value = this._transformValue(rawValue);
console.log(value);
if (this._value !== value) {
if (this.onChange) {
this.onChange(value);

View File

@@ -0,0 +1,4 @@
export interface SelectOption {
value: any;
name: string;
}

View File

@@ -0,0 +1,45 @@
<div class="ui-select" [ngClass]="{'ui-select--invalid': uiInvalid}">
<div class="ui-select__input-wrapper">
<digi-form-label
[afLabel]="uiLabel"
[afAnnounceIfOptional]="uiAnnounceIfOptional"
[afRequired]="uiRequired"
[afFor]="uiId"
[afDescription]="uiDescription"
></digi-form-label>
<div class="ui-select__select-wrapper">
<select
class="ui-select__select"
[ngClass]="{'ui-select__select--invalid': uiInvalid}"
[attr.id]="uiId"
[attr.name]="name"
[attr.required]="uiRequired"
(change)="checkForChange($event.target.value)"
>
<option
*ngIf="uiPlaceholder"
[selected]="!currentValue"
class="ui-select__option ui-select__option--placeholder"
disabled
value=""
>
{{uiPlaceholder}}
</option>
<option
*ngFor="let option of uiOptions"
[selected]="option.value === currentValue"
[attr.value]="option.value"
class="ui-select__option"
>
{{option.name}}
</option>
</select>
<digi-icon-arrow-down aria-hidden="true" class="ui-select__icon" slot="icon"></digi-icon-arrow-down>
</div>
</div>
<div aria-atomic="true" role="alert">
<digi-form-validation-message *ngIf="uiInvalid && uiValidationMessage" af-variation="error"
>{{uiValidationMessage}}</digi-form-validation-message
>
</div>
</div>

View File

@@ -0,0 +1,45 @@
@import 'variables/gutters';
@import 'functions/rem';
.ui-select {
display: flex;
flex-direction: column;
&--invalid {
gap: var(--digi--layout--gutter--xs);
}
&__select-wrapper {
position: relative;
display: flex;
align-items: center;
}
&__select {
appearance: none;
width: 100%;
padding: var(--digi--ui--input--padding);
padding-right: var(--digi--layout--padding--30);
height: var(--digi--ui--input--height);
border-width: rem(1);
cursor: pointer;
font-size: var(--digi--typography--font-size--m);
color: var(--digi--typography--color--text);
background-color: var(--digi--ui--color--background);
border-color: var(--digi--ui--input--border--color);
&--invalid {
border-color: var(--digi--ui--color--border--error);
background-color: var(--digi--ui--color--background--error);
}
}
&__option {
font-size: var(--digi--typography--font-size--l);
}
&__icon {
position: absolute;
right: rem(12);
}
}

View File

@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectComponent } from './select.component';
describe('SelectComponent', () => {
let component: SelectComponent;
let fixture: ComponentFixture<SelectComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SelectComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,16 @@
import { ReactiveFormsModule } from '@angular/forms';
import { SelectComponent } from './select.component';
import { UiSelectModule } from './select.module';
export default { title: 'Select', component: SelectComponent };
const componentModule = {
moduleMetadata: {
imports: [ReactiveFormsModule, UiSelectModule],
},
};
export const standard = () => ({
...componentModule,
template: '<ui-select></ui-select>',
});

View File

@@ -0,0 +1,111 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Injector,
Input,
OnChanges,
Output,
} from '@angular/core';
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { uuid } from '@utils/uuid.util';
import { SelectOption } from './select-option.model';
/**
* A select component.
*
* ## Usage
* ``import {UiSelectModule} from '@ui/select/select.module';``
*/
@Component({
selector: 'ui-select',
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectComponent implements ControlValueAccessor, AfterViewInit, OnChanges {
@Input() uiInvalid: boolean;
@Input() uiValidationMessage: string;
@Input() uiPlaceholder: string;
@Input() uiOptions: SelectOption[];
@Input() uiRequired: boolean;
@Input() uiLabel: string;
@Input() uiDescription: string;
@Input() uiAnnounceIfOptional: boolean;
@Input() uiId: string = uuid();
@Input() uiName: string;
@Output() uiOnChange = new EventEmitter<any>();
name: string | number;
onTouched: () => {};
private onChange: (value: any) => {};
private _value: any;
constructor(private injector: Injector, private changeDetectorRef: ChangeDetectorRef) {}
get currentValue(): any {
return this._value;
}
private get _requiredText() {
if (this.uiRequired && !this.uiAnnounceIfOptional) {
return ' (obligatoriskt)';
}
if (!this.uiRequired && this.uiAnnounceIfOptional) {
return ' (frivilligt)';
}
return '';
}
getLabelText(label: string): string {
return `${label}${this._requiredText}`;
}
ngAfterViewInit(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = this.uiName || ngControl.name;
}
}
ngOnChanges(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = this.uiName || ngControl.name;
}
}
checkForChange(value: any): void {
if (this._value !== value) {
if (this.onChange) {
this.onChange(value);
}
this._value = value;
this.uiOnChange.emit(value);
}
}
writeValue(value: any): void {
this._value = value;
this.changeDetectorRef.detectChanges();
}
registerOnChange(fn: (value: any) => {}) {
this.onChange = fn;
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn;
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { SelectComponent } from './select.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [CommonModule],
declarations: [SelectComponent],
exports: [SelectComponent],
})
export class UiSelectModule {}