Merge pull request #234 in TEA/mina-sidor-fa-web from feature/periodisk-redovisning-add-hours-to-activity to develop

Squashed commit of the following:

commit 01d637f78f28a68794ba4e8a12135f05fb91ddb7
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Oct 29 12:42:29 2021 +0200

    Added hours to PR incl. models

commit 8bea7c7ff17b80a2aa05416fd63d2bc697ba45a4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Fri Oct 29 11:57:59 2021 +0200

    wip
This commit is contained in:
Erik Tiekstra
2021-10-29 12:45:26 +02:00
parent 5b50bc407b
commit e52f499f45
10 changed files with 109 additions and 30 deletions

View File

@@ -137,26 +137,47 @@
formControlName="isSelected" formControlName="isSelected"
[afLabel]="activitiesFormArrayMetadata[i].name" [afLabel]="activitiesFormArrayMetadata[i].name"
></digi-ng-form-checkbox> ></digi-ng-form-checkbox>
<div class="periodisk-redovisning-form__activity-location" *ngIf="isSelected.currentValue"> <div class="periodisk-redovisning-form__activity-details" *ngIf="isSelected.currentValue">
<digi-ng-form-checkbox <div class="periodisk-redovisning-form__activity-group">
formControlName="performedRemotely" <digi-form-input
[afInvalid]="activityLocationIsInvalid(activityFormGroup)" class="periodisk-redovisning-form__activity-hours"
afLabel="Utfört på distans" af-label="Antal timmar under perioden"
></digi-ng-form-checkbox> af-type="number"
<digi-ng-form-checkbox af-max="200"
formControlName="performedPhysically" af-min="0"
[afInvalid]="activityLocationIsInvalid(activityFormGroup)" [afValidation]="activityHoursIsInvalid(activityFormGroup) ? 'error' : 'neutral'"
afLabel="Utfört på plats" formControlName="hours"
></digi-ng-form-checkbox> ngDefaultControl
<div aria-atomic="true" role="alert"> ></digi-form-input>
<ng-container *ngIf="formControlIsInvalid(activityFormGroup)"> <div aria-atomic="true" role="alert">
<digi-form-validation-message <digi-form-validation-message
*ngFor="let errorText of errorsToArray(activityFormGroup.errors)" *ngIf="activityHoursIsInvalid(activityFormGroup)"
af-variation="error" af-variation="error"
> >
{{errorText}} {{activityFormGroup.errors.hours}}
</digi-form-validation-message> </digi-form-validation-message>
</ng-container> </div>
</div>
<div class="periodisk-redovisning-form__activity-group">
<digi-ng-form-checkbox
formControlName="performedRemotely"
[afInvalid]="activityLocationIsInvalid(activityFormGroup)"
afLabel="Utfört på distans"
></digi-ng-form-checkbox>
<digi-ng-form-checkbox
formControlName="performedPhysically"
[afInvalid]="activityLocationIsInvalid(activityFormGroup)"
afLabel="Utfört på plats"
></digi-ng-form-checkbox>
<div aria-atomic="true" role="alert">
<digi-form-validation-message
*ngIf="activityLocationIsInvalid(activityFormGroup)"
af-variation="error"
>
{{activityFormGroup.errors.locationCheckboxes}}
</digi-form-validation-message>
</div>
</div> </div>
</div> </div>
</ng-container> </ng-container>
@@ -208,11 +229,7 @@
class="msfa__digi-icon periodisk-redovisning-form__activity-check" class="msfa__digi-icon periodisk-redovisning-form__activity-check"
aria-hidden="true" aria-hidden="true"
></digi-icon-check-circle> ></digi-icon-check-circle>
<span> <span>{{getActivityInfoAsString(activity)}}</span>
{{getActivityMetadata(activity.id).name}}: {{ activity.performedRemotely &&
activity.performedPhysically ? 'på distans och på plats' : activity.performedRemotely ? 'på
distans' : 'på plats'}}
</span>
</dd> </dd>
</ng-template> </ng-template>
</ng-container> </ng-container>

View File

@@ -42,18 +42,27 @@
&__no-activities-has-been-conducted-checkbox, &__no-activities-has-been-conducted-checkbox,
&__activity, &__activity,
&__activity-location { &__activity-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--digi--layout--gutter--s); gap: var(--digi--layout--gutter--s);
} }
&__activity-hours {
::ng-deep .digi-form-input__content {
max-width: 10rem;
}
}
&__no-activities-has-been-conducted-checkbox { &__no-activities-has-been-conducted-checkbox {
margin-bottom: $digi--layout--gutter--l; margin-bottom: $digi--layout--gutter--l;
} }
&__activity-location { &__activity-details {
margin-left: var(--digi--layout--gutter); display: flex;
flex-direction: column;
gap: var(--digi--layout--gutter);
margin-left: 1.875rem;
margin-bottom: var(--digi--layout--gutter--s); margin-bottom: var(--digi--layout--gutter--s);
} }

