Merge pull request #28 in TEA/dafa-web-monorepo from feature/TV-285-hantera-avrop-skelett to develop

Squashed commit of the following:

commit 4d31c7c490c37ba2b9715b6d4b5ee2cb58f1fb06
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 23 14:18:52 2021 +0200

    add step 4

commit cfeb21b69200a70987cd93caa17527402d0447a1
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 23 13:49:20 2021 +0200

    Update avrop-table.component.ts

commit 253f77f8882028d1590bd7e5ff2a17516ab9ed01
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 23 13:18:01 2021 +0200

    Update avrop-table.component.ts

commit 86f8ea2a6840834c9661b715c08bdfaeb20f7e98
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 22 13:50:39 2021 +0200

    remove unused tests

commit 5ec16261ba95911a808c1535233950509df1235f
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 22 13:02:05 2021 +0200

    cleanup

commit f80630704573fac90b618f06ba50db6409a576ae
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 22 11:53:28 2021 +0200

    filters only in step 1

commit 0c1c2ac6c5c8c8016b676ef4ba7959c10d0e5b74
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 22 11:52:09 2021 +0200

    default checked

commit 1516ac5074846dabfaa354a8dca7e1cd81e8174c
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 22 11:42:04 2021 +0200

    add filters and checkboxes

commit b5b5d96a713c37a03ab4a4dcbe34d2f236ccc1c3
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 22 10:46:38 2021 +0200

    add apis methods for filters

commit 3a71113b9dc7bde5306be7e47fc3a3e3568dcd27
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 15:56:35 2021 +0200

    selectableDeltagareList$ from API service

commit 7efcfcbb9e19fff41b3424b079910000954fec03
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 15:46:16 2021 +0200

    add avrop api service

commit 7146c45ea88e4871856efe0bc263db901f0ee393
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 14:02:32 2021 +0200

    Fixed flow

commit d8690eb36b13c5f01f8bfe655d9885b50e6631a7
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 12:21:41 2021 +0200

    structure start

commit 06344cb34c1e0702310cfabaccb094094eb44649
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 11:10:31 2021 +0200

    add table and row components

commit 28dba7accfc819b3a91150275ec5f1ec10494d3b
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 11:03:46 2021 +0200

    added avrop-filters

commit f477dc3d19e30ae461b09de1b0817b41e120c9f2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 21 11:02:17 2021 +0200

    rename call-off to avrop

commit 9109bd15b518dcb5e3b1c272dcc8c90fa9e6843b
Author: Chingiz <chingiz.esenbaev@arbetsformedlingen.se>
Date:   Fri Jun 18 14:24:31 2021 +0200

    fix: removed comment and console print

commit e75537d2434ae57d4933b435150641ddabd262fa
Merge: 9c8a224 f7081d8
Author: Aden Hassan <aden.hassan@arbetsformedlingen.se>
Date:   Fri Jun 18 13:03:49 2021 +0200

    Merge branch 'develop' into feature/TV-285-hantera-avrop-skelett

commit 9c8a22424e4a7e692a38148e1d06da464f136748
Author: Aden Hassan <aden.hassan@arbetsformedlingen.se>
Date:   Fri Jun 18 10:04:10 2021 +0200

    feature/tv-285: added basic logic to toggle between the progress steps and render correct ui

commit e39b5b9b1dc513e1a24dd945590343b5c063f65f
Merge: 8004cd5 88c68e1
Author: Chingiz <chingiz.esenbaev@arbetsformedlingen.se>
Date:   Thu Jun 17 17:21:19 2021 +0200

    Merge branch 'develop' into feature/TV-285-hantera-avrop-skelett

... and 2 more commits
This commit is contained in:
Chingiz Esenbaev
2021-06-23 15:24:39 +02:00
parent f7081d84b5
commit 4afe9b589b
37 changed files with 808 additions and 127 deletions

View File

