refactor(project): Renamed all instances of dafa to msfa or mina-sidor-fa. (TV-379)
Squashed commit of the following: commit d3f52ff6876f6e246c7d3c188e56cc2370289341 Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Tue Aug 17 14:10:38 2021 +0200 Renamed all dafa instances to msfa
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AvropService } from './avrop.service';
|
||||
|
||||
describe('AvropServiceService', () => {
|
||||
let service: AvropService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AvropService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
98
apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.html
Normal file
98
apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<msfa-layout>
|
||||
<section class="call-off" *ngIf="currentStep$ | async; let currentStep; else: loadingRef">
|
||||
<digi-typography>
|
||||
<h2>Välj deltagare att tilldela</h2>
|
||||
<p>Steg {{ currentStep }} av {{ steps }}:</p>
|
||||
</digi-typography>
|
||||
<digi-ng-progress-progressbar [afSteps]="steps" afAriaLabel="An aria label" [afActiveStep]="currentStep">
|
||||
</digi-ng-progress-progressbar>
|
||||
|
||||
<div>
|
||||
<ng-container *ngIf="currentStep == 4">
|
||||
<h2>Avropet är sparat</h2>
|
||||
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="goToStep1()">
|
||||
Tillbaka till nya deltagare
|
||||
</digi-button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentStep == 1">
|
||||
<msfa-avrop-filters></msfa-avrop-filters>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentStep == 3">
|
||||
<h2>Vänligen bekräfta</h2>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentStep < 4">
|
||||
<msfa-avrop-table
|
||||
[selectableDeltagareList]="selectableDeltagareList$ | async"
|
||||
[selectedDeltagareListInput]="selectedDeltagareList$ | async"
|
||||
[isLocked]="deltagareListIsLocked$ | async"
|
||||
(changedSelectedDeltagareList)="updateSelectedDeltagareList($event)"
|
||||
[handledare]="selectedHandledare$ | async"
|
||||
[handledareConfirmed]="handledareConfirmed$ | async"
|
||||
></msfa-avrop-table>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentStep == 1">
|
||||
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="lockSelectedDeltagare()">
|
||||
Lås deltagare
|
||||
</digi-button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentStep == 2">
|
||||
<h2>Välj handledare</h2>
|
||||
<ng-container *ngIf="selectableHandledareList$ | async; let selectableHandledareList; else loadingRefSmall">
|
||||
<select
|
||||
[value]="(selectedHandledare$ | async)?.id ? (selectedHandledare$ | async)?.id : ''"
|
||||
(change)="changeHandledare($event)"
|
||||
>
|
||||
<option disabled value="">Välj handledare</option>
|
||||
|
||||
<option *ngFor="let selectableHandledare of selectableHandledareList" [value]="selectableHandledare?.id">
|
||||
{{ selectableHandledare?.fullName }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<span *ngIf="selectableHandledareList.length === 0"
|
||||
>Inga handledare har behörighet till alla markerade deltagare</span
|
||||
>
|
||||
</ng-container>
|
||||
|
||||
<br /><br />
|
||||
<digi-button
|
||||
af-variation="secondary"
|
||||
af-size="m"
|
||||
class="employee-form__read-more"
|
||||
(afOnClick)="unlockSelectedDeltagare()"
|
||||
>
|
||||
Tillbaka
|
||||
</digi-button>
|
||||
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="confirmHandledare()">
|
||||
Tilldela
|
||||
</digi-button>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="currentStep == 3">
|
||||
<br /><br />
|
||||
<digi-button
|
||||
af-variation="secondary"
|
||||
af-size="m"
|
||||
class="employee-form__read-more"
|
||||
(afOnClick)="unconfirmHandledare()"
|
||||
>
|
||||
Tillbaka
|
||||
</digi-button>
|
||||
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="save()"> Spara avrop </digi-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #loadingRef>
|
||||
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #loadingRefSmall>
|
||||
<digi-icon-spinner af-title="Laddar innehåll"></digi-icon-spinner>
|
||||
</ng-template>
|
||||
|
||||
<hr />
|
||||
</msfa-layout>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AvropComponent } from './avrop.component';
|
||||
|
||||
describe('CallOffComponent', () => {
|
||||
let component: AvropComponent;
|
||||
let fixture: ComponentFixture<AvropComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [AvropComponent],
|
||||
imports: [RouterTestingModule],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvropComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
63
apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.ts
Normal file
63
apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Avrop } from '@msfa-models/avrop.model';
|
||||
import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AvropService } from './avrop.service';
|
||||
import { HandledareAvrop } from './models/handledare-avrop';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-avrop',
|
||||
templateUrl: './avrop.component.html',
|
||||
styleUrls: ['./avrop.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AvropComponent {
|
||||
steps = 3;
|
||||
|
||||
currentStep$ = this.avropService.currentStep$;
|
||||
|
||||
selectedUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this.avropService
|
||||
.selectedUtforandeVerksamheter$;
|
||||
selectableDeltagareList$: Observable<Avrop[]> = this.avropService.selectableDeltagareList$;
|
||||
selectedDeltagareList$: Observable<Avrop[]> = this.avropService.selectedDeltagareList$;
|
||||
selectableHandledareList$: Observable<HandledareAvrop[]> = this.avropService.selectableHandledareList$;
|
||||
selectedHandledare$: Observable<HandledareAvrop> = this.avropService.selectedHandledare$;
|
||||
deltagareListIsLocked$: Observable<boolean> = this.avropService.deltagareListIsLocked$;
|
||||
handledareConfirmed$: Observable<boolean> = this.avropService.handledareIsConfirmed$;
|
||||
|
||||
constructor(private avropService: AvropService) {}
|
||||
|
||||
updateSelectedDeltagareList(deltagareList: Avrop[]): void {
|
||||
this.avropService.setSelectedDeltagare(deltagareList);
|
||||
}
|
||||
|
||||
lockSelectedDeltagare(): void {
|
||||
this.avropService.lockSelectedDeltagare();
|
||||
}
|
||||
|
||||
unlockSelectedDeltagare(): void {
|
||||
this.avropService.unlockSelectedDeltagare();
|
||||
}
|
||||
|
||||
confirmHandledare(): void {
|
||||
this.avropService.confirmHandledare();
|
||||
}
|
||||
|
||||
unconfirmHandledare(): void {
|
||||
this.avropService.unconfirmHandledare();
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
return this.avropService.save();
|
||||
}
|
||||
|
||||
changeHandledare(newHandledare: { target: HTMLInputElement }): void {
|
||||
const handledareId = newHandledare.target.value;
|
||||
|
||||
this.avropService.setHandledareState(handledareId);
|
||||
}
|
||||
|
||||
goToStep1(): void {
|
||||
this.avropService.goToStep1();
|
||||
}
|
||||
}
|
||||
30
apps/mina-sidor-fa/src/app/pages/avrop/avrop.module.ts
Normal file
30
apps/mina-sidor-fa/src/app/pages/avrop/avrop.module.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar';
|
||||
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||
import { AvropComponent } from './avrop.component';
|
||||
import { AvropFiltersComponent } from './components/avrop-filters/avrop-filters.component';
|
||||
import { TemporaryFilterComponent } from './components/avrop-filters/temporary-filter/temporary-filter.component';
|
||||
import { AvropTableRowComponent } from './components/avrop-table/avrop-table-row/avrop-table-row.component';
|
||||
import { AvropTableComponent } from './components/avrop-table/avrop-table.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [
|
||||
AvropComponent,
|
||||
AvropFiltersComponent,
|
||||
AvropTableComponent,
|
||||
AvropTableRowComponent,
|
||||
TemporaryFilterComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{ path: '', component: AvropComponent }]),
|
||||
LayoutModule,
|
||||
DigiNgProgressProgressbarModule,
|
||||
DigiNgSkeletonBaseModule,
|
||||
],
|
||||
})
|
||||
export class AvropModule {}
|
||||
194
apps/mina-sidor-fa/src/app/pages/avrop/avrop.service.ts
Normal file
194
apps/mina-sidor-fa/src/app/pages/avrop/avrop.service.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Avrop } from '@msfa-models/avrop.model';
|
||||
import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option';
|
||||
import { AvropApiService } from '@msfa-services/api/avrop-api.service';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { HandledareAvrop } from './models/handledare-avrop';
|
||||
|
||||
type Step = 1 | 2 | 3 | 4;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AvropService {
|
||||
private _selectedTjanster$ = new BehaviorSubject<MultiselectFilterOption[]>(null);
|
||||
private _selectedUtforandeVerksamheter$ = new BehaviorSubject<MultiselectFilterOption[]>(null);
|
||||
private _selectedKommuner$ = new BehaviorSubject<MultiselectFilterOption[]>(null);
|
||||
private _selectedDeltagareList$ = new BehaviorSubject<Avrop[]>([]);
|
||||
private _deltagareListIsLocked$ = new BehaviorSubject<boolean>(null);
|
||||
private _selectedHandledare$ = new BehaviorSubject<HandledareAvrop>(null);
|
||||
private _handledareIsConfirmed$ = new BehaviorSubject<boolean>(false);
|
||||
private _avropIsSaved$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
selectedTjanster$: Observable<MultiselectFilterOption[]> = this._selectedTjanster$.asObservable();
|
||||
selectedUtforandeVerksamheter$: Observable<
|
||||
MultiselectFilterOption[]
|
||||
> = this._selectedUtforandeVerksamheter$.asObservable();
|
||||
selectedKommuner$: Observable<MultiselectFilterOption[]> = this._selectedKommuner$.asObservable();
|
||||
|
||||
selectableDeltagareList$: Observable<Avrop[]> = combineLatest([
|
||||
this.selectedTjanster$,
|
||||
this.selectedUtforandeVerksamheter$,
|
||||
this.selectedKommuner$,
|
||||
]).pipe(
|
||||
switchMap(([selectedTjanster, selectedUtforandeVerksamheter, selectedKommuner]) =>
|
||||
this.avropApiService.getNyaAvrop$(selectedTjanster, selectedKommuner, selectedUtforandeVerksamheter)
|
||||
)
|
||||
);
|
||||
|
||||
selectableTjanster$: Observable<MultiselectFilterOption[]> = combineLatest([
|
||||
this.selectedUtforandeVerksamheter$,
|
||||
this.selectedKommuner$,
|
||||
]).pipe(
|
||||
switchMap(([selectedUtforandeVerksamheter, selectedKommuner]) =>
|
||||
this.avropApiService.getSelectableTjanster$(selectedKommuner, selectedUtforandeVerksamheter)
|
||||
)
|
||||
);
|
||||
selectableUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = combineLatest([
|
||||
this.selectedTjanster$,
|
||||
this.selectedKommuner$,
|
||||
]).pipe(
|
||||
switchMap(([selectedTjanster, selectedKommuner]) =>
|
||||
this.avropApiService.getSelectableUtforandeVerksamheter$(selectedTjanster, selectedKommuner)
|
||||
)
|
||||
);
|
||||
selectableKommuner$: Observable<MultiselectFilterOption[]> = combineLatest([
|
||||
this.selectedTjanster$,
|
||||
this.selectedUtforandeVerksamheter$,
|
||||
]).pipe(
|
||||
switchMap(([selectedTjanster, selectedUtforandeVerksamheter]) =>
|
||||
this.avropApiService.getSelectableKommuner$(selectedTjanster, selectedUtforandeVerksamheter)
|
||||
)
|
||||
);
|
||||
|
||||
selectedDeltagareList$: Observable<Avrop[]> = this._selectedDeltagareList$.asObservable();
|
||||
|
||||
deltagareListIsLocked$: Observable<boolean> = this._deltagareListIsLocked$.asObservable();
|
||||
lockedDeltagareList$: Observable<Avrop[]> = combineLatest([
|
||||
this.selectedDeltagareList$,
|
||||
this.deltagareListIsLocked$,
|
||||
]).pipe(map(([selectedDeltagareList, isLocked]) => (isLocked ? selectedDeltagareList : null)));
|
||||
|
||||
selectableHandledareList$: Observable<HandledareAvrop[]> = this.lockedDeltagareList$.pipe(
|
||||
switchMap(lockedDeltagare => this.avropApiService.getSelectableHandledare$(lockedDeltagare))
|
||||
);
|
||||
|
||||
selectedHandledare$: Observable<HandledareAvrop> = this._selectedHandledare$.asObservable();
|
||||
|
||||
handledareIsConfirmed$: Observable<boolean> = this._handledareIsConfirmed$.asObservable();
|
||||
avropIsSaved$: Observable<boolean> = this._handledareIsConfirmed$.asObservable();
|
||||
|
||||
currentStep$: Observable<Step> = combineLatest([
|
||||
this.handledareIsConfirmed$,
|
||||
this._deltagareListIsLocked$,
|
||||
this.avropIsSaved$,
|
||||
]).pipe(
|
||||
map(([confirmedHandledare, lockedDeltagareList, avropIsSaved]) =>
|
||||
AvropService.calculateStep(confirmedHandledare, lockedDeltagareList, avropIsSaved)
|
||||
)
|
||||
);
|
||||
|
||||
private static calculateStep(
|
||||
confirmedHandledare: boolean,
|
||||
deltagareListIsLocked: boolean,
|
||||
avropIsSaved: boolean
|
||||
): Step {
|
||||
if (avropIsSaved && confirmedHandledare && deltagareListIsLocked) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (confirmedHandledare && deltagareListIsLocked) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (deltagareListIsLocked) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
setSelectedDeltagare(deltagare: Avrop[]): void {
|
||||
this._selectedDeltagareList$.next(deltagare);
|
||||
}
|
||||
|
||||
constructor(private avropApiService: AvropApiService) {}
|
||||
|
||||
lockSelectedDeltagare(): void {
|
||||
if ((this._selectedDeltagareList$?.value?.length ?? -1) <= 0) {
|
||||
throw new Error('För att låsa deltagare behöver några ha markerats först.');
|
||||
}
|
||||
this._deltagareListIsLocked$.next(true);
|
||||
}
|
||||
|
||||
unlockSelectedDeltagare(): void {
|
||||
this._deltagareListIsLocked$.next(false);
|
||||
}
|
||||
|
||||
confirmHandledare(): void {
|
||||
if (!this._selectedHandledare$?.value) {
|
||||
throw new Error('För att kunna tilldela behövs en handledare väljas först.');
|
||||
}
|
||||
this._handledareIsConfirmed$.next(true);
|
||||
}
|
||||
|
||||
unconfirmHandledare(): void {
|
||||
this._handledareIsConfirmed$.next(false);
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
if (!this._handledareIsConfirmed$) {
|
||||
throw new Error('Handledaren måste bekräftas innan avropet kan sparas');
|
||||
}
|
||||
|
||||
if (!this._deltagareListIsLocked$) {
|
||||
throw new Error('Deltagarlistan måste låsas innan avropet kan sparas');
|
||||
}
|
||||
|
||||
await this.avropApiService.tilldelaHandledare(this._selectedDeltagareList$.value, this._selectedHandledare$.value);
|
||||
this._avropIsSaved$.next(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setHandledareState(handledareId: string): void {
|
||||
this.selectableHandledareList$.pipe(first()).subscribe(handledareList => {
|
||||
this._selectedHandledare$.next(handledareList.find(handledare => handledare.id === handledareId));
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedTjanster(selectedFilterOptions: MultiselectFilterOption[]): void {
|
||||
this._selectedTjanster$.next(selectedFilterOptions);
|
||||
}
|
||||
|
||||
setSelectedUtforandeVerksamheter(selectedFilterOptions: MultiselectFilterOption[]): void {
|
||||
this._selectedUtforandeVerksamheter$.next(selectedFilterOptions);
|
||||
}
|
||||
|
||||
setSelectedKommuner(selectedFilterOptions: MultiselectFilterOption[]): void {
|
||||
this._selectedKommuner$.next(selectedFilterOptions);
|
||||
}
|
||||
|
||||
goToStep1(): void {
|
||||
this._selectedHandledare$.next(null);
|
||||
this._selectedDeltagareList$.next([]);
|
||||
this._deltagareListIsLocked$.next(false);
|
||||
this._handledareIsConfirmed$.next(false);
|
||||
}
|
||||
|
||||
removeKommun(kommunToRemove: MultiselectFilterOption) {
|
||||
this.setSelectedKommuner(this._selectedKommuner$.value.filter(selectedKommun => selectedKommun !== kommunToRemove));
|
||||
}
|
||||
|
||||
removeUtforandeVerksamhet(utforandeVerksamhetToRemove: MultiselectFilterOption) {
|
||||
this.setSelectedUtforandeVerksamheter(
|
||||
this._selectedUtforandeVerksamheter$.value.filter(
|
||||
selectedUtforandeVerksamhet => selectedUtforandeVerksamhet !== utforandeVerksamhetToRemove
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
removeTjanst(tjanstToRemove: MultiselectFilterOption) {
|
||||
this.setSelectedTjanster(this._selectedTjanster$.value.filter(selectedTjanst => selectedTjanst !== tjanstToRemove));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<div style="display: flex">
|
||||
<ng-container *ngIf="selectableTjanster$ | async; let selectableTjanster; else loadingRef">
|
||||
<msfa-temporary-filter
|
||||
[filterLabel]="'Tjänster'"
|
||||
[filterOptions]="selectableTjanster"
|
||||
[selectedOptions]="selectedTjanster$ | async"
|
||||
(selectedOptionsChange)="updateSelectedTjanster($event)"
|
||||
></msfa-temporary-filter>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectableUtforandeVerksamheter$ | async; let selectableUtforandeVerksamheter; else loadingRef">
|
||||
<msfa-temporary-filter
|
||||
[filterLabel]="'Utförande verksamheter'"
|
||||
[filterOptions]="selectableUtforandeVerksamheter"
|
||||
[selectedOptions]="selectedUtforandeVerksamheter$ | async"
|
||||
(selectedOptionsChange)="updateSelectedUtforandeVerksamheter($event)"
|
||||
></msfa-temporary-filter>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectableKommuner$ | async; let selectableKommuner; else loadingRef">
|
||||
<msfa-temporary-filter
|
||||
[filterLabel]="'Kommuner'"
|
||||
[filterOptions]="selectableKommuner"
|
||||
[selectedOptions]="selectedKommuner$ | async"
|
||||
(selectedOptionsChange)="updateSelectedKommuner($event)"
|
||||
></msfa-temporary-filter>
|
||||
</ng-container>
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="avrop-filters__tags">
|
||||
<div class="avrop-filters__tag" *ngFor="let kommun of selectedKommuner$ | async">
|
||||
<digi-tag [afText]="kommun.label" (click)="removeKommun(kommun)" af-no-icon="false" af-size="s"></digi-tag>
|
||||
</div>
|
||||
<div class="avrop-filters__tag" *ngFor="let kommun of selectedUtforandeVerksamheter$ | async">
|
||||
<digi-tag
|
||||
[afText]="kommun.label"
|
||||
(click)="removeUtforandeVerksamhet(kommun)"
|
||||
af-no-icon="false"
|
||||
af-size="s"
|
||||
></digi-tag>
|
||||
</div>
|
||||
<div class="avrop-filters__tag" *ngFor="let kommun of selectedTjanster$ | async">
|
||||
<digi-tag [afText]="kommun.label" (click)="removeTjanst(kommun)" af-no-icon="false" af-size="s"></digi-tag>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #loadingRef>
|
||||
<div class="avrop-filters__loading-spinner">
|
||||
<digi-icon-spinner af-title="Laddar innehåll"></digi-icon-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,17 @@
|
||||
@import 'variables/gutters';
|
||||
|
||||
.avrop-filters {
|
||||
&__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
display: inline-block;
|
||||
margin-right: $digi--layout--gutter--s;
|
||||
}
|
||||
&__loading-spinner {
|
||||
margin-right: $digi--layout--gutter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
|
||||
import { AvropFiltersComponent } from './avrop-filters.component';
|
||||
import { TemporaryFilterComponent } from './temporary-filter/temporary-filter.component';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { AvropService } from '../../avrop.service';
|
||||
import { of } from 'rxjs';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('AvropFiltersComponent', () => {
|
||||
let component: AvropFiltersComponent;
|
||||
let fixture: ComponentFixture<AvropFiltersComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [AvropFiltersComponent, TemporaryFilterComponent],
|
||||
providers: [AvropService],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvropFiltersComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show 1 tag if selectedKommuner$ is an observable with one value', () => {
|
||||
component.selectedKommuner$ = of([{ id: '1', label: 'Stockholm', count: 1 }]);
|
||||
fixture.detectChanges();
|
||||
const tags = fixture.debugElement.queryAll(By.css('.avrop-filters--tag'));
|
||||
expect(tags.length).toBe(1);
|
||||
});
|
||||
|
||||
it('clicking a kommun-tag should trigger removeKommun()', fakeAsync(() => {
|
||||
jest.spyOn(component, 'removeKommun').mockReturnThis();
|
||||
component.selectedKommuner$ = of([{ id: '1', label: 'Stockholm', count: 1 }]);
|
||||
fixture.detectChanges();
|
||||
const tags = fixture.debugElement.query(By.css('digi-tag'));
|
||||
tags.nativeElement.click();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
expect(component.removeKommun).toHaveBeenCalled();
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('should show loading spinners when filters are loading', () => {
|
||||
fixture.detectChanges();
|
||||
const tags = fixture.debugElement.queryAll(By.css('digi-icon-spinner'));
|
||||
expect(tags.length).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AvropService } from '../../avrop.service';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-avrop-filters',
|
||||
templateUrl: './avrop-filters.component.html',
|
||||
styleUrls: ['./avrop-filters.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AvropFiltersComponent {
|
||||
selectableTjanster$: Observable<MultiselectFilterOption[]> = this.avropService.selectableTjanster$;
|
||||
selectableUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this.avropService
|
||||
.selectableUtforandeVerksamheter$;
|
||||
selectableKommuner$: Observable<MultiselectFilterOption[]> = this.avropService.selectableKommuner$;
|
||||
selectedTjanster$: Observable<MultiselectFilterOption[]> = this.avropService.selectedTjanster$;
|
||||
selectedUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this.avropService
|
||||
.selectedUtforandeVerksamheter$;
|
||||
selectedKommuner$: Observable<MultiselectFilterOption[]> = this.avropService.selectedKommuner$;
|
||||
|
||||
constructor(private avropService: AvropService) {}
|
||||
|
||||
updateSelectedTjanster(filterOptions: MultiselectFilterOption[]): void {
|
||||
this.avropService.setSelectedTjanster(filterOptions);
|
||||
}
|
||||
|
||||
updateSelectedUtforandeVerksamheter(filterOptions: MultiselectFilterOption[]): void {
|
||||
this.avropService.setSelectedUtforandeVerksamheter(filterOptions);
|
||||
}
|
||||
|
||||
updateSelectedKommuner(filterOptions: MultiselectFilterOption[]): void {
|
||||
this.avropService.setSelectedKommuner(filterOptions);
|
||||
}
|
||||
|
||||
removeKommun(kommunToRemove: MultiselectFilterOption) {
|
||||
this.avropService.removeKommun(kommunToRemove);
|
||||
}
|
||||
|
||||
removeUtforandeVerksamhet(utforandeVerksamhetToRemove: MultiselectFilterOption) {
|
||||
this.avropService.removeUtforandeVerksamhet(utforandeVerksamhetToRemove);
|
||||
}
|
||||
|
||||
removeTjanst(tjanstToRemove: MultiselectFilterOption) {
|
||||
this.avropService.removeTjanst(tjanstToRemove);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<div style="border: 2px solid #CCC; background: #EFEFEF; margin-right: 2rem; padding: 1rem;">
|
||||
<strong>{{filterLabel}}</strong>
|
||||
<digi-form-checkbox
|
||||
*ngFor="let filterOption of filterOptions"
|
||||
[afLabel]="filterOption.label + ' (' + (filterOption.count || 0) + ')'"
|
||||
(change)="setOptionState(filterOption, $event.target.checked)"
|
||||
[afChecked]="isSelected(filterOption)"
|
||||
>
|
||||
</digi-form-checkbox>
|
||||
|
||||
<digi-button (click)="emitSelectedOptions()" af-size="s">Spara</digi-button>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TemporaryFilterComponent } from './temporary-filter.component';
|
||||
|
||||
describe('TemporaryFilterComponent', () => {
|
||||
let component: TemporaryFilterComponent;
|
||||
let fixture: ComponentFixture<TemporaryFilterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TemporaryFilterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TemporaryFilterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-temporary-filter',
|
||||
templateUrl: './temporary-filter.component.html',
|
||||
styleUrls: ['./temporary-filter.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TemporaryFilterComponent implements OnInit, OnChanges {
|
||||
private _selectedAvropFilterOption$ = new BehaviorSubject<MultiselectFilterOption[]>(null);
|
||||
selectedAvropFilterOptionState$: Observable<MultiselectFilterOption[]>;
|
||||
|
||||
@Input() filterLabel: string;
|
||||
@Input() filterOptions: MultiselectFilterOption[];
|
||||
@Input() selectedOptions: MultiselectFilterOption[];
|
||||
@Output() selectedOptionsChange = new EventEmitter<MultiselectFilterOption[]>();
|
||||
|
||||
// THIS SHOULD BE REPLACED BY DIGI COMPONENT
|
||||
ngOnInit(): void {
|
||||
this._selectedAvropFilterOption$ = new BehaviorSubject<MultiselectFilterOption[]>(this.selectedOptions);
|
||||
this.selectedAvropFilterOptionState$ = this._selectedAvropFilterOption$.asObservable();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.selectedOptions?.currentValue) {
|
||||
this._selectedAvropFilterOption$.next(changes.selectedOptions.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
isSelected(filterOption: MultiselectFilterOption): boolean {
|
||||
return this.selectedOptions?.includes(filterOption) ?? false;
|
||||
}
|
||||
|
||||
setOptionState(filterOption: MultiselectFilterOption, isSelected: boolean): void {
|
||||
if (isSelected) {
|
||||
return this._selectedAvropFilterOption$.next([...(this._selectedAvropFilterOption$.value ?? []), filterOption]);
|
||||
}
|
||||
return this._selectedAvropFilterOption$.next(
|
||||
this._selectedAvropFilterOption$.value?.filter(item => item != filterOption) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
emitSelectedOptions(): void {
|
||||
this.selectedOptionsChange.emit(this._selectedAvropFilterOption$.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<div class="avrop-table-row">
|
||||
<div class="avrop-table-row__data-row">
|
||||
<div class="avrop-table-row__data-column">
|
||||
<digi-form-checkbox
|
||||
*ngIf="!isLocked"
|
||||
class="avrop-table-row__checkbox"
|
||||
[afLabel]="'Välj sökande'"
|
||||
[afChecked]="isSelected"
|
||||
(change)="emitSelectionChange($event.target.checked)"
|
||||
>
|
||||
</digi-form-checkbox>
|
||||
</div>
|
||||
<div class="avrop-table-row__data-column">
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Namn:</strong>
|
||||
<span>{{deltagare?.fullName}}</span>
|
||||
</digi-typography>
|
||||
</div>
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Tjänst:</strong>
|
||||
<span>{{deltagare?.tjanst}}</span>
|
||||
</digi-typography>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avrop-table-row__data-column">
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Startdatum:</strong>
|
||||
<digi-typography-time *ngIf="deltagare?.startDate" [afDateTime]="deltagare?.startDate"></digi-typography-time>
|
||||
</digi-typography>
|
||||
</div>
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Slutdatum:</strong>
|
||||
<digi-typography-time *ngIf="deltagare?.endDate" [afDateTime]="deltagare?.endDate"></digi-typography-time>
|
||||
</digi-typography>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avrop-table-row__data-column">
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Språkstöd/Tolk:</strong>
|
||||
<span>{{deltagare?.sprakstod + '/' + deltagare?.tolkbehov}}</span>
|
||||
</digi-typography>
|
||||
</div>
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Utförande adress:</strong>
|
||||
<span>{{deltagare?.utforandeAdress}}</span>
|
||||
</digi-typography>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avrop-table-row__data-column avrop-table-row__data-column--bottom-align">
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Spår/nivå:</strong>
|
||||
<span>{{deltagare?.trackCode}}</span>
|
||||
</digi-typography>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="handledare" class="avrop-table-row__data-column avrop-table-row__data-column--bottom-align">
|
||||
<div class="avrop-table__cell">
|
||||
<digi-typography>
|
||||
<strong class="avrop-table__label">Vald handledare:</strong>
|
||||
<span>{{handledare?.fullName}}</span>
|
||||
</digi-typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<digi-button
|
||||
class="avrop-table-row__close-btn"
|
||||
*ngIf="handledareConfirmed"
|
||||
[attr.af-variation]="ButtonVariation.TERTIARY"
|
||||
(afOnClick)="emitDeltagareDeleted()"
|
||||
>
|
||||
Ta bort
|
||||
<digi-icon-x slot="icon"></digi-icon-x>
|
||||
</digi-button>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
@import 'variables/gutters';
|
||||
|
||||
.avrop-table-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: var(--digi--layout--gutter) var(--digi--layout--gutter);
|
||||
background-color: var(--digi--ui--color--background--secondary);
|
||||
}
|
||||
|
||||
.avrop-table-row__close-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
::ng-deep {
|
||||
button {
|
||||
padding: 0.5rem 0.75rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avrop-table-row__checkbox {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.avrop-table-row__data-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $digi--layout--gutter--xl $digi--layout--gutter--l;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.avrop-table-row__data-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $digi--layout--gutter--l 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.avrop-table-row__data-column--bottom-align {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.avrop-table__label {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AvropTableRowComponent } from './avrop-table-row.component';
|
||||
|
||||
describe('AvropTableRowComponent', () => {
|
||||
let component: AvropTableRowComponent;
|
||||
let fixture: ComponentFixture<AvropTableRowComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AvropTableRowComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvropTableRowComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Avrop } from '@msfa-models/avrop.model';
|
||||
import { ButtonVariation } from '../../../enums/button-vatiation.enum';
|
||||
import { HandledareAvrop } from '../../../models/handledare-avrop';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-avrop-table-row',
|
||||
templateUrl: './avrop-table-row.component.html',
|
||||
styleUrls: ['./avrop-table-row.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AvropTableRowComponent {
|
||||
@Input() deltagare: Avrop;
|
||||
@Input() isSelected: boolean;
|
||||
@Input() isLocked: boolean;
|
||||
@Input() handledare: HandledareAvrop;
|
||||
@Input() handledareConfirmed: boolean;
|
||||
@Output() isSelectedChange = new EventEmitter<boolean>();
|
||||
@Output() deleteDeltagareClicked = new EventEmitter<void>();
|
||||
|
||||
ButtonVariation = ButtonVariation;
|
||||
|
||||
emitSelectionChange(isSelected: boolean): void {
|
||||
this.isSelectedChange.emit(isSelected);
|
||||
}
|
||||
|
||||
emitDeltagareDeleted(): void {
|
||||
this.deleteDeltagareClicked.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="avrop-table">
|
||||
<msfa-avrop-table-row
|
||||
*ngFor="let deltagare of deltagareRows"
|
||||
[deltagare]="deltagare"
|
||||
[isSelected]="isSelected(deltagare)"
|
||||
[isLocked]="isLocked"
|
||||
(isSelectedChange)="isSelectedChange(deltagare, $event)"
|
||||
[handledare]="handledare"
|
||||
[handledareConfirmed]="handledareConfirmed"
|
||||
></msfa-avrop-table-row>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.avrop-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AvropTableComponent } from './avrop-table.component';
|
||||
|
||||
describe('AvropTableComponent', () => {
|
||||
let component: AvropTableComponent;
|
||||
let fixture: ComponentFixture<AvropTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AvropTableComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvropTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Avrop } from '@msfa-models/avrop.model';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { HandledareAvrop } from '../../models/handledare-avrop';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-avrop-table',
|
||||
templateUrl: './avrop-table.component.html',
|
||||
styleUrls: ['./avrop-table.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AvropTableComponent implements OnInit {
|
||||
private _selectedDeltagare$ = new BehaviorSubject<Avrop[]>(null);
|
||||
selectedDeltagareState$: Observable<Avrop[]> = this._selectedDeltagare$.asObservable();
|
||||
|
||||
@Input() selectableDeltagareList: Avrop[];
|
||||
@Input() selectedDeltagareListInput: Avrop[];
|
||||
@Input() handledare: HandledareAvrop;
|
||||
@Input() isLocked: boolean;
|
||||
@Input() handledareConfirmed: boolean;
|
||||
@Output() changedSelectedDeltagareList = new EventEmitter<Avrop[]>();
|
||||
|
||||
get deltagareRows(): Avrop[] {
|
||||
return this.isLocked ? this.selectedDeltagareListInput : this.selectableDeltagareList;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._selectedDeltagare$
|
||||
.pipe(filter(x => !!x))
|
||||
.subscribe(selectedDeltagare => this.changedSelectedDeltagareList.emit(selectedDeltagare));
|
||||
// TODO lägg till unusubscribeOnDestroy
|
||||
}
|
||||
|
||||
isSelected(deltagare: Avrop): boolean {
|
||||
return this.selectedDeltagareListInput?.includes(deltagare) ?? false;
|
||||
}
|
||||
|
||||
isSelectedChange(deltagare: Avrop, isSelected: boolean): void {
|
||||
if (isSelected) {
|
||||
return this._selectedDeltagare$.next([
|
||||
...(this._selectedDeltagare$.value?.filter(deltagareInList => deltagareInList != deltagare) ?? []),
|
||||
deltagare,
|
||||
]);
|
||||
}
|
||||
return this._selectedDeltagare$.next(
|
||||
this._selectedDeltagare$.value?.filter(deltagareInList => deltagareInList != deltagare) ?? []
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum ButtonVariation {
|
||||
PRIMARY = 'primary',
|
||||
SECONDARY = 'secondary',
|
||||
TERTIARY = 'tertiary',
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface HandledareAvrop {
|
||||
fullName: string;
|
||||
id: string;
|
||||
}
|
||||
Reference in New Issue
Block a user