mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merge branch 'feature/189-Warenausgabe/277-Filter/823-Reset-Filters-Instead-Of-Delete-Filters' into feature/189-Warenausgabe/277-Filter/main
This commit is contained in:
@@ -5,4 +5,5 @@ export interface SelectFilterOption extends FilterOptionBase {
|
||||
expanded?: boolean;
|
||||
options?: SelectFilterOption[];
|
||||
id?: string;
|
||||
initial_selected_state?: boolean;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export type FilterResetStrategy = 'all' | 'default';
|
||||
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './filter-reset-strategy.model';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="container" *ngIf="value">
|
||||
<div class="clear-all">
|
||||
<div class="clear-all" *ngIf="showResetButton">
|
||||
<button
|
||||
class="isa-btn isa-btn-primary-link remove-all isa-pt-0 isa-pb-0 isa-pl-15 isa-pr-15"
|
||||
(click)="clearAllFilters()"
|
||||
(click)="resetFilters()"
|
||||
>
|
||||
Alle Filter Entfernen
|
||||
{{ resetStrategy | filterResetWording }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { SelectedFilterOptionsComponent } from './selected-filter-options.component';
|
||||
import { ChangeDetectorRef, Renderer2 } from '@angular/core';
|
||||
import { IconModule } from '@libs/ui';
|
||||
import {
|
||||
FilterResetWordingPipe,
|
||||
SelectedFiltersPipe,
|
||||
CheckAllChildOptionsSelectedPipe,
|
||||
} from '../pipe';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { PrimaryFilterOption } from '../../../shelf/defs';
|
||||
import {
|
||||
mockFilters,
|
||||
primaryFiltersMock,
|
||||
} from '../../../shelf/shared/mockdata';
|
||||
import { SelectFilter } from '../../models';
|
||||
|
||||
fdescribe('SelectedFilterOptionsComponent', () => {
|
||||
let fixture: ComponentFixture<SelectedFilterOptionsComponent>;
|
||||
let component: SelectedFilterOptionsComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [IconModule, IconModule],
|
||||
providers: [ChangeDetectorRef, Renderer2, SelectedFilterOptionsComponent],
|
||||
declarations: [
|
||||
SelectedFilterOptionsComponent,
|
||||
SelectedFiltersPipe,
|
||||
FilterResetWordingPipe,
|
||||
CheckAllChildOptionsSelectedPipe,
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SelectedFilterOptionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.value = [];
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('#ResetFilters', () => {
|
||||
let button: HTMLButtonElement;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'resetFilters').and.callThrough();
|
||||
component.resetStrategy = 'all';
|
||||
component.showResetButton = true;
|
||||
|
||||
button = fixture.debugElement.query(
|
||||
By.css('.isa-btn.isa-btn-primary-link.remove-all')
|
||||
).nativeElement;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be shown by default', () => {
|
||||
expect(button).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not be shwon if showResetButton is false', async () => {
|
||||
component.showResetButton = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
await fixture.whenStable();
|
||||
|
||||
const debugButtonEl = fixture.debugElement.query(
|
||||
By.css('.isa-btn.isa-btn-primary-link.remove-all')
|
||||
);
|
||||
button = debugButtonEl && debugButtonEl.nativeElement;
|
||||
|
||||
expect(button).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should deselect all filters when resetStrategy is all', () => {
|
||||
spyOn(component, 'deselectAllFilters').and.callThrough();
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.resetFilters).toHaveBeenCalled();
|
||||
expect(component.deselectAllFilters).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should revert to the default selection of filters when resetStrategy is default', () => {
|
||||
spyOn(component, 'resetToDefault').and.callThrough();
|
||||
component.resetStrategy = 'default';
|
||||
fixture.detectChanges();
|
||||
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.resetFilters).toHaveBeenCalled();
|
||||
expect(component.resetToDefault).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetToDefault', () => {
|
||||
let primaryFilters: PrimaryFilterOption[];
|
||||
let selectFilters: SelectFilter[];
|
||||
|
||||
beforeEach(() => {
|
||||
primaryFilters = primaryFiltersMock;
|
||||
selectFilters = mockFilters;
|
||||
});
|
||||
|
||||
it('should reset the filters to its initial state', () => {
|
||||
selectFilters[0].options[0].selected = true;
|
||||
selectFilters[0].options[0].initial_selected_state = false;
|
||||
|
||||
spyOn(component, 'resetToDefault').and.callThrough();
|
||||
|
||||
const result = component.resetToDefault(selectFilters);
|
||||
|
||||
expect(result[0].options[0].selected).toBe(false);
|
||||
expect(result[0].options[0].initial_selected_state).toBe(false);
|
||||
expect(result.length).toEqual(selectFilters.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deselectAllFilters', () => {
|
||||
let primaryFilters: PrimaryFilterOption[];
|
||||
let selectFilters: SelectFilter[];
|
||||
|
||||
beforeEach(() => {
|
||||
primaryFilters = primaryFiltersMock;
|
||||
selectFilters = mockFilters;
|
||||
});
|
||||
|
||||
it('should deselect all filters', () => {
|
||||
spyOn(component, 'deselectAllFilters').and.callThrough();
|
||||
|
||||
const result = component.deselectAllFilters(selectFilters);
|
||||
|
||||
expect(
|
||||
result.every((filter) => {
|
||||
if (filter.options) {
|
||||
return filter.options.every((o) => !o.selected);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
expect(result.length).toEqual(selectFilters.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { Filter, SelectFilterOption } from '../../models';
|
||||
import { cloneFilter } from '../../utils';
|
||||
import { FilterResetStrategy } from './defs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-selected-filter-options',
|
||||
@@ -21,11 +22,13 @@ import { cloneFilter } from '../../utils';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SelectedFilterOptionsComponent implements AfterViewInit {
|
||||
@Input()
|
||||
value: Filter[];
|
||||
@Input() value: Filter[];
|
||||
@Input() module: 'Customer' | 'Branch' = 'Branch';
|
||||
@Input() resetStrategy: FilterResetStrategy = 'all';
|
||||
@Input() showResetButton = true;
|
||||
|
||||
@Output() filterChanged = new EventEmitter<Filter[]>();
|
||||
@Output() reset = new EventEmitter<void>();
|
||||
|
||||
@ViewChildren('filteredItem') items: QueryList<ElementRef>;
|
||||
|
||||
@@ -61,8 +64,18 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
clearAllFilters() {
|
||||
this.filterChanged.emit(this.deselectAllFilters(this.value));
|
||||
resetFilters() {
|
||||
switch (this.resetStrategy) {
|
||||
case 'default':
|
||||
this.filterChanged.emit(this.resetToDefault(this.value));
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
this.filterChanged.emit(this.deselectAllFilters(this.value));
|
||||
break;
|
||||
}
|
||||
|
||||
this.reset.emit();
|
||||
}
|
||||
|
||||
clearFilter(filter: SelectFilterOption) {
|
||||
@@ -106,7 +119,7 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
|
||||
this.filterChanged.emit(clonedFilter);
|
||||
}
|
||||
|
||||
private deselectAllFilters(filters: Filter[]): Filter[] {
|
||||
deselectAllFilters(filters: Filter[]): Filter[] {
|
||||
return filters.reduce((acc, curr) => {
|
||||
if (Array.isArray(curr.options)) {
|
||||
curr.options.forEach((option) => this.deselect(option));
|
||||
@@ -115,6 +128,21 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
|
||||
}, []);
|
||||
}
|
||||
|
||||
resetToDefault(filters: Filter[]): Filter[] {
|
||||
const updatedFilters = filters.map((filter) => {
|
||||
let updatedOptions = filter.options || [];
|
||||
if (filter.options) {
|
||||
updatedOptions = filter.options.map((f) => ({
|
||||
...this.resetToDefaultValue(f),
|
||||
}));
|
||||
}
|
||||
|
||||
return { ...filter, options: updatedOptions };
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
}
|
||||
|
||||
private deselectAllMatches(filter: SelectFilterOption, match?: string) {
|
||||
if (filter.name === match) {
|
||||
filter.selected = false;
|
||||
@@ -142,6 +170,18 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
|
||||
return filter;
|
||||
}
|
||||
|
||||
private resetToDefaultValue(filter: SelectFilterOption): SelectFilterOption {
|
||||
return {
|
||||
...filter,
|
||||
selected: filter.initial_selected_state,
|
||||
options: [
|
||||
...(filter.options
|
||||
? filter.options.map((option) => this.resetToDefaultValue(option))
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private hasNestedOptions(filter: SelectFilterOption): boolean {
|
||||
return filter && Array.isArray(filter.options) && !!filter.options.length;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { FilterResetWordingPipe } from './filter-reset-wording.pipe';
|
||||
|
||||
describe('#FilterResetWordingPipe', () => {
|
||||
let pipe: FilterResetWordingPipe;
|
||||
|
||||
beforeEach(() => {
|
||||
pipe = new FilterResetWordingPipe();
|
||||
});
|
||||
|
||||
it('should show Alle Filter Entfernen for the default strategy', () => {
|
||||
const result = pipe.transform('all');
|
||||
expect(result).toEqual('Alle Filter Entfernen');
|
||||
});
|
||||
|
||||
it('should show Filter zurücksetzen for the default strategy', () => {
|
||||
const result = pipe.transform('default');
|
||||
expect(result).toEqual('Filter zurücksetzen');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { FilterResetStrategy } from '../components/defs';
|
||||
|
||||
@Pipe({
|
||||
name: 'filterResetWording',
|
||||
})
|
||||
export class FilterResetWordingPipe implements PipeTransform {
|
||||
RESET_TO_DEFAULT_WORDING = 'Filter zurücksetzen';
|
||||
RESET_All_WORDING = 'Alle Filter Entfernen';
|
||||
|
||||
transform(strategy: FilterResetStrategy): string {
|
||||
if (strategy === 'default') {
|
||||
return this.RESET_TO_DEFAULT_WORDING;
|
||||
}
|
||||
|
||||
return this.RESET_All_WORDING;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// start:ng42.barrel
|
||||
export * from './check-all-selected.pipe';
|
||||
export * from './selected-filters.pipe';
|
||||
export * from './filter-reset-wording.pipe';
|
||||
// end:ng42.barrel
|
||||
|
||||
|
||||
@@ -2,15 +2,25 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IconModule } from '@libs/ui';
|
||||
import { SelectedFilterOptionsComponent } from './components';
|
||||
import { SelectedFiltersPipe, CheckAllChildOptionsSelectedPipe} from './pipe';
|
||||
import {
|
||||
SelectedFiltersPipe,
|
||||
CheckAllChildOptionsSelectedPipe,
|
||||
FilterResetWordingPipe,
|
||||
} from './pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, IconModule],
|
||||
exports: [SelectedFilterOptionsComponent, CheckAllChildOptionsSelectedPipe],
|
||||
declarations:
|
||||
[SelectedFilterOptionsComponent,
|
||||
exports: [
|
||||
SelectedFilterOptionsComponent,
|
||||
CheckAllChildOptionsSelectedPipe,
|
||||
FilterResetWordingPipe,
|
||||
],
|
||||
declarations: [
|
||||
SelectedFilterOptionsComponent,
|
||||
SelectedFiltersPipe,
|
||||
CheckAllChildOptionsSelectedPipe],
|
||||
FilterResetWordingPipe,
|
||||
CheckAllChildOptionsSelectedPipe,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
export class SelectedFilterOptionsModule {}
|
||||
|
||||
@@ -3,4 +3,5 @@ export interface PrimaryFilterOption {
|
||||
id: string;
|
||||
key: string;
|
||||
selected: boolean;
|
||||
initial_selected_state?: boolean;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<ng-container *ngIf="pendingFilters$ | async as filters">
|
||||
<app-selected-filter-options
|
||||
*ngIf="hasSelectedFilter$ | async"
|
||||
[resetStrategy]="resetStrategy"
|
||||
[showResetButton]="filtersChangedFromDefault$ | async"
|
||||
(reset)="onResetFilters()"
|
||||
[value]="filters"
|
||||
[module]="'Customer'"
|
||||
(filterChanged)="onFiltersChange($event)"
|
||||
|
||||
@@ -13,10 +13,11 @@ import { slideIn } from 'apps/sales/src/app/core/overlay';
|
||||
import { Observable, BehaviorSubject, interval } from 'rxjs';
|
||||
import { Filter, SelectFilter } from '../../../filter';
|
||||
import { ShelfFilterService } from '../../services/shelf-filter.service';
|
||||
import { startWith, first } from 'rxjs/operators';
|
||||
import { startWith, first, tap } from 'rxjs/operators';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { ShelfSearchInputComponent } from '../../pages/shelf-search/search';
|
||||
import { cloneFilter } from '../../../filter/utils';
|
||||
import { FilterResetStrategy } from '../../../filter/selected-filter-options/components/defs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shelf-filter',
|
||||
@@ -27,6 +28,7 @@ import { cloneFilter } from '../../../filter/utils';
|
||||
})
|
||||
export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
animate = 'in';
|
||||
resetStrategy: FilterResetStrategy = 'default';
|
||||
|
||||
@ViewChild('selectionContainer', { static: false })
|
||||
selectionContainer: ElementRef;
|
||||
@@ -42,6 +44,7 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
selectedFilter$: Observable<Filter>;
|
||||
pendingFilters$ = new BehaviorSubject<Filter[]>(null);
|
||||
hasSelectedFilter$: Observable<boolean>;
|
||||
filtersChangedFromDefault$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private overlayRef: IsaOverlayRef,
|
||||
@@ -55,6 +58,7 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
this.getSelectedFilter();
|
||||
this.getPendingFilters();
|
||||
this.getHasSelectedFilters();
|
||||
this.getFiltersChangedFromDefault();
|
||||
}
|
||||
|
||||
ngOnDestroy() {}
|
||||
@@ -75,6 +79,10 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
this.hasSelectedFilter$ = this.filterService.hasSelectedFilters$;
|
||||
}
|
||||
|
||||
getFiltersChangedFromDefault() {
|
||||
this.filtersChangedFromDefault$ = this.filterService.filtersChangedFromDefault$;
|
||||
}
|
||||
|
||||
onFiltersChange(updatedFilters: SelectFilter[]) {
|
||||
this.pendingFilters$.next(updatedFilters.map(cloneFilter));
|
||||
this.setSearchFocus();
|
||||
@@ -144,6 +152,13 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
onResetFilters() {
|
||||
if (this.resetStrategy === 'default') {
|
||||
this.filterService.resetPrimaryFilters();
|
||||
this.filterService.resetSelectedFilters();
|
||||
}
|
||||
}
|
||||
|
||||
private initiateSearchAndCloseOverlay() {
|
||||
if (this.searchInput) {
|
||||
const searchbarValue =
|
||||
|
||||
@@ -1 +1,43 @@
|
||||
describe('#ShelfFilterService', () => {});
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { SearchStateFacade } from '@shelf-store';
|
||||
import { ShelfFilterService } from './shelf-filter.service';
|
||||
import { mockFilters } from '../shared/mockdata';
|
||||
|
||||
fdescribe('#ShelfFilterService', () => {
|
||||
let service: ShelfFilterService;
|
||||
let facade: jasmine.SpyObj<SearchStateFacade>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: SearchStateFacade,
|
||||
useValue: jasmine.createSpyObj('searchStateFacade', [
|
||||
'selectFilters$',
|
||||
'primaryFilterChangedFromDefault$',
|
||||
]),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
spyOn(
|
||||
ShelfFilterService.prototype,
|
||||
'initPendingFilters'
|
||||
).and.callFake(() => {});
|
||||
|
||||
facade = TestBed.get(SearchStateFacade);
|
||||
service = TestBed.get(ShelfFilterService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('#FiltersChangedFromDefault', () => {
|
||||
it('should return true if the primary Filters changed', async () => {
|
||||
const mockSelectFilters = mockFilters;
|
||||
});
|
||||
it('should return true if the pending Filters changed', () => {});
|
||||
it('should return false if neither pending nor primary filters changed', () => {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { Observable, BehaviorSubject, Subject, combineLatest } from 'rxjs';
|
||||
import { SelectFilter, Filter, SelectFilterOption } from '../../filter';
|
||||
import { tap, startWith, map, filter } from 'rxjs/operators';
|
||||
import {
|
||||
tap,
|
||||
startWith,
|
||||
map,
|
||||
filter,
|
||||
withLatestFrom,
|
||||
first,
|
||||
} from 'rxjs/operators';
|
||||
import { SearchStateFacade } from '../../../store/customer';
|
||||
import { flatten } from '../../../shared/utils';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
@@ -32,6 +39,24 @@ export class ShelfFilterService implements OnDestroy {
|
||||
return this.pendingFilters$.pipe(map(this.computeHasSelectedFilters));
|
||||
}
|
||||
|
||||
get filtersChangedFromDefault$(): Observable<boolean> {
|
||||
return this.pendingFilters$.pipe(
|
||||
filter((pendingFilters) => !isNullOrUndefined(pendingFilters)),
|
||||
withLatestFrom(this.searchStateFacade.primaryFilterChangedFromDefault$),
|
||||
map(([pendingFilters, primaryFiltersChanged]) => {
|
||||
return pendingFilters.some((f) =>
|
||||
f.options
|
||||
? f.options.some((o) =>
|
||||
isNullOrUndefined(o.initial_selected_state)
|
||||
? o.selected !== false
|
||||
: o.selected !== o.initial_selected_state
|
||||
)
|
||||
: primaryFiltersChanged
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
@@ -54,6 +79,14 @@ export class ShelfFilterService implements OnDestroy {
|
||||
this.searchStateFacade.setSelectedFilters(updatedFilters);
|
||||
}
|
||||
|
||||
resetPrimaryFilters(): void {
|
||||
this.searchStateFacade.resetPrimaryFilters();
|
||||
}
|
||||
|
||||
resetSelectedFilters() {
|
||||
this.searchStateFacade.resetSelectedFilters();
|
||||
}
|
||||
|
||||
public overlayClosed() {
|
||||
this.resetFilters$.next();
|
||||
this.overlayClosed$.next();
|
||||
|
||||
@@ -11,41 +11,49 @@ export const mockFilters: SelectFilter[] = [
|
||||
id: 'abgeholt',
|
||||
name: 'abgeholt',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'storniert Kunde',
|
||||
name: 'storniert Kunde',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'storniert Lieferant',
|
||||
name: 'storniert Lieferant',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'storniert',
|
||||
name: 'storniert',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'ans Lager (nicht abgeholt)',
|
||||
name: 'ans Lager (nicht abgeholt)',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'zugestellt',
|
||||
name: 'abgeholt',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'nicht lieferbar',
|
||||
name: 'nicht lieferbar',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'derzeit nicht lieferbar',
|
||||
name: 'derzeit nicht lieferbar',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -59,31 +67,37 @@ export const mockFilters: SelectFilter[] = [
|
||||
id: 'Online',
|
||||
name: 'Online',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'Hugendubel.de',
|
||||
name: 'Hugendubel.de',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'Filiale',
|
||||
name: 'Filiale',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'HSC',
|
||||
name: 'HSC',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'Amazon',
|
||||
name: 'Amazon',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'Etsy',
|
||||
name: 'Etsy',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -97,21 +111,25 @@ export const mockFilters: SelectFilter[] = [
|
||||
id: 'Kundenkarte',
|
||||
name: 'Kundenkarte',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'Onlinekonto',
|
||||
name: 'Onlinekonto',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'B2B',
|
||||
name: 'B2B',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
id: 'Gastkunde',
|
||||
name: 'Gastkunde',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -6,11 +6,13 @@ export const primaryFiltersMock: PrimaryFilterOption[] = [
|
||||
id: 'filter1',
|
||||
key: 'One',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
{
|
||||
name: 'Primary Filter Two',
|
||||
id: 'filter2',
|
||||
key: 'Two',
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,5 +9,6 @@ export function inputDtoToPrimaryFilterOption(
|
||||
key: inputDTO.key,
|
||||
name: inputDTO.label,
|
||||
selected: false,
|
||||
initial_selected_state: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export function optionDtoToSelectFilterOption(
|
||||
name: optionDto.label,
|
||||
id: optionDto.key || optionDto.value,
|
||||
selected: optionDto.selected || false,
|
||||
initial_selected_state: optionDto.selected || false,
|
||||
expanded: false,
|
||||
options:
|
||||
optionDto.values && optionDto.values.map(optionDtoToSelectFilterOption),
|
||||
|
||||
@@ -38,11 +38,21 @@ export const setSelectFilters = createAction(
|
||||
props<{ id: number; filters: SelectFilter[] }>()
|
||||
);
|
||||
|
||||
export const resetSelectFilters = createAction(
|
||||
`${prefix} Reset Selected Filters`,
|
||||
props<{ id: number }>()
|
||||
);
|
||||
|
||||
export const setPrimaryFilters = createAction(
|
||||
`${prefix} Set Primary Filters`,
|
||||
props<{ id: number; filters: PrimaryFilterOption[] }>()
|
||||
);
|
||||
|
||||
export const resetPrimaryFilters = createAction(
|
||||
`${prefix} Reset Primary Filters`,
|
||||
props<{ id: number }>()
|
||||
);
|
||||
|
||||
export const setInput = createAction(
|
||||
`${prefix} Search Input`,
|
||||
props<{ id: number; input: string }>()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
|
||||
import { SearchEffects } from './search.effects';
|
||||
import { provideMockStore, MockStore } from '@ngrx/store/testing';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
StrictHttpResponse,
|
||||
ResponseArgsOfIEnumerableOfInputDTO,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { hot, cold } from 'jasmine-marbles';
|
||||
import * as actions from './search.actions';
|
||||
import * as processActions from 'apps/sales/src/app/core/store/actions/process.actions';
|
||||
import { Store as NgxsStore, ofActionDispatched } from '@ngxs/store';
|
||||
import { Store as NgxsStore } from '@ngxs/store';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions, NgxsModule } from '@ngxs/store';
|
||||
import { BranchService } from '@sales/core-services';
|
||||
|
||||
@@ -2,12 +2,16 @@ import { SearchStateFacade } from './search.facade';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { Store as NgxsStore } from '@ngxs/store';
|
||||
import { primaryFiltersMock } from 'apps/sales/src/app/modules/shelf/shared/mockdata';
|
||||
import {
|
||||
primaryFiltersMock,
|
||||
mockFilters,
|
||||
} from 'apps/sales/src/app/modules/shelf/shared/mockdata';
|
||||
import { PrimaryFilterOption } from 'apps/sales/src/app/modules/shelf/defs';
|
||||
import { of, Observable } from 'rxjs';
|
||||
import * as actions from './search.actions';
|
||||
import { SelectFilter } from 'apps/sales/src/app/modules/filter';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { hot, cold } from 'jasmine-marbles';
|
||||
|
||||
fdescribe('SearchFacade', () => {
|
||||
let facade: SearchStateFacade;
|
||||
@@ -117,4 +121,85 @@ fdescribe('SearchFacade', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#filtersChangedFromDefault$', () => {
|
||||
let selectFilters: SelectFilter[];
|
||||
let primaryFilters: PrimaryFilterOption[];
|
||||
|
||||
beforeEach(() => {
|
||||
selectFilters = mockFilters;
|
||||
primaryFilters = primaryFiltersMock;
|
||||
});
|
||||
|
||||
it('should return true if the select filters are changed', async () => {
|
||||
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
|
||||
of(primaryFilters)
|
||||
);
|
||||
const updatedSelectFilters = [
|
||||
...selectFilters.map((filter) =>
|
||||
filter.options
|
||||
? {
|
||||
...filter,
|
||||
options: filter.options.map((o, i) =>
|
||||
!i ? { ...o, selected: !o.initial_selected_state } : o
|
||||
),
|
||||
}
|
||||
: filter
|
||||
),
|
||||
];
|
||||
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
|
||||
of(updatedSelectFilters)
|
||||
);
|
||||
const result = await facade.filtersChangedFromDefault$
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
it('should return false if the select filters are unchanged', async () => {
|
||||
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
|
||||
of(primaryFilters)
|
||||
);
|
||||
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
|
||||
of(selectFilters)
|
||||
);
|
||||
const result = await facade.filtersChangedFromDefault$
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
xit('should return false if the primary filters are unchanged', () => {
|
||||
const selectFilters$ = hot('--a', { a: selectFilters });
|
||||
const primaryFilters$ = hot('--a', { a: primaryFilters });
|
||||
const expected = cold('--a', { b: false });
|
||||
|
||||
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
|
||||
selectFilters$
|
||||
);
|
||||
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
|
||||
primaryFilters$
|
||||
);
|
||||
|
||||
expect(facade.filtersChangedFromDefault$).toBeObservable(expected);
|
||||
});
|
||||
|
||||
xit('should return true if the primary filters are changed', async () => {
|
||||
const selectFilters$ = hot('--a', { a: selectFilters });
|
||||
const primaryFilters$ = hot('--a', {
|
||||
a: primaryFilters.map((f, i) => (!i ? { ...f, selected: true } : f)),
|
||||
});
|
||||
const expected = cold('--a', { b: true });
|
||||
|
||||
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
|
||||
selectFilters$
|
||||
);
|
||||
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
|
||||
primaryFilters$
|
||||
);
|
||||
|
||||
expect(facade.filtersChangedFromDefault$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
|
||||
import { Store as NgxsStore } from '@ngxs/store';
|
||||
import * as actions from './search.actions';
|
||||
import * as selectors from './search.selectors';
|
||||
import { switchMap, filter, map, first, withLatestFrom } from 'rxjs/operators';
|
||||
import { switchMap, filter, map, first } from 'rxjs/operators';
|
||||
import { SharedSelectors } from 'apps/sales/src/app/core/store/selectors/shared.selectors';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import {
|
||||
@@ -90,6 +90,47 @@ export class SearchStateFacade {
|
||||
);
|
||||
}
|
||||
|
||||
get selectedFilterChangedFromDefault$() {
|
||||
return this.selectFilters$.pipe(
|
||||
map((selectFilters) => {
|
||||
return selectFilters.some((f) => {
|
||||
if (f.options) {
|
||||
return f.options.some((o) => {
|
||||
return isNullOrUndefined(o.initial_selected_state)
|
||||
? o.selected !== true
|
||||
: o.selected !== o.initial_selected_state;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get primaryFilterChangedFromDefault$() {
|
||||
return this.primaryFilters$.pipe(
|
||||
map((primaryFilters) => {
|
||||
return primaryFilters.some((pf) =>
|
||||
isNullOrUndefined(pf.initial_selected_state)
|
||||
? pf.selected !== false
|
||||
: pf.selected !== pf.initial_selected_state
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get filtersChangedFromDefault$(): Observable<boolean> {
|
||||
return combineLatest([
|
||||
this.selectedFilterChangedFromDefault$,
|
||||
this.primaryFilterChangedFromDefault$,
|
||||
]).pipe(
|
||||
map(([selectFiltersChanged, primaryFiltersChanged]) => {
|
||||
return selectFiltersChanged || primaryFiltersChanged;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get selectedFilter$(): Observable<{ [key: string]: string }> {
|
||||
return combineLatest([this.selectFilters$, this.primaryFilters$]).pipe(
|
||||
map(([selectFilters, primaryFilters]) => {
|
||||
@@ -216,6 +257,24 @@ export class SearchStateFacade {
|
||||
);
|
||||
}
|
||||
|
||||
async resetPrimaryFilters(id?: number) {
|
||||
let processId = id;
|
||||
if (typeof processId !== 'number') {
|
||||
processId = await this.getProcessId();
|
||||
}
|
||||
|
||||
this.store.dispatch(actions.resetPrimaryFilters({ id: processId }));
|
||||
}
|
||||
|
||||
async resetSelectedFilters(id?: number) {
|
||||
let processId = id;
|
||||
if (typeof processId !== 'number') {
|
||||
processId = await this.getProcessId();
|
||||
}
|
||||
|
||||
this.store.dispatch(actions.resetSelectFilters({ id: processId }));
|
||||
}
|
||||
|
||||
async fetchResult(id?: number) {
|
||||
let processId = id;
|
||||
if (typeof processId !== 'number') {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { SearchState, INITIAL_SEARCH_STATE } from './search.state';
|
||||
import { TypedAction } from '@ngrx/store/src/models';
|
||||
import { PrimaryFilterOption } from 'apps/sales/src/app/modules/shelf/defs';
|
||||
import { primaryFiltersMock } from 'apps/sales/src/app/modules/shelf/shared/mockdata';
|
||||
import {
|
||||
primaryFiltersMock,
|
||||
mockFilters,
|
||||
} from 'apps/sales/src/app/modules/shelf/shared/mockdata';
|
||||
import * as actions from './search.actions';
|
||||
import { searchReducer } from './search.reducer';
|
||||
|
||||
@@ -43,4 +46,60 @@ fdescribe('#SearchStateReducer', () => {
|
||||
expect(entity.filters.primaryFilters[1]).toEqual(primaryFiltersMock[1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPrimaryFilters', () => {
|
||||
let action: {
|
||||
id: number;
|
||||
} & TypedAction<string>;
|
||||
|
||||
beforeEach(() => {
|
||||
action = actions.resetPrimaryFilters({ id });
|
||||
});
|
||||
|
||||
it('should reset the primary Filters to their default state', () => {
|
||||
const updatedPrimaryFilters = primaryFiltersMock.map((primaryFilter, i) =>
|
||||
i % 2 === 0 ? primaryFilter : { ...primaryFilter, selected: true }
|
||||
);
|
||||
const setPrimaryFilterAction = actions.setPrimaryFilters({
|
||||
id,
|
||||
filters: updatedPrimaryFilters,
|
||||
});
|
||||
|
||||
let state = searchReducer(initialState, setPrimaryFilterAction);
|
||||
state = searchReducer(state, action);
|
||||
|
||||
expect(state.entities[id].filters.primaryFilters).toEqual(
|
||||
primaryFiltersMock
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetSelectedFilters', () => {
|
||||
let action: {
|
||||
id: number;
|
||||
} & TypedAction<string>;
|
||||
|
||||
beforeEach(() => {
|
||||
action = actions.resetSelectFilters({ id });
|
||||
});
|
||||
|
||||
it('should reset all selected filters to their default state', () => {
|
||||
const selectFilters = [
|
||||
...mockFilters.map((filter) => ({
|
||||
...filter,
|
||||
options: filter.options.map((o) => ({ ...o, selected: true })),
|
||||
})),
|
||||
];
|
||||
|
||||
const setSelectedFilters = actions.setSelectFilters({
|
||||
id,
|
||||
filters: selectFilters,
|
||||
});
|
||||
|
||||
let state = searchReducer(initialState, setSelectedFilters);
|
||||
state = searchReducer(state, action);
|
||||
|
||||
expect(state.entities[id].filters.selectedFilters).toEqual(mockFilters);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,6 +44,51 @@ const _searchReducer = createReducer(
|
||||
s
|
||||
)
|
||||
),
|
||||
on(actions.resetSelectFilters, (s, a) =>
|
||||
searchStateAdapter.updateOne(
|
||||
{
|
||||
id: a.id,
|
||||
changes: {
|
||||
filters: {
|
||||
...s.entities[a.id].filters,
|
||||
selectedFilters: s.entities[a.id].filters.selectedFilters.map(
|
||||
(filter) => {
|
||||
let options = filter.options || [];
|
||||
if (filter.options) {
|
||||
options = filter.options.map((o) => ({
|
||||
...o,
|
||||
selected: o.initial_selected_state,
|
||||
}));
|
||||
}
|
||||
|
||||
return { ...filter, options };
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
s
|
||||
)
|
||||
),
|
||||
on(actions.resetPrimaryFilters, (s, a) =>
|
||||
searchStateAdapter.updateOne(
|
||||
{
|
||||
id: a.id,
|
||||
changes: {
|
||||
filters: {
|
||||
...s.entities[a.id].filters,
|
||||
primaryFilters: s.entities[a.id].filters.primaryFilters.map(
|
||||
(primaryFilter) => ({
|
||||
...primaryFilter,
|
||||
selected: primaryFilter.initial_selected_state,
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
s
|
||||
)
|
||||
),
|
||||
on(actions.clearError, (s, a) =>
|
||||
searchStateAdapter.updateOne(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user