@@ -21,7 +21,7 @@ const routes: Routes = [
{ {
path: 'avrop', path: 'avrop',
data: { title: 'Avrop' }, data: { title: 'Avrop' },
loadChildren: () => import('./pages/call-off/call-off.module').then(m => m.CallOffModule), loadChildren: () => import('./pages/avrop/avrop.module').then(m => m.AvropModule),
}, },
{ {
path: 'meddelanden', path: 'meddelanden',

View File

@@ -13,6 +13,7 @@ import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module'; import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
import { ToastListModule } from './components/toast-list/toast-list.module'; import { ToastListModule } from './components/toast-list/toast-list.module';
import { AuthInterceptor } from '@dafa-services/api/auth.interceptor'; import { AuthInterceptor } from '@dafa-services/api/auth.interceptor';
import { AvropModule } from './pages/avrop/avrop.module';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
@@ -28,6 +29,7 @@ import { AuthInterceptor } from '@dafa-services/api/auth.interceptor';
FooterModule, FooterModule,
MarkdownModule.forRoot({ loader: HttpClient }), MarkdownModule.forRoot({ loader: HttpClient }),
DigiNgNavigationBreadcrumbsModule, DigiNgNavigationBreadcrumbsModule,
AvropModule,
], ],
providers: [ providers: [
{ {

View File

@@ -7,24 +7,20 @@ import { SidebarModule } from '../sidebar/sidebar.module';
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs'; import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { FooterModule } from '../footer/footer.module'; import { FooterModule } from '../footer/footer.module';
import { ToastListModule } from '../toast-list/toast-list.module'; import { ToastListModule } from '../toast-list/toast-list.module';
import { RouterModule } from '@angular/router';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule,
CommonModule, CommonModule,
SkipToContentModule, SkipToContentModule,
NavigationModule, NavigationModule,
SidebarModule, SidebarModule,
DigiNgNavigationBreadcrumbsModule, DigiNgNavigationBreadcrumbsModule,
FooterModule, FooterModule,
ToastListModule ToastListModule,
],
declarations: [
LoggedInShellComponent
],
exports: [
LoggedInShellComponent
], ],
declarations: [LoggedInShellComponent],
exports: [LoggedInShellComponent],
}) })
export class LoggedInShellModule { } export class LoggedInShellModule {}

View File

@@ -0,0 +1,11 @@
<div style=" display: flex">
<dafa-temporary-filter [filterLabel]="'Tjänster'" [filterOptions]="selectableTjanster$ | async" (selectedOptionsChange)="updateSelectedTjanster($event)"></dafa-temporary-filter>
<dafa-temporary-filter [filterLabel]="'Utförande verksamheter'" [filterOptions]="selectableUtforandeVerksamheter$ | async" (selectedOptionsChange)="updateSelectedUtforandeVerksamheter($event)"></dafa-temporary-filter>
<dafa-temporary-filter [filterLabel]="'Kommuner'" [filterOptions]="selectableKommuner$ | async" (selectedOptionsChange)="updateSelectedKommuner($event)"></dafa-temporary-filter>
</div>
<br><br>

View File

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

View File

@@ -0,0 +1,34 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { AvropService } from '../avrop.service';
import { Observable } from 'rxjs';
import { Deltagare } from '../models/Deltagare';
import { MultiselectFilterOption } from '../models/AvropFilterOptions';
@Component({
selector: 'dafa-avrop-filters',
templateUrl: './avrop-filters.component.html',
styleUrls: ['./avrop-filters.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvropFiltersComponent implements OnInit {
selectableTjanster$: Observable<MultiselectFilterOption[]> = this.avropService.selectableTjanster$;
selectableUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this.avropService
.selectableUtforandeVerksamheter$;
selectableKommuner$: Observable<MultiselectFilterOption[]> = this.avropService.selectableKommuner$;
constructor(private avropService: AvropService) {}
ngOnInit(): void {}
updateSelectedTjanster(filterOptions: MultiselectFilterOption[]) {
this.avropService.setSelectedTjanster(filterOptions);
}
updateSelectedUtforandeVerksamheter(filterOptions: MultiselectFilterOption[]) {
this.avropService.setSelectedUtforandeVerksamheter(filterOptions);
}
updateSelectedKommuner(filterOptions: MultiselectFilterOption[]) {
this.avropService.setSelectedKommuner(filterOptions);
}
}

View File

@@ -0,0 +1,10 @@
<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 + ')'"
(change)="setOptionState(filterOption, $event.target.checked)">
</digi-form-checkbox>
<digi-button (click)="emitSelectedOptions()" af-size="s">Spara</digi-button>
</div>

View File

@@ -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();
});
});