View File

@@ -181,6 +181,7 @@ export class PeriodiskRedovisningFormComponent implements OnInit {
isSelected: new FormControl(), isSelected: new FormControl(),
performedRemotely: new FormControl(), performedRemotely: new FormControl(),
performedPhysically: new FormControl(), performedPhysically: new FormControl(),
hours: new FormControl(),
}, },
PeriodiskRedovisningValidator.activityIsValid() PeriodiskRedovisningValidator.activityIsValid()
) )
@@ -200,14 +201,20 @@ export class PeriodiskRedovisningFormComponent implements OnInit {
): PeriodiskRedovisningRequest { ): PeriodiskRedovisningRequest {
const { period, hasOfferedJob, hasOfferedLanguageSupport } = formData; const { period, hasOfferedJob, hasOfferedLanguageSupport } = formData;
const activities: PeriodiskRedovisningActivityRequest[] = formData.activities const activities: PeriodiskRedovisningActivityRequest[] = formData.activities
.map(({ performedPhysically, performedRemotely, isSelected }, index) => ({ .map(({ performedPhysically, performedRemotely, isSelected, hours }, index) => ({
id: this.activitiesFormArrayMetadata[index].id, id: this.activitiesFormArrayMetadata[index].id,
performedPhysically: performedPhysically ?? false, performedPhysically: performedPhysically ?? false,
performedRemotely: performedRemotely ?? false, performedRemotely: performedRemotely ?? false,
hours: +hours,
isSelected, isSelected,
})) }))
.filter(activity => activity.isSelected) .filter(activity => activity.isSelected)
.map(({ id, performedPhysically, performedRemotely }) => ({ id, performedPhysically, performedRemotely })); .map(({ id, performedPhysically, performedRemotely, hours }) => ({
id,
performedPhysically,
performedRemotely,
hours,
}));
return { return {
genomforandeReferens, genomforandeReferens,
period, period,
@@ -226,10 +233,27 @@ export class PeriodiskRedovisningFormComponent implements OnInit {
return this.formControlIsInvalid(activityFormGroup) && !!errors.locationCheckboxes; return this.formControlIsInvalid(activityFormGroup) && !!errors.locationCheckboxes;
} }
activityHoursIsInvalid(activityFormGroup: AbstractControl): boolean {
const errors = activityFormGroup.errors as ActivityFormErrors;
return this.formControlIsInvalid(activityFormGroup) && !!errors.hours;
}
getActivityMetadata(activityId: number): Activity { getActivityMetadata(activityId: number): Activity {
return this.activitiesFormArrayMetadata.find(activity => activity.id === activityId); return this.activitiesFormArrayMetadata.find(activity => activity.id === activityId);
} }
getActivityInfoAsString(activity: PeriodiskRedovisningActivityRequest): string {
const { name } = this.getActivityMetadata(activity.id);
const hours = activity.hours === 1 ? '1 timme' : `${activity.hours} timmar`;
const location =
activity.performedRemotely && activity.performedPhysically
? 'på distans och på plats'
: activity.performedRemotely
? 'på distans'
: 'på plats';
return `${name}: ${hours} ${location}`;
}
openChangePeriodDialogIfValuesExist(): void { openChangePeriodDialogIfValuesExist(): void {
const { activities, hasOfferedJob, hasOfferedLanguageSupport, noActivitiesHasBeenConducted } = this.formGroup const { activities, hasOfferedJob, hasOfferedLanguageSupport, noActivitiesHasBeenConducted } = this.formGroup
.value as PeriodiskRedovisningFormData; .value as PeriodiskRedovisningFormData;

View File

@@ -2,6 +2,7 @@ export interface PeriodiskRedovisningFormActivity {
isSelected: boolean; isSelected: boolean;
performedRemotely: boolean; performedRemotely: boolean;
performedPhysically: boolean; performedPhysically: boolean;
hours: string;
} }
export interface PeriodiskRedovisningFormData { export interface PeriodiskRedovisningFormData {
@@ -20,4 +21,5 @@ export interface PeriodiskRedovisningFormErrors {
export interface ActivityFormErrors { export interface ActivityFormErrors {
locationCheckboxes?: string; locationCheckboxes?: string;
hours?: string;
} }

View File

@@ -36,8 +36,10 @@ export class PeriodiskRedovisningValidator {
static activityIsValid(): ValidatorFn { static activityIsValid(): ValidatorFn {
return (c: AbstractControl): ActivityFormErrors => { return (c: AbstractControl): ActivityFormErrors => {
let errors: ActivityFormErrors; let errors: ActivityFormErrors;
const controlValue = c.value as PeriodiskRedovisningFormActivity;
const { performedRemotely, performedPhysically, isSelected, hours } = controlValue;
const { performedRemotely, performedPhysically, isSelected } = c.value as PeriodiskRedovisningFormActivity;
if (isSelected && !performedPhysically && !performedRemotely) { if (isSelected && !performedPhysically && !performedRemotely) {
errors = { errors = {
...errors, ...errors,
@@ -45,6 +47,15 @@ export class PeriodiskRedovisningValidator {
}; };
} }
const isValidHours = hours?.length > 0 && Number.isInteger(+hours) && +hours >= 0 && +hours <= 200;
if (isSelected && !isValidHours) {
errors = {
...errors,
hours: 'Antal timmar måste vara ett heltal mellan 0 och 200',
};
}
return errors; return errors;
}; };
} }

View File

@@ -20,8 +20,7 @@
class="msfa__digi-icon periodisk-redovisning-view__activity-check" class="msfa__digi-icon periodisk-redovisning-view__activity-check"
aria-hidden="true" aria-hidden="true"
></digi-icon-check-circle> ></digi-icon-check-circle>
{{activity.name}}: {{ activity.performedRemotely && activity.performedPhysically ? 'På distans och på {{getActivityInfoAsString(activity)}}
plats' : activity.performedRemotely ? 'På distans' : 'På plats'}}
</li> </li>
</ul> </ul>
</dd> </dd>

View File

@@ -2,10 +2,10 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Params } from '@msfa-models/api/params.model'; import { Params } from '@msfa-models/api/params.model';
import { Avrop } from '@msfa-models/avrop.model'; import { Avrop } from '@msfa-models/avrop.model';
import { PeriodiskRedovisning, PeriodiskRedovisningActivity } from '@msfa-models/periodisk-redovisning.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators'; import { map, shareReplay, switchMap } from 'rxjs/operators';
import { PeriodiskRedovisningViewService } from './periodisk-redovisning-view.service'; import { PeriodiskRedovisningViewService } from './periodisk-redovisning-view.service';
import { PeriodiskRedovisning } from '@msfa-models/periodisk-redovisning.model';
@Component({ @Component({
selector: 'msfa-periodisk-redovisning-view', selector: 'msfa-periodisk-redovisning-view',
@@ -34,6 +34,17 @@ export class PeriodiskRedovisningViewComponent {
shareReplay(1) shareReplay(1)
); );
getActivityInfoAsString(activity: PeriodiskRedovisningActivity): string {
const hours = activity.hours === 1 ? '1 timme' : `${activity.hours} timmar`;
const location =
activity.performedRemotely && activity.performedPhysically
? 'på distans och på plats'
: activity.performedRemotely
? 'på distans'
: 'på plats';
return `${activity.name}: ${hours} ${location}`;
}
constructor( constructor(
private periodiskRedovisningViewService: PeriodiskRedovisningViewService, private periodiskRedovisningViewService: PeriodiskRedovisningViewService,
private activatedRoute: ActivatedRoute private activatedRoute: ActivatedRoute

View File

@@ -2,6 +2,7 @@ export interface PeriodiskRedovisningActivityRequest {
id: number; id: number;
performedRemotely: boolean; performedRemotely: boolean;
performedPhysically: boolean; performedPhysically: boolean;
hours: number;
} }
export interface PeriodiskRedovisningRequest { export interface PeriodiskRedovisningRequest {

View File

@@ -3,6 +3,7 @@ export interface PeriodiskRedovisningActivityResponse {
name: string; name: string;
performedRemotely: boolean; performedRemotely: boolean;
performedPhysically: boolean; performedPhysically: boolean;
hours: number;
} }
export interface PeriodiskRedovisningResponse { export interface PeriodiskRedovisningResponse {
@@ -25,18 +26,21 @@ export function mockOnePeriodiskRedovisningResponse(): PeriodiskRedovisningRespo
name: 'Aktivitet 1', name: 'Aktivitet 1',
performedRemotely: false, performedRemotely: false,
performedPhysically: true, performedPhysically: true,
hours: 25,
}, },
{ {
id: 19, id: 19,
name: 'Aktivitet 2', name: 'Aktivitet 2',
performedRemotely: true, performedRemotely: true,
performedPhysically: false, performedPhysically: false,
hours: 3,
}, },
{ {
id: 31, id: 31,
name: 'Aktivitet 3', name: 'Aktivitet 3',
performedRemotely: true, performedRemotely: true,
performedPhysically: true, performedPhysically: true,
hours: 2,
}, },
], ],
}; };

View File

@@ -5,6 +5,7 @@ export interface PeriodiskRedovisningActivity {
name: string; name: string;
performedRemotely: boolean; performedRemotely: boolean;
performedPhysically: boolean; performedPhysically: boolean;
hours: number;
} }
export interface PeriodiskRedovisning { export interface PeriodiskRedovisning {