diff --git a/apps/mina-sidor-fa/src/app/app.module.ts b/apps/mina-sidor-fa/src/app/app.module.ts
index 606a076..eebefba 100644
--- a/apps/mina-sidor-fa/src/app/app.module.ts
+++ b/apps/mina-sidor-fa/src/app/app.module.ts
@@ -1,30 +1,37 @@
import { registerLocaleData } from '@angular/common';
-import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import localeSe from '@angular/common/locales/sv';
-import { ErrorHandler, LOCALE_ID, NgModule } from '@angular/core';
+import { ErrorHandler, LOCALE_ID, NgModule, Provider } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
+import { environment } from '@msfa-environment';
import { AuthInterceptor } from '@msfa-interceptors/auth.interceptor';
-import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ToastListModule } from './components/toast-list/toast-list.module';
import { LoggingModule } from './logging.module';
import { AvropModule } from './pages/avrop/avrop.module';
+import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler';
+
registerLocaleData(localeSe);
+const providers: Provider[] = [
+ ApmErrorHandler,
+
+ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
+ { provide: LOCALE_ID, useValue: 'sv-SE' },
+];
+// Skip error handler in Dev until "Uncaught Error: ApplicationRef.tick is called recursively" is fixed
+if (environment.production) {
+ providers.push({
+ provide: ErrorHandler,
+ useClass: CustomErrorHandler,
+ });
+}
@NgModule({
declarations: [AppComponent],
imports: [LoggingModule, BrowserModule, HttpClientModule, AppRoutingModule, ToastListModule, AvropModule],
- providers: [
- ApmErrorHandler,
- {
- provide: ErrorHandler,
- useClass: CustomErrorHandler,
- },
- { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
- { provide: LOCALE_ID, useValue: 'sv-SE' },
- ],
+ providers,
bootstrap: [AppComponent],
})
export class AppModule {}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/deltagare-details.module.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/deltagare-details.module.ts
index 217e0d2..ca2445e 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/deltagare-details.module.ts
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/deltagare-details.module.ts
@@ -1,5 +1,9 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+import { Feature } from '@msfa-enums/feature.enum';
+import { environment } from '@msfa-environment';
+
+const activeFeatures: Feature[] = environment.activeFeatures;
const routes: Routes = [
{
@@ -55,21 +59,32 @@ const routes: Routes = [
m => m.GemensamPlaneringViewModule
),
},
- {
- path: 'periodisk-redovisning',
- data: { title: 'Skapa periodisk redovisning' },
- loadChildren: () =>
- import('./pages/report-forms/deltagare-periodisk-redovisning/deltagare-periodisk-redovisning.module').then(
- m => m.DeltagarePeriodiskRedovisningModule
- ),
- },
- {
- path: 'signal',
- data: { title: 'Skapa signal om arbete eller studier' },
- loadChildren: () => import('./pages/report-forms/signal-form/signal-form.module').then(m => m.SignalFormModule),
- },
];
+activeFeatures.forEach(feature => {
+ switch (feature) {
+ case Feature.REPORTING_PERIODISK_REDOVISNING:
+ routes.push({
+ path: 'periodisk-redovisning',
+ data: { title: 'Skapa Periodisk redovisning' },
+ loadChildren: () =>
+ import('./pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.module').then(
+ m => m.PeriodiskRedovisningFormModule
+ ),
+ });
+ break;
+ case Feature.REPORTING_SIGNAL:
+ routes.push({
+ path: 'signal',
+ data: { title: 'Skapa signal om arbete eller studier' },
+ loadChildren: () => import('./pages/report-forms/signal-form/signal-form.module').then(m => m.SignalFormModule),
+ });
+ break;
+ default:
+ break;
+ }
+});
+
@NgModule({
imports: [RouterModule.forChild(routes)],
})
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html
index 62baef3..0d646d5 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/components/deltagare-tab-reports/deltagare-tab-reports.component.html
@@ -24,7 +24,14 @@
afText="Skapa ny Avvikelserapport (avvikelse)"
>
-
+
+
+
+
();
+ _activeFeatures: Feature[] = environment.activeFeatures;
+
+ get signalButtonVisible(): boolean {
+ return this._activeFeatures.includes(Feature.REPORTING_SIGNAL);
+ }
+ get periodiskRedovisningButtonVisible(): boolean {
+ return this._activeFeatures.includes(Feature.REPORTING_PERIODISK_REDOVISNING);
+ }
+
emitNewPage(page: number): void {
this.reportsPaginated.emit(page);
}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/deltagare-card.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/deltagare-card.component.ts
index 8627b87..6a96a07 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/deltagare-card.component.ts
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/deltagare-card/deltagare-card.component.ts
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
+import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { Feature } from '@msfa-enums/feature.enum';
import { RoleEnum } from '@msfa-enums/role.enum';
import { environment } from '@msfa-environment';
@@ -19,7 +20,6 @@ import { HandledareService } from '@msfa-services/handledare.service';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
import { DeltagareCardService } from './deltagare-card.service';
-import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
@Component({
selector: 'msfa-deltagare-details',
@@ -113,7 +113,7 @@ export class DeltagareCardComponent extends UnsubscribeDirective {
get sensitiveDataVisible(): boolean {
return (
- this._activeFeatures.includes(Feature.SENSITIVE_INFORMATION) &&
+ this._activeFeatures.includes(Feature.DELTAGARE_SENSITIVE_INFORMATION) &&
this._userRoles?.some(role => role.type === RoleEnum.MSFA_ReportAndPlanning)
);
}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.html
index a78a58d..b074dbd 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.html
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.html
@@ -129,8 +129,7 @@
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.ts
index c19343e..6c68f32 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.ts
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/avvikelse-report-form/avvikelse-report-form.component.ts
@@ -40,7 +40,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
reportingDateFormName: AvvikelseFormKeys = 'reportingDate';
submitIsLoading$ = new BehaviorSubject(false);
- error$ = new BehaviorSubject(null);
+ submitError$ = new BehaviorSubject(null);
genomforandeReferens$: Observable = this.activatedRoute.params.pipe(
map((params: Params) => +params.genomforandeReferens)
@@ -110,10 +110,6 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
this.subscriptions.push(
this.chosenReason$.subscribe(() => {
this.shouldValidate$.next(false);
- }),
- this.questionsForChosenReason$.subscribe(questions => {
- this.clearQuestions();
- questions.forEach(question => this.addQuestionToForm(question));
})
);
@@ -157,7 +153,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
this.confirmDialogIsOpen$.next(false);
},
error: (customError: CustomError) => {
- this.error$.next({ ...customError, message: customError.error.message });
+ this.submitError$.next({ ...customError, message: customError.error.message });
this.submitIsLoading$.next(false);
throw { ...customError, avoidToast: true };
},
@@ -167,7 +163,7 @@ export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
cancelConfirmDialog(): void {
this.confirmDialogIsOpen$.next(false);
- this.error$.next(null);
+ this.submitError$.next(null);
}
ngOnDestroy(): void {
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/franvaro-report-form/franvaro-report-form.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/franvaro-report-form/franvaro-report-form.component.html
index 92d2ff6..ca2cb5a 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/franvaro-report-form/franvaro-report-form.component.html
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/franvaro-report-form/franvaro-report-form.component.html
@@ -318,7 +318,7 @@
{{expectedPresenceStartTimeFormControl.value}} - {{expectedPresenceEndTimeFormControl.value}}
(null);
+ submitError$ = new BehaviorSubject(null);
submitLoading$ = new BehaviorSubject(false);
lastSubmittedFranvaroReport$ = new BehaviorSubject(null);
currentGenomforandeReferens$: Observable = this.activatedRoute.params.pipe(
@@ -142,7 +142,7 @@ export class FranvaroReportFormComponent {
cancelConfirmDialog(): void {
this.confirmDialogOpen$.next(false);
- this.error$.next(null);
+ this.submitError$.next(null);
}
reasonChanged(): void {
@@ -168,7 +168,7 @@ export class FranvaroReportFormComponent {
genomforandeReferens: +genomforandeReferens,
franvaro: {
avvikelseOrsaksKod: reason,
- datum: dateToIsoString(date),
+ datum: formatDate(date),
heldag: wholeDay,
startTid: this.showTimePickers ? startTime : '0:00', // BÄR doesn't accept empty string or null
slutTid: this.showTimePickers ? endTime : '23:59', // BÄR doesn't accept empty string or null
@@ -195,7 +195,7 @@ export class FranvaroReportFormComponent {
this.confirmDialogOpen$.next(false);
},
error: (customError: CustomError) => {
- this.error$.next({ ...customError, message: customError.error.message });
+ this.submitError$.next({ ...customError, message: customError.error.message });
this.submitLoading$.next(false);
throw { ...customError, avoidToast: true };
},
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/extract-avrop-periods.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/extract-avrop-periods.spec.ts
new file mode 100644
index 0000000..397b984
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/extract-avrop-periods.spec.ts
@@ -0,0 +1,56 @@
+import { formatDate } from '@msfa-utils/format-to-date.util';
+import { parseISO } from 'date-fns';
+import { extractAvropPeriods } from './extract-avrop-periods';
+
+describe('extract-avrop-periods', function () {
+ describe('when avrop is between 2021-05-01 and 2021-08-15', function () {
+ const startDate = parseISO('2021-05-01');
+ const endDate = parseISO('2021-08-15');
+ const periods = extractAvropPeriods(startDate, endDate);
+
+ it('should yield 3 periods between 2021-05-01 and 2021-08-15', function () {
+ expect(periods.map(period => period.periodId)).toEqual(['2021-05', '2021-06', '2021-07', '2021-08']);
+ });
+
+ it('first period should start at startDate', function () {
+ expect(periods[0].startDate).toEqual(startDate);
+ });
+
+ it('second period should start with first day of month', function () {
+ expect(formatDate(periods[1].startDate)).toEqual('2021-06-01');
+ });
+
+ it('second period should end with last day of month', function () {
+ expect(formatDate(periods[1].endDate)).toEqual('2021-06-30');
+ });
+
+ it('last period should end with last day of avrop', function () {
+ expect(formatDate(periods[periods.length - 1].endDate)).toEqual('2021-08-15');
+ });
+ });
+
+ it('should throw if startDate is greater than endDate', function () {
+ const startDate = parseISO('2021-09-01');
+ const endDate = parseISO('2021-08-15');
+ expect(() => extractAvropPeriods(startDate, endDate)).toThrowError();
+ });
+
+ it('should throw if startDate is null', function () {
+ const startDate = null;
+ const endDate = parseISO('2021-08-15');
+ expect(() => extractAvropPeriods(startDate, endDate)).toThrowError();
+ });
+
+ it('should throw if endDate is null', function () {
+ const startDate = parseISO('2021-08-15');
+ const endDate = null;
+ expect(() => extractAvropPeriods(startDate, endDate)).toThrowError();
+ });
+
+ it('should work if startDate is end of short month', function () {
+ const startDate = parseISO('2021-02-28');
+ const endDate = parseISO('2021-03-31');
+ const periods = extractAvropPeriods(startDate, endDate);
+ expect(periods.map(period => period.periodId)).toEqual(['2021-02', '2021-03']);
+ });
+});
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/extract-avrop-periods.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/extract-avrop-periods.ts
new file mode 100644
index 0000000..4b176b7
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/extract-avrop-periods.ts
@@ -0,0 +1,30 @@
+import { AvropPeriod } from '@msfa-models/avrop-period.model';
+import { addMonths, endOfMonth, formatISO, startOfMonth, subMonths } from 'date-fns';
+
+export function dateToPeriodId(date: Date): string {
+ return formatISO(date).slice(0, 7);
+}
+
+export function extractAvropPeriods(avropStartDate: Date, avropEndDate: Date): AvropPeriod[] {
+ const today = new Date();
+
+ if (avropStartDate > today) {
+ throw new Error('Avropet har inte börjat ännu.');
+ }
+ if (avropStartDate > avropEndDate) {
+ throw new Error('Avropets startdatum måste komma innan slutdatumet.');
+ }
+
+ const previousPeriod = dateToPeriodId(subMonths(today, 1));
+ const periods: AvropPeriod[] = [] as AvropPeriod[];
+ let dateCounter = avropStartDate;
+
+ while (dateCounter <= avropEndDate && dateToPeriodId(dateCounter) <= previousPeriod) {
+ const startDate = dateCounter === avropStartDate ? avropStartDate : startOfMonth(dateCounter);
+ const endDate = endOfMonth(dateCounter) > avropEndDate ? avropEndDate : endOfMonth(dateCounter);
+ const period: AvropPeriod = { startDate, endDate, periodId: dateToPeriodId(dateCounter) };
+ periods.push(period);
+ dateCounter = addMonths(dateCounter, 1);
+ }
+ return periods;
+}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.html b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.html
new file mode 100644
index 0000000..1b7bd26
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.html
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.scss b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.scss
new file mode 100644
index 0000000..48d87e1
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.scss
@@ -0,0 +1,38 @@
+@import 'mixins/list';
+@import 'variables/gutters';
+
+.periodisk-redovisning-form {
+ max-width: var(--digi--typography--text--max-width);
+
+ &__confirmation,
+ &__warning,
+ &__form {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: $digi--layout--gutter--l;
+ }
+
+ &__footer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--digi--layout--gutter);
+ }
+
+ &__cta-wrapper {
+ display: flex;
+ gap: var(--digi--layout--gutter);
+ }
+
+ &__no-activities-has-been-conducted-checkbox {
+ margin-bottom: $digi--layout--gutter--l;
+ }
+
+ &__activity-checkboxes {
+ margin-bottom: var(--digi--layout--gutter--s);
+ }
+
+ &__activity-location-checkboxes {
+ margin-left: var(--digi--layout--gutter);
+ }
+}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.spec.ts
new file mode 100644
index 0000000..4050658
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.spec.ts
@@ -0,0 +1,38 @@
+import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
+import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { LayoutComponent } from '@msfa-shared/components/layout/layout.component';
+import { PeriodiskRedovisningFormComponent } from './periodisk-redovisning-form.component';
+import { PeriodiskRedovisningFormService } from './periodisk-redovisning-form.service';
+
+describe('PeriodiskRedovisningFormComponent', () => {
+ let component: PeriodiskRedovisningFormComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ declarations: [PeriodiskRedovisningFormComponent, LayoutComponent],
+ imports: [
+ RouterTestingModule,
+ HttpClientTestingModule,
+ DigiNgFormRadiobuttonGroupModule,
+ DigiNgFormCheckboxModule,
+ ],
+ providers: [PeriodiskRedovisningFormService],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PeriodiskRedovisningFormComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.ts
new file mode 100644
index 0000000..18f475d
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.component.ts
@@ -0,0 +1,233 @@
+import { FormSelectItem } from '@af/digi-ng/_form/form-select';
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
+import { ActivatedRoute, Params } from '@angular/router';
+import { Avrop } from '@msfa-models/avrop.model';
+import { AvropPeriod } from '@msfa-models/avrop-period.model';
+import { formatDate } from '@msfa-utils/format-to-date.util';
+import { subMonths } from 'date-fns';
+import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
+import { map, shareReplay, switchMap, take } from 'rxjs/operators';
+import { dateToPeriodId, extractAvropPeriods } from './extract-avrop-periods';
+import { PeriodiskRedovisningFormService } from './periodisk-redovisning-form.service';
+import { RadiobuttonGroupDirection } from '@af/digi-ng/_form/form-radiobutton-group';
+import { RequiredValidator } from '@msfa-validators/required.validator';
+import { Activity } from '@msfa-models/activity.model';
+import { GemensamPlaneringApiService } from '@msfa-services/api/gemensam-planering-api.service';
+import {
+ PeriodiskRedovisningActivityRequest,
+ PeriodiskRedovisningRequest,
+} from '@msfa-models/api/periodisk-redovisning.request.model';
+import { DateFormatOptions } from '@msfa-models/date-format-options.model';
+import {
+ ActivityFormErrors,
+ PeriodiskRedovisningFormData,
+ PeriodiskRedovisningFormErrors,
+ PeriodiskRedovisningFormKeys,
+} from './periodisk-redovisning-form.model';
+import { PeriodiskRedovisningValidator } from './periodisk-redovisning.validator';
+import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
+import { CustomError } from '@msfa-models/error/custom-error';
+
+@Component({
+ selector: 'msfa-periodisk-redovisning-form',
+ templateUrl: './periodisk-redovisning-form.component.html',
+ styleUrls: ['./periodisk-redovisning-form.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PeriodiskRedovisningFormComponent extends UnsubscribeDirective implements OnInit {
+ readonly PERIOD_FORM_NAME: PeriodiskRedovisningFormKeys = 'period';
+ readonly HAS_OFFERED_LANGUAGE_SUPPORT_FORM_NAME: PeriodiskRedovisningFormKeys = 'hasOfferedLanguageSupport';
+ readonly HAS_OFFERED_JOB_FORM_NAME: PeriodiskRedovisningFormKeys = 'hasOfferedJob';
+ readonly ACTIVITES_FORM_NAME: PeriodiskRedovisningFormKeys = 'activities';
+ readonly NO_ACTIVITIES_HAS_BEEN_CONDUCTED_FORM_NAME: PeriodiskRedovisningFormKeys = 'noActivitiesHasBeenConducted';
+
+ today = new Date();
+ shouldValidate$ = new BehaviorSubject(false);
+ confirmDialogIsOpen$ = new BehaviorSubject(false);
+ submittedDate$ = new BehaviorSubject(null);
+ submitIsLoading$ = new BehaviorSubject(false);
+ radiobuttonGroupDirection = RadiobuttonGroupDirection;
+ activitiesFormArrayMetadata: Activity[];
+ submitError$ = new BehaviorSubject(null);
+
+ genomforandeReferens$: Observable = this.activatedRoute.params.pipe(
+ map((params: Params) => +params.genomforandeReferens)
+ );
+ avrop$: Observable = this.genomforandeReferens$.pipe(
+ switchMap(genomforandeReferens =>
+ this.periodiskRedovisningFormService.fetchAvropInformation$(genomforandeReferens)
+ ),
+ shareReplay(1)
+ );
+
+ availableActivities$: Observable = this.gemensamPlaneringApiService.fetchActivities$();
+
+ periods$: Observable = this.avrop$.pipe(
+ map(avrop => extractAvropPeriods(avrop.startDate, avrop.endDate))
+ );
+ previousPeriod = dateToPeriodId(subMonths(new Date(), 1));
+ formGroup = new FormGroup(
+ {
+ [this.PERIOD_FORM_NAME]: new FormControl(this.previousPeriod, RequiredValidator()),
+ [this.HAS_OFFERED_LANGUAGE_SUPPORT_FORM_NAME]: new FormControl(null, RequiredValidator()),
+ [this.HAS_OFFERED_JOB_FORM_NAME]: new FormControl(null, RequiredValidator()),
+ [this.ACTIVITES_FORM_NAME]: new FormArray([]),
+ [this.NO_ACTIVITIES_HAS_BEEN_CONDUCTED_FORM_NAME]: new FormControl(),
+ },
+ [PeriodiskRedovisningValidator.periodiskRedovisningIsValid()]
+ );
+
+ formData$: Observable = this.formGroup
+ .valueChanges as Observable;
+
+ submitData$ = combineLatest([this.genomforandeReferens$, this.formData$]).pipe(
+ map(([genomforandeReferens, formData]) => this.formDataToSubmitData(genomforandeReferens, formData)),
+ shareReplay(1)
+ );
+
+ periodsToFormselectItems(periods: AvropPeriod[]): FormSelectItem[] {
+ return periods.map(period => ({
+ name: `${formatDate(period.startDate, 'sv-SE', { month: 'long', year: 'numeric' } as DateFormatOptions)}`,
+ value: period.periodId,
+ }));
+ }
+
+ get formErrors(): PeriodiskRedovisningFormErrors {
+ return this.formGroup.errors as PeriodiskRedovisningFormErrors;
+ }
+
+ constructor(
+ private periodiskRedovisningFormService: PeriodiskRedovisningFormService,
+ private activatedRoute: ActivatedRoute,
+ // TODO. GemensamPlaneringApiService is used for fetching activities. Replace with own service once ready
+ private gemensamPlaneringApiService: GemensamPlaneringApiService
+ ) {
+ super();
+
+ super.unsubscribeOnDestroy(
+ this.formGroup.valueChanges.subscribe(value => {
+ this.shouldValidate$.next(false);
+ })
+ );
+ }
+
+ get periodFormControl(): AbstractControl | undefined {
+ return this.formGroup.get(this.PERIOD_FORM_NAME);
+ }
+
+ get hasOfferedLanguageSupportFormControl(): AbstractControl | undefined {
+ return this.formGroup.get(this.HAS_OFFERED_LANGUAGE_SUPPORT_FORM_NAME);
+ }
+
+ get hasOfferedJobFormControl(): AbstractControl | undefined {
+ return this.formGroup.get(this.HAS_OFFERED_JOB_FORM_NAME);
+ }
+
+ get noActivitiesHasBeenConductedFormControl(): AbstractControl | undefined {
+ return this.formGroup.get(this.NO_ACTIVITIES_HAS_BEEN_CONDUCTED_FORM_NAME);
+ }
+
+ get activitiesFormArray(): FormArray {
+ return this.formGroup.get(this.ACTIVITES_FORM_NAME) as FormArray;
+ }
+
+ openConfirmDialog(): void {
+ this.shouldValidate$.next(true);
+
+ if (this.formGroup.invalid) {
+ return;
+ }
+ this.confirmDialogIsOpen$.next(true);
+ }
+
+ cancelConfirmDialog(): void {
+ this.confirmDialogIsOpen$.next(false);
+ this.submitError$.next(null);
+ }
+
+ submitAndCloseConfirmDialog(genomforandeReferens: number): void {
+ this.submitIsLoading$.next(true);
+ this.submitData$.pipe(take(1)).subscribe(submitData =>
+ this.periodiskRedovisningFormService.submitPeriodiskRedovisning$(submitData).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 };
+ },
+ })
+ );
+ }
+
+ formControlIsInvalid(formControl: AbstractControl): boolean {
+ return formControl.invalid && (formControl.touched || this.shouldValidate$.value);
+ }
+
+ private clearActivities(): void {
+ this.activitiesFormArray.clear();
+ this.activitiesFormArrayMetadata = [];
+ }
+
+ private addActivityToForm(activity: Activity): void {
+ // FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit
+ // It's important that the metadata is updated at the same time as the formArray to avoid sync problems
+ this.activitiesFormArrayMetadata.push(activity);
+
+ this.activitiesFormArray.push(
+ new FormGroup(
+ {
+ isSelected: new FormControl(),
+ performedRemotely: new FormControl(),
+ performedPhysically: new FormControl(),
+ },
+ PeriodiskRedovisningValidator.activityIsValid()
+ )
+ );
+ }
+
+ ngOnInit(): void {
+ this.availableActivities$.subscribe(activities => {
+ this.clearActivities();
+ activities.forEach(activity => this.addActivityToForm(activity));
+ });
+ }
+
+ private formDataToSubmitData(
+ genomforandeReferens: number,
+ formData: PeriodiskRedovisningFormData
+ ): PeriodiskRedovisningRequest {
+ const { period, hasOfferedJob, hasOfferedLanguageSupport } = formData;
+ const activities: PeriodiskRedovisningActivityRequest[] = formData.activities
+ .filter(activity => activity.isSelected)
+ .map(({ performedPhysically, performedRemotely }, index) => ({
+ id: this.activitiesFormArrayMetadata[index].id,
+ performedPhysically: performedPhysically ?? false,
+ performedRemotely: performedRemotely ?? false,
+ }));
+ return {
+ genomforandeReferens,
+ period,
+ hasOfferedJob,
+ hasOfferedLanguageSupport,
+ activities,
+ };
+ }
+
+ errorsToArray(errors: { [key: string]: string }): string[] {
+ return Object.values(errors);
+ }
+
+ activityLocationIsInvalid(activityFormGroup: AbstractControl): boolean {
+ const errors = activityFormGroup.errors as ActivityFormErrors;
+ return this.formControlIsInvalid(activityFormGroup) && !!errors.locationCheckboxes;
+ }
+
+ getActivityMetadata(activityId: number) {
+ return this.activitiesFormArrayMetadata.find(activity => activity.id === activityId);
+ }
+}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.model.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.model.ts
new file mode 100644
index 0000000..167f115
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.model.ts
@@ -0,0 +1,23 @@
+export interface PeriodiskRedovisningFormActivity {
+ isSelected: boolean;
+ performedRemotely: boolean;
+ performedPhysically: boolean;
+}
+
+export interface PeriodiskRedovisningFormData {
+ period: string;
+ hasOfferedLanguageSupport: boolean;
+ hasOfferedJob: boolean;
+ noActivitiesHasBeenConducted: boolean;
+ activities: PeriodiskRedovisningFormActivity[];
+}
+
+export type PeriodiskRedovisningFormKeys = keyof PeriodiskRedovisningFormData;
+
+export interface PeriodiskRedovisningFormErrors {
+ activitiesMismatch?: string;
+}
+
+export interface ActivityFormErrors {
+ locationCheckboxes?: string;
+}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.module.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.module.ts
new file mode 100644
index 0000000..94c4760
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.module.ts
@@ -0,0 +1,39 @@
+import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
+import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
+import { CommonModule } from '@angular/common';
+import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
+import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
+import { LoaderModule } from '@msfa-shared/components/loader/loader.module';
+import { ReportLayoutModule } from '../../../components/report-layout/report-layout.module';
+import { PeriodiskRedovisningFormComponent } from './periodisk-redovisning-form.component';
+import { PeriodiskRedovisningFormService } from './periodisk-redovisning-form.service';
+import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
+import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
+import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
+import { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
+
+@NgModule({
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ declarations: [PeriodiskRedovisningFormComponent],
+ imports: [
+ CommonModule,
+ RouterModule.forChild([{ path: '', component: PeriodiskRedovisningFormComponent }]),
+ LayoutModule,
+ ReactiveFormsModule,
+ ReportLayoutModule,
+ BackLinkModule,
+ LoaderModule,
+ DigiNgSkeletonBaseModule,
+ DigiNgDialogModule,
+ DigiNgFormSelectModule,
+ DigiNgFormRadiobuttonGroupModule,
+ DigiNgFormCheckboxModule,
+ HideTextModule,
+ ],
+ providers: [PeriodiskRedovisningFormService],
+ exports: [PeriodiskRedovisningFormComponent],
+})
+export class PeriodiskRedovisningFormModule {}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.service.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.service.ts
new file mode 100644
index 0000000..b2bc273
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning-form.service.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { Avrop } from '@msfa-models/avrop.model';
+import { PeriodiskRedovisning } from '@msfa-models/periodisk-redovisning.model';
+import { DeltagareApiService } from '@msfa-services/api/deltagare.api.service';
+import { PeriodiskRedovisningApiService } from '@msfa-services/api/periodisk-redovisning.api.service';
+import { Observable } from 'rxjs';
+import { PeriodiskRedovisningRequest } from '@msfa-models/api/periodisk-redovisning.request.model';
+
+@Injectable()
+export class PeriodiskRedovisningFormService {
+ constructor(
+ private periodiskRedovisningApiService: PeriodiskRedovisningApiService,
+ private deltagareApiService: DeltagareApiService
+ ) {}
+
+ fetchPeriodiskRedovisning$(
+ periodStart: string,
+ periodEnd: string,
+ genomforandeReferens: number
+ ): Observable {
+ return this.periodiskRedovisningApiService.fetchPeriodiskRedovisning$(periodStart, periodEnd, genomforandeReferens);
+ }
+
+ fetchAvropInformation$(genomforandeReferens: number): Observable {
+ return this.deltagareApiService.fetchAvropInformation$(genomforandeReferens);
+ }
+
+ submitPeriodiskRedovisning$(submitData: PeriodiskRedovisningRequest): Observable {
+ return this.periodiskRedovisningApiService.postPeriodiskRedovisning$(submitData);
+ }
+}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning.validator.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning.validator.ts
new file mode 100644
index 0000000..570ac94
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/periodisk-redovisning-form/periodisk-redovisning.validator.ts
@@ -0,0 +1,51 @@
+import { AbstractControl, ValidatorFn } from '@angular/forms';
+import {
+ ActivityFormErrors,
+ PeriodiskRedovisningFormActivity,
+ PeriodiskRedovisningFormData,
+ PeriodiskRedovisningFormErrors,
+} from './periodisk-redovisning-form.model';
+
+export class PeriodiskRedovisningValidator {
+ static periodiskRedovisningIsValid(): ValidatorFn {
+ return (c: AbstractControl): PeriodiskRedovisningFormErrors => {
+ let errors: PeriodiskRedovisningFormErrors;
+
+ const { noActivitiesHasBeenConducted, activities } = c.value as PeriodiskRedovisningFormData;
+ if (noActivitiesHasBeenConducted && activities.filter(activity => activity.isSelected).length > 0) {
+ errors = {
+ ...errors,
+ activitiesMismatch:
+ 'Ifall kryssrutan "Deltagaren har inte deltagit i några aktiviteter denna period" är förbockad får inga aktiviteter vara valda. ',
+ };
+ } else if (
+ noActivitiesHasBeenConducted !== true &&
+ activities.filter(activity => activity.isSelected).length === 0
+ ) {
+ errors = {
+ ...errors,
+ activitiesMismatch:
+ 'Ifall inga aktiviteter är valda ska "Deltagaren har inte deltagit i några aktiviteter denna period" vara förbockad. ',
+ };
+ }
+
+ return errors;
+ };
+ }
+
+ static activityIsValid(): ValidatorFn {
+ return (c: AbstractControl): ActivityFormErrors => {
+ let errors: ActivityFormErrors;
+
+ const { performedRemotely, performedPhysically, isSelected } = c.value as PeriodiskRedovisningFormActivity;
+ if (isSelected && !performedPhysically && !performedRemotely) {
+ errors = {
+ ...errors,
+ locationCheckboxes: 'Minst en plats måste väljas',
+ };
+ }
+
+ return errors;
+ };
+ }
+}
diff --git a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/signal-form/signal-form.component.ts b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/signal-form/signal-form.component.ts
index 3a5ecfe..101bdef 100644
--- a/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/signal-form/signal-form.component.ts
+++ b/apps/mina-sidor-fa/src/app/pages/deltagare/pages/deltagare-details/pages/report-forms/signal-form/signal-form.component.ts
@@ -7,7 +7,7 @@ import { SignalRequest } from '@msfa-models/api/signal.request.model';
import { Avrop } from '@msfa-models/avrop.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { Signal } from '@msfa-models/signal.model';
-import { dateToIsoString } from '@msfa-utils/format-to-date.util';
+import { formatDate } from '@msfa-utils/format-to-date.util';
import { add } from 'date-fns';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
@@ -125,7 +125,7 @@ export class SignalFormComponent {
typ: type,
omfattning,
omfattning_procent: omfattning === 'deltid' ? percent : null,
- startdatum: dateToIsoString(startDate),
+ startdatum: formatDate(startDate),
};
this.signalFormService
diff --git a/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts b/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts
index 6c3093f..0cf775f 100644
--- a/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts
+++ b/apps/mina-sidor-fa/src/app/shared/enums/feature.enum.ts
@@ -1,14 +1,17 @@
export enum Feature {
- AVROP,
- DELTAGARE,
- ADMINISTRATION,
- MY_ACCOUNT,
- MY_ORGANIZATION,
+ LOGGING,
RELEASES,
MOCK_LOGIN,
VERSION_INFO,
+
+ AVROP,
+ DELTAGARE,
+ DELTAGARE_SENSITIVE_INFORMATION,
+ ADMINISTRATION,
+ MY_ACCOUNT,
+ MY_ORGANIZATION,
ACCESSIBILITY_REPORT,
REPORTING,
- SENSITIVE_INFORMATION,
- LOGGING,
+ REPORTING_SIGNAL,
+ REPORTING_PERIODISK_REDOVISNING,
}
diff --git a/apps/mina-sidor-fa/src/app/shared/models/api/periodisk-redovisning.request.model.ts b/apps/mina-sidor-fa/src/app/shared/models/api/periodisk-redovisning.request.model.ts
new file mode 100644
index 0000000..1cda0f3
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/shared/models/api/periodisk-redovisning.request.model.ts
@@ -0,0 +1,13 @@
+export interface PeriodiskRedovisningActivityRequest {
+ id: number;
+ performedRemotely: boolean;
+ performedPhysically: boolean;
+}
+
+export interface PeriodiskRedovisningRequest {
+ genomforandeReferens: number;
+ period: string;
+ hasOfferedLanguageSupport: boolean;
+ hasOfferedJob: boolean;
+ activities: PeriodiskRedovisningActivityRequest[];
+}
diff --git a/apps/mina-sidor-fa/src/app/shared/models/api/periodisk-redovisning.response.model.ts b/apps/mina-sidor-fa/src/app/shared/models/api/periodisk-redovisning.response.model.ts
new file mode 100644
index 0000000..0807691
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/shared/models/api/periodisk-redovisning.response.model.ts
@@ -0,0 +1,39 @@
+export interface PeriodiskRedovisningActivityResponse {
+ activityId: number;
+ activityName: string;
+ performedRemotely: boolean;
+}
+
+export interface PeriodiskRedovisningResponse {
+ genomforandeReferens: number;
+ period: string;
+ hasOfferedLanguageSupport: boolean;
+ hasOfferedJob: boolean;
+ activities: PeriodiskRedovisningActivityResponse[];
+}
+
+export function mockOnePeriodiskRedovisningResponse(): PeriodiskRedovisningResponse {
+ return {
+ genomforandeReferens: 100003857,
+ hasOfferedJob: false,
+ hasOfferedLanguageSupport: true,
+ period: '2021-10',
+ activities: [
+ {
+ activityId: 24,
+ activityName: 'Aktivitet 1',
+ performedRemotely: false,
+ },
+ {
+ activityId: 19,
+ activityName: 'Aktivitet 5',
+ performedRemotely: true,
+ },
+ {
+ activityId: 31,
+ activityName: 'Aktivitet 5',
+ performedRemotely: true,
+ },
+ ],
+ };
+}
diff --git a/apps/mina-sidor-fa/src/app/shared/models/avrop-period.model.ts b/apps/mina-sidor-fa/src/app/shared/models/avrop-period.model.ts
new file mode 100644
index 0000000..a5d7713
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/shared/models/avrop-period.model.ts
@@ -0,0 +1,5 @@
+export interface AvropPeriod {
+ periodId: string;
+ startDate: Date;
+ endDate: Date;
+}
diff --git a/apps/mina-sidor-fa/src/app/shared/models/periodisk-redovisning.model.ts b/apps/mina-sidor-fa/src/app/shared/models/periodisk-redovisning.model.ts
new file mode 100644
index 0000000..d0a18b2
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/shared/models/periodisk-redovisning.model.ts
@@ -0,0 +1,27 @@
+import { PeriodiskRedovisningResponse } from './api/periodisk-redovisning.response.model';
+
+export interface PeriodiskRedovisningActivity {
+ activityId: number;
+ activityName: string;
+ performedRemotely: boolean;
+}
+
+export interface PeriodiskRedovisning {
+ genomforandeReferens: number;
+ period: string;
+ hasOfferedLanguageSupport: boolean;
+ hasOfferedJob: boolean;
+ activities: PeriodiskRedovisningActivity[];
+}
+
+export function mapResponseToPeriodiskRedovisning(data: PeriodiskRedovisningResponse): PeriodiskRedovisning {
+ const { genomforandeReferens, period, hasOfferedLanguageSupport, hasOfferedJob, activities } = data;
+
+ return {
+ genomforandeReferens,
+ period,
+ hasOfferedJob,
+ hasOfferedLanguageSupport,
+ activities,
+ };
+}
diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/periodisk-redovisning.api.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/periodisk-redovisning.api.service.ts
new file mode 100644
index 0000000..22e2a14
--- /dev/null
+++ b/apps/mina-sidor-fa/src/app/shared/services/api/periodisk-redovisning.api.service.ts
@@ -0,0 +1,57 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { ErrorType } from '@msfa-enums/error-type.enum';
+import { environment } from '@msfa-environment';
+import { Params } from '@msfa-models/api/params.model';
+import { PeriodiskRedovisningRequest } from '@msfa-models/api/periodisk-redovisning.request.model';
+import {
+ mockOnePeriodiskRedovisningResponse,
+ PeriodiskRedovisningResponse,
+} from '@msfa-models/api/periodisk-redovisning.response.model';
+import { CustomError } from '@msfa-models/error/custom-error';
+import { mapResponseToPeriodiskRedovisning, PeriodiskRedovisning } from '@msfa-models/periodisk-redovisning.model';
+import { Observable, of } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PeriodiskRedovisningApiService {
+ private _apiBaseUrl = `${environment.api.url}/rapporter/periodisk-redovisining`;
+
+ constructor(private httpClient: HttpClient) {}
+
+ public fetchPeriodiskRedovisning$(
+ periodStart: string,
+ periodEnd: string,
+ genomforandeReferens: number
+ ): Observable {
+ const params: Params = {
+ genomforandeReferens: genomforandeReferens.toString(),
+ periodStart,
+ periodEnd,
+ };
+ return of(mapResponseToPeriodiskRedovisning(mockOnePeriodiskRedovisningResponse()));
+ // return this.httpClient
+ // .get<{ data: PeriodiskRedovisningResponse }>(`${this._apiBaseUrl}`, { params })
+ // .pipe(map(({ data }) => (data ? mapResponseToPeriodiskRedovisning(data) : null)));
+ }
+
+ public fetchAllPeriodiskaRedovisningar$(genomforandeReferens: number): Observable {
+ return this.httpClient
+ .get<{ data: PeriodiskRedovisningResponse[] }>(`${this._apiBaseUrl}/${genomforandeReferens}`)
+ .pipe(map(({ data }) => data.map(pr => mapResponseToPeriodiskRedovisning(pr))));
+ }
+
+ public postPeriodiskRedovisning$(requestData: PeriodiskRedovisningRequest): Observable {
+ return this.httpClient.post(`${this._apiBaseUrl}`, requestData).pipe(
+ catchError((error: Error) => {
+ throw new CustomError({
+ error,
+ message: `Kunde inte spara Periodisk redovisning.\n\n${error.message}`,
+ type: ErrorType.API,
+ });
+ })
+ );
+ }
+}
diff --git a/apps/mina-sidor-fa/src/app/shared/utils/format-to-date.util.ts b/apps/mina-sidor-fa/src/app/shared/utils/format-to-date.util.ts
index c5c97c1..705b1cf 100644
--- a/apps/mina-sidor-fa/src/app/shared/utils/format-to-date.util.ts
+++ b/apps/mina-sidor-fa/src/app/shared/utils/format-to-date.util.ts
@@ -1,16 +1,5 @@
import { DateFormatOptions } from '@msfa-models/date-format-options.model';
-// Takes either 6 or 8 characters string (YYYYMMDD) and formats it to ISO standard (YYYY-MM-DD).
-export function formatToIsoString(date: string): string {
- if (date.length === 6) {
- return `${date.substring(0, 4)}-${date.substring(4)}`;
- } else if (date.length === 8) {
- return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6)}`;
- }
-
- return date;
-}
-
export function formatToDate(date: string): Date {
const year = date.substring(0, 4);
const month = date.substring(4, 6) || '01';
@@ -19,8 +8,8 @@ export function formatToDate(date: string): Date {
return new Date(`${year}-${month}-${day}`);
}
-export function dateToIsoString(date: Date, locale: string = 'sv-SE'): string {
- const formatOptions: DateFormatOptions = {
+export function formatDate(date: Date, locale: string = 'sv-SE', options?: DateFormatOptions): string {
+ const formatOptions: DateFormatOptions = options || {
year: 'numeric',
month: 'numeric',
day: 'numeric',
diff --git a/apps/mina-sidor-fa/src/app/shared/utils/validators/required.validator.ts b/apps/mina-sidor-fa/src/app/shared/utils/validators/required.validator.ts
index 995711f..5983d82 100644
--- a/apps/mina-sidor-fa/src/app/shared/utils/validators/required.validator.ts
+++ b/apps/mina-sidor-fa/src/app/shared/utils/validators/required.validator.ts
@@ -4,7 +4,11 @@ import { ValidationError } from '@msfa-models/validation-error.model';
export function RequiredValidator(message = 'Fältet är obligatoriskt'): ValidatorFn {
return (control: AbstractControl): ValidationError => {
if (control) {
- if (!control.value || (Array.isArray(control.value) && !control.value.length)) {
+ if (
+ control.value === null ||
+ control.value === undefined ||
+ (Array.isArray(control.value) && !control.value.length)
+ ) {
return { required: message };
}
}
diff --git a/apps/mina-sidor-fa/src/environments/active-features.ts b/apps/mina-sidor-fa/src/environments/active-features.ts
index 18c9646..597bee8 100644
--- a/apps/mina-sidor-fa/src/environments/active-features.ts
+++ b/apps/mina-sidor-fa/src/environments/active-features.ts
@@ -20,7 +20,8 @@ export const ACTIVE_FEATURES_TEST: Feature[] = [
Feature.AVROP,
Feature.REPORTING,
Feature.LOGGING,
- Feature.SENSITIVE_INFORMATION,
+ Feature.DELTAGARE_SENSITIVE_INFORMATION,
Feature.RELEASES,
Feature.VERSION_INFO,
+ Feature.REPORTING_PERIODISK_REDOVISNING,
];
diff --git a/package-lock.json b/package-lock.json
index adf5465..76a614a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,12 @@
{
"name": "mina-sidor-fa-web",
- "version": "2.1.0",
+ "version": "2.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "version": "2.1.0",
+ "name": "mina-sidor-fa-web",
+ "version": "2.2.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -23742,6 +23743,11 @@
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
+ "bin": {
+ "sshpk-conv": "bin/sshpk-conv",
+ "sshpk-sign": "bin/sshpk-sign",
+ "sshpk-verify": "bin/sshpk-verify"
+ },
"engines": {
"node": ">=0.10.0"
}
@@ -40478,7 +40484,9 @@
"resolved": "http://nexus.arbetsformedlingen.se/repository/npm/ajv-formats/-/ajv-formats-2.1.0.tgz",
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
"peer": true,
- "requires": {}
+ "requires": {
+ "ajv": "^8.0.0"
+ }
},
"alphanum-sort": {
"version": "1.0.2",