View File

@@ -0,0 +1,46 @@
import { Component, OnInit, ChangeDetectionStrategy, Input, Output } from '@angular/core';
import { MultiselectFilterOption } from '../../models/AvropFilterOptions';
import { BehaviorSubject, Observable } from 'rxjs';
import { Deltagare } from '../../models/Deltagare';
import { EventEmitter } from '@angular/core';
@Component({
selector: 'dafa-temporary-filter',
templateUrl: './temporary-filter.component.html',
styleUrls: ['./temporary-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TemporaryFilterComponent implements OnInit {
private _selectedAvropFilterOption$: BehaviorSubject<MultiselectFilterOption[]>;
selectedAvropFilterOptionState$: Observable<MultiselectFilterOption[]>;
@Input() filterLabel: string;
@Input() filterOptions: MultiselectFilterOption[];
@Input() selectedOptions: MultiselectFilterOption[];
@Output() selectedOptionsChange = new EventEmitter<MultiselectFilterOption[]>();
// THIS SHOULD BE REPLACED BY DIGI COMPONENT
constructor() {}
ngOnInit(): void {
this._selectedAvropFilterOption$ = new BehaviorSubject<MultiselectFilterOption[]>(this.selectedOptions);
this.selectedAvropFilterOptionState$ = this._selectedAvropFilterOption$.asObservable();
}
isSelected(filterOption: MultiselectFilterOption): boolean {
return this.selectedOptions?.includes(filterOption) ?? false;
}
setOptionState(filterOption: MultiselectFilterOption, isSelected: boolean) {
if (isSelected) {
return this._selectedAvropFilterOption$.next([...(this._selectedAvropFilterOption$.value ?? []), filterOption]);
}
return this._selectedAvropFilterOption$.next(
this._selectedAvropFilterOption$.value?.filter(item => item != filterOption) ?? []
);
}
emitSelectedOptions() {
this.selectedOptionsChange.emit(this._selectedAvropFilterOption$.value);
}
}

View File

@@ -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();
});
});

View File

@@ -0,0 +1,12 @@
<div>
Deltagare: {{deltagare?.fullName}} <br>
<ng-container *ngIf="handledare">handledare: {{handledare?.fullName}}</ng-container>
<digi-form-checkbox
*ngIf="!isLocked"
[afLabel]="'Markera'"
[afChecked]="isSelected"
(change)="emitSelectionChange($event.target.checked)">
</digi-form-checkbox>
</div>
<hr>

View File

@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoggedInShellComponent } from './logged-in-shell.component'; import { AvropTableRowComponent } from './avrop-table-row.component';
describe('LoggedInShellComponent', () => { describe('AvropTableRowComponent', () => {
let component: LoggedInShellComponent; let component: AvropTableRowComponent;
let fixture: ComponentFixture<LoggedInShellComponent>; let fixture: ComponentFixture<AvropTableRowComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ LoggedInShellComponent ] declarations: [ AvropTableRowComponent ]
}) })
.compileComponents(); .compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(LoggedInShellComponent); fixture = TestBed.createComponent(AvropTableRowComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -0,0 +1,26 @@
import { EventEmitter } from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy, Input, Output } from '@angular/core';
import { Deltagare } from '../../models/Deltagare';
import { Handledare } from '../../models/Handledare';
@Component({
selector: 'dafa-avrop-table-row',
templateUrl: './avrop-table-row.component.html',
styleUrls: ['./avrop-table-row.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvropTableRowComponent implements OnInit {
@Input() deltagare: Deltagare;
@Input() isSelected: boolean;
@Input() isLocked: boolean;
@Output() isSelectedChange = new EventEmitter<boolean>();
@Input() handledare: Handledare;
@Input() handledareConfirmed: boolean;
constructor() {}
ngOnInit(): void {}
emitSelectionChange(isSelected: boolean) {
this.isSelectedChange.emit(isSelected);
}
}

View File

@@ -0,0 +1,9 @@
<dafa-avrop-table-row
*ngFor="let deltagare of deltagareRows"
[deltagare]="deltagare"
[isSelected]="isSelected(deltagare)"
[isLocked]="isLocked"
(isSelectedChange)="isSelectedChange(deltagare, $event)"
[handledare]="handledare"
[handledareConfirmed]="handledareConfirmed"
></dafa-avrop-table-row>

View File

@@ -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();
});
});

View File

@@ -0,0 +1,52 @@
import { Component, OnInit, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core';
import { Deltagare } from '../models/Deltagare';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Handledare } from '../models/Handledare';
@Component({
selector: 'dafa-avrop-table',
templateUrl: './avrop-table.component.html',
styleUrls: ['./avrop-table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvropTableComponent implements OnInit {
private _selectedDeltagare$ = new BehaviorSubject<Deltagare[]>(null);
selectedDeltagareState$: Observable<Deltagare[]> = this._selectedDeltagare$.asObservable();
@Input() selectableDeltagareList: Deltagare[];
@Input() selectedDeltagareListInput: Deltagare[];
@Input() handledare: Handledare;
@Input() isLocked: boolean;
@Input() handledareConfirmed: boolean;
@Output() changedSelectedDeltagareList = new EventEmitter<Deltagare[]>();
get deltagareRows(): Deltagare[] {
return this.isLocked ? this.selectedDeltagareListInput : this.selectableDeltagareList;
}
constructor() {}
ngOnInit(): void {
this._selectedDeltagare$
.pipe(filter(x => !!x))
.subscribe(selectedDeltagare => this.changedSelectedDeltagareList.emit(selectedDeltagare));
// TODO lägg till unusubscribeOnDestroy
}
isSelected(deltagare: Deltagare): boolean {
return this.selectedDeltagareListInput?.includes(deltagare) ?? false;
}
isSelectedChange(deltagare: Deltagare, 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) ?? []
);
}
}

View File

@@ -0,0 +1,110 @@
<dafa-logged-in-shell>
<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 class="" style="height:300px; padding: 30px 0;">
<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">
<dafa-avrop-filters></dafa-avrop-filters>
</ng-container>
<ng-container *ngIf="currentStep == 3">
<h2>Vänligen bekräfta</h2>
</ng-container>
<ng-container *ngIf="currentStep < 4">
<dafa-avrop-table
[selectableDeltagareList]="selectableDeltagareList$ | async"
[selectedDeltagareListInput]="selectedDeltagareList$ | async"
[isLocked]="deltagareListIsLocked$ | async"
(changedSelectedDeltagareList)="updateSelectedDeltagareList($event)"
[handledare]="selectedHandledare$ | async"
[handledareConfirmed]="handledareConfirmed$ | async"
></dafa-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>
</dafa-logged-in-shell>
<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>

View File

@@ -1,22 +1,22 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { CallOffComponent } from './call-off.component'; import { AvropComponent } from './avrop.component';
describe('CallOffComponent', () => { describe('CallOffComponent', () => {
let component: CallOffComponent; let component: AvropComponent;
let fixture: ComponentFixture<CallOffComponent>; let fixture: ComponentFixture<AvropComponent>;
beforeEach( beforeEach(
waitForAsync(() => { waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [CallOffComponent], declarations: [AvropComponent],
imports: [RouterTestingModule], imports: [RouterTestingModule],
}).compileComponents(); }).compileComponents();
}) })
); );
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CallOffComponent); fixture = TestBed.createComponent(AvropComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -0,0 +1,67 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { AvropService } from './avrop.service';
import { Observable } from 'rxjs';
import { MultiselectFilterOption } from './models/AvropFilterOptions';
import { Deltagare } from './models/Deltagare';
import { Handledare } from './models/Handledare';
@Component({
selector: 'dafa-avrop',
templateUrl: './avrop.component.html',
styleUrls: ['./avrop.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvropComponent implements OnInit {
steps = 3;
currentStep$ = this.avropService.currentStep$;
selectedUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this.avropService
.selectedUtforandeVerksamheter$;
selectableDeltagareList$: Observable<Deltagare[]> = this.avropService.selectableDeltagareList$;
selectedDeltagareList$: Observable<Deltagare[]> = this.avropService.selectedDeltagareList$;
deltagareListIsLocked$: Observable<boolean> = this.avropService.deltagareListIsLocked$;
selectableHandledareList$: Observable<Handledare[]> = this.avropService.selectableHandledareList$;
selectedHandledare$: Observable<Handledare> = this.avropService.selectedHandledare$;
handledareConfirmed$: Observable<boolean> = this.avropService.handledareIsConfirmed$;
constructor(private avropService: AvropService) {}
updateSelectedDeltagareList(deltagareList: Deltagare[]) {
this.avropService.setSelectedDeltagare(deltagareList);
}
lockSelectedDeltagare() {
this.avropService.lockSelectedDeltagare();
}
unlockSelectedDeltagare() {
this.avropService.unlockSelectedDeltagare();
}
ngOnInit(): void {
// this.avropService.loadFromAPI();
}
confirmHandledare() {
this.avropService.confirmHandledare();
}
unconfirmHandledare() {
this.avropService.unconfirmHandledare();
}
save() {
this.avropService.save();
}
changeHandledare(newHandledare: Event) {
const handledareId = newHandledare.target['value'];
this.avropService.setHandledareState(handledareId);
}
goToStep1() {
this.avropService.goToStep1();
}
}

View File

@@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AvropComponent } from './avrop.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar';
import { AvropFiltersComponent } from './avrop-filters/avrop-filters.component';
import { AvropTableComponent } from './avrop-table/avrop-table.component';
import { AvropTableRowComponent } from './avrop-table/avrop-table-row/avrop-table-row.component';
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { TemporaryFilterComponent } from './avrop-filters/temporary-filter/temporary-filter.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvropComponent, AvropFiltersComponent, AvropTableComponent, AvropTableRowComponent, TemporaryFilterComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: AvropComponent }]),
LoggedInShellModule,
DigiNgProgressProgressbarModule,
DigiNgSkeletonBaseModule,
],
})
export class AvropModule {}

View File

@@ -0,0 +1,177 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { MultiselectFilterOption } from './models/AvropFilterOptions';
import { Deltagare } from './models/Deltagare';
import { Handledare } from './models/Handledare';
import { first, map, switchMap } from 'rxjs/operators';
import { AvropApiService } from '@dafa-services/api/avrop-api.service';
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<Deltagare[]>([]);
private _deltagareListIsLocked$ = new BehaviorSubject<boolean>(null);
private _selectedHandledare$ = new BehaviorSubject<Handledare>(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<Deltagare[]> = combineLatest([
this.selectedTjanster$,
this.selectedUtforandeVerksamheter$,
this.selectedKommuner$,
]).pipe(
switchMap(([selectedTjanster, selectedUtforandeVerksamheter, selectedKommuner]) =>
this.avropApiService.getNyaDeltagare$(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<Deltagare[]> = this._selectedDeltagareList$.asObservable();
deltagareListIsLocked$: Observable<boolean> = this._deltagareListIsLocked$.asObservable();
lockedDeltagareList$: Observable<Deltagare[]> = combineLatest([
this.selectedDeltagareList$,
this.deltagareListIsLocked$,
]).pipe(map(([selectedDeltagareList, isLocked]) => (isLocked ? selectedDeltagareList : null)));
selectableHandledareList$: Observable<Handledare[]> = this.lockedDeltagareList$.pipe(
switchMap(lockedDeltagare => this.avropApiService.getSelectableHandledare$(lockedDeltagare))
);
selectedHandledare$: Observable<Handledare> = 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: Deltagare[]) {
this._selectedDeltagareList$.next(deltagare);
}
constructor(private avropApiService: AvropApiService) {}
lockSelectedDeltagare() {
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() {
this._deltagareListIsLocked$.next(false);
}
confirmHandledare() {
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() {
this._handledareIsConfirmed$.next(false);
}
async save() {
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);
}
setHandledareState(handledareId: string) {
this.selectableHandledareList$.pipe(first()).subscribe(handledareList => {
this._selectedHandledare$.next(handledareList.find(handledare => handledare.id === handledareId));
});
}
setSelectedTjanster(selectedFilterOptions: MultiselectFilterOption[]) {
this._selectedTjanster$.next(selectedFilterOptions);
}
setSelectedUtforandeVerksamheter(selectedFilterOptions: MultiselectFilterOption[]) {
this._selectedUtforandeVerksamheter$.next(selectedFilterOptions);
}
setSelectedKommuner(selectedFilterOptions: MultiselectFilterOption[]) {
this._selectedKommuner$.next(selectedFilterOptions);
}
goToStep1() {
this._selectedHandledare$.next(null);
this._selectedDeltagareList$.next(null);
this._deltagareListIsLocked$.next(false);
this._handledareIsConfirmed$.next(false);
}
}

View File

@@ -0,0 +1,5 @@
export interface MultiselectFilterOption {
label: string;
id: string;
count: number;
}

View File

@@ -0,0 +1,3 @@
export interface Deltagare {
fullName: string;
}

View File

@@ -0,0 +1,4 @@
export interface Handledare {
fullName: string;
id: string;
}

View File

@@ -1 +0,0 @@
<dafa-logged-in-shell><section class="call-off">Avrop funkar!</section></dafa-logged-in-shell>

View File

@@ -1,9 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'dafa-call-off',
templateUrl: './call-off.component.html',
styleUrls: ['./call-off.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CallOffComponent {}

View File

@@ -1,11 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CallOffComponent } from './call-off.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [CallOffComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: CallOffComponent }]), LoggedInShellModule]
})
export class CallOffModule {}

View File

@@ -1,27 +0,0 @@
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { CiamLandingComponent } from './ciam-landing.component';
describe('ReleasesComponent', () => {
let component: CiamLandingComponent;
let fixture: ComponentFixture<CiamLandingComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [CiamLandingComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(CiamLandingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,27 +0,0 @@
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LogoutComponent } from './logout.component';
describe('ReleasesComponent', () => {
let component: LogoutComponent;
let fixture: ComponentFixture<LogoutComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [LogoutComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LogoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,28 +0,0 @@
import { DigiNgTableModule } from '@af/digi-ng/_table/table';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ParticipantCardComponent } from './participant-card.component';
describe('ParticipantCardComponent', () => {
let component: ParticipantCardComponent;
let fixture: ComponentFixture<ParticipantCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [ParticipantCardComponent],
imports: [RouterTestingModule, DigiNgTableModule],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ParticipantCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,106 @@
import { Injectable } from '@angular/core';
import { Deltagare } from '../../pages/avrop/models/Deltagare';
import { Handledare } from '../../pages/avrop/models/Handledare';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { MultiselectFilterOption } from '../../pages/avrop/models/AvropFilterOptions';
const tempHandledareMock: Handledare[] = [
{ id: '1', fullName: 'Göran Persson' },
{ id: '2', fullName: 'Stefan Löfven' },
];
const tempDeltagareMock: Deltagare[] = [{ fullName: 'Daniel' }, { fullName: 'Chingiz' }];
const tempKommunerMock: MultiselectFilterOption[] = [
{ id: '124', count: 12, label: 'Stockholm' },
{ id: '125', count: 42, label: 'Göteborg' },
];
const tempUtforandeVerksamheterMock: MultiselectFilterOption[] = [
{ id: 'a124', count: 312, label: 'Utf verk 1' },
{ id: 'b125', count: 142, label: 'Utf verk 2' },
];
const tempTjansterMock: MultiselectFilterOption[] = [
{ id: '013', count: 312, label: 'Karriärvägledning' },
{ id: '321', count: 142, label: 'Karriärval rusta och matcha' },
];
@Injectable({
providedIn: 'root',
})
export class AvropApiService {
constructor() {}
getNyaDeltagare$(
tjanstIds: MultiselectFilterOption[],
kommunIds: MultiselectFilterOption[],
utforandeVerksamhetIds: MultiselectFilterOption[]
): Observable<Deltagare[]> {
// TODO replace with API-call using tjanstIds, kommunIds, utforandeVerksamhetIds
console.log(
'[API call] getNyaDeltagare$. Inputs: tjanstIds, kommunIds, utforandeVerksamhetIds',
tjanstIds,
kommunIds,
utforandeVerksamhetIds
);
console.log(
'[API call] getNyaDeltagare$. Inputs: tjanstIds, kommunIds, utforandeVerksamhetIds',
tjanstIds,
kommunIds,
utforandeVerksamhetIds
);
return of(tempDeltagareMock).pipe(delay(300));
}
getSelectableHandledare$(deltagare: Deltagare[]): Observable<Handledare[]> {
// TODO replace with API-call
console.log('[API call] getSelectableHandledare$. Inputs: deltagare', deltagare);
return of(tempHandledareMock).pipe(delay(350));
}
getSelectableTjanster$(
selectedKommuner: MultiselectFilterOption[],
selectedUtforandeVerksamheter: MultiselectFilterOption[]
): Observable<MultiselectFilterOption[]> {
// TODO replace with API-call
console.log(
'[API call] getSelectableTjanster$. Inputs: selectedKommuner, selectedUtforandeVerksamheter',
selectedKommuner,
selectedUtforandeVerksamheter
);
return of(tempTjansterMock).pipe(delay(300));
}
getSelectableUtforandeVerksamheter$(
selectedTjanster: MultiselectFilterOption[],
selectedKommuner: MultiselectFilterOption[]
): Observable<MultiselectFilterOption[]> {
// TODO replace with API-call
console.log(
'[API call] getSelectableUtforandeVerksamheter$. Inputs: selectedTjanster, selectedKommuner',
selectedTjanster,
selectedKommuner
);
return of(tempUtforandeVerksamheterMock).pipe(delay(300));
}
getSelectableKommuner$(
selectedTjanster: MultiselectFilterOption[],
selectedUtforandeVerksamheter: MultiselectFilterOption[]
): Observable<MultiselectFilterOption[]> {
// TODO replace with API-call
console.log(
'[API call] getSelectableKommuner$. Inputs: selectedTjanster, selectedUtforandeVerksamheter',
selectedTjanster,
selectedUtforandeVerksamheter
);
return of(tempKommunerMock).pipe(delay(300));
}
async tilldelaHandledare(deltagare: Deltagare[], handledare: Handledare) {
console.log('[API call] SAVE avrop. Inputs: deltagare, handledare', deltagare, handledare);
// TODO anropa API
return;
}
}

View File

@@ -26,7 +26,6 @@ function generateIncomingAvrop(amount = 10) {
errandNumber: faker.datatype.number({ min: 100000, max: 999999 }), errandNumber: faker.datatype.number({ min: 100000, max: 999999 }),
startDate: faker.date.recent(), startDate: faker.date.recent(),
endDate: faker.date.future(), endDate: faker.date.future(),
//handleBefore: faker.date.soon(),
supportLanguage: language.name, supportLanguage: language.name,
interpreter: language.name, interpreter: language.name,
organization: ORGANIZATIONS[Math.floor(Math.random() * ORGANIZATIONS.length)], organization: ORGANIZATIONS[Math.floor(Math.random() * ORGANIZATIONS.length)],
@@ -35,7 +34,7 @@ function generateIncomingAvrop(amount = 10) {
avropList.push({ ...avrop, fullName: `${avrop.firstName} ${avrop.lastName}` }); avropList.push({ ...avrop, fullName: `${avrop.firstName} ${avrop.lastName}` });
} }
console.info('Incoming avrop generated...', avropList); console.info('Incoming avrop generated...');
return avropList; return avropList;
} }