#685 Add Customer Theme to Shelf Filter Overlay

+ Set Focus on Searchbar Submit Button on Filter Selection
This commit is contained in:
Sebastian
2020-06-30 14:12:51 +02:00
parent 8df2ee5018
commit 8d68647d45
19 changed files with 187 additions and 37 deletions

View File

@@ -5,6 +5,7 @@
type="button"
(click)="selected = filter"
class="isa-btn isa-btn-block isa-btn-default isa-btn-l"
[class.customer]="module === 'Customer'"
[class.isa-mt-10]="!first"
[class.is-active]="filter === selected"
[class.isa-font-weight-emphasis]="filter !== selected"
@@ -12,7 +13,13 @@
>
<span>{{ filter.name }}</span>
<lib-icon
[name]="filter === selected ? 'Arrow_Next_Dark' : 'Arrow_Next_Grey'"
[name]="
filter === selected
? 'Arrow_Next_Dark'
: module === 'Customer'
? 'Arrow_Next'
: 'Arrow_Next_Grey'
"
height="17px"
></lib-icon>
</button>
@@ -21,6 +28,7 @@
<app-select-filter
*ngSwitchCase="'select'"
[options]="selected.options"
[module]="module"
[max]="selected.max"
(optionsChanged)="emitOnChange($event)"
>

View File

@@ -1,3 +1,5 @@
@import 'variables';
:host {
display: block;
}
@@ -18,7 +20,10 @@
}
.is-active {
background: #89949e;
background: $isa-branch;
}
.is-active.customer {
background: $isa-customer-light;
}
}

View File

@@ -27,7 +27,8 @@ import { cloneFilter } from '../../../utils';
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FilterGroupComponent implements OnInit, ControlValueAccessor, OnChanges {
export class FilterGroupComponent
implements OnInit, ControlValueAccessor, OnChanges {
@Input()
disabled: boolean;
@@ -43,13 +44,15 @@ export class FilterGroupComponent implements OnInit, ControlValueAccessor, OnCha
@Input()
selected: Filter;
@Input() module: 'Customer' | 'Branch' = 'Branch';
onChange: (value: Filter[]) => void;
onTouch: (value: Filter[]) => void;
trackByKeyOrName = (filter: Filter) => filter.key || filter.name;
constructor(private cdr: ChangeDetectorRef) { }
constructor(private cdr: ChangeDetectorRef) {}
ngOnChanges({ value, selected }: SimpleChanges): void {
if (value) {
@@ -63,7 +66,7 @@ export class FilterGroupComponent implements OnInit, ControlValueAccessor, OnCha
}
}
ngOnInit() { }
ngOnInit() {}
writeValue(obj: Filter[]): void {
if (obj !== this.value) {
@@ -98,7 +101,9 @@ export class FilterGroupComponent implements OnInit, ControlValueAccessor, OnCha
getSelectedFilter() {
if (this.value && this.selected) {
const index = this.value.findIndex((f) => this.selected.key === f.key || this.selected.name === f.name);
const index = this.value.findIndex(
(f) => this.selected.key === f.key || this.selected.name === f.name
);
const filter = this.value[index];
return { index, filter };

View File

@@ -1,5 +1,8 @@
<div class="select-filter-option-wrapper">
<div class="isa-form-group isa-pt-15 isa-pb-15">
<div
class="isa-form-group isa-pt-15 isa-pb-15"
[class.customer]="module === 'Customer'"
>
<input
type="checkbox"
[name]="option?.name"
@@ -22,6 +25,7 @@
</div>
<app-select-filter-option
[class.customer]=""
[class.expanded]="option?.expanded"
*ngFor="let childOption of option?.options; let index = index"
[option]="childOption"

View File

@@ -24,7 +24,9 @@ export class SelectFilterOptionComponent implements OnChanges {
@Output()
optionChanged = new EventEmitter<SelectFilterOption>();
constructor(private cdr: ChangeDetectorRef) { }
@Input() module: 'Customer' | 'Branch' = 'Branch';
constructor(private cdr: ChangeDetectorRef) {}
ngOnChanges({ option }: SimpleChanges): void {
if (option) {
@@ -40,14 +42,17 @@ export class SelectFilterOptionComponent implements OnChanges {
updateChildren: true,
}
) {
this.option.selected = typeof val === 'boolean' ? val : !this.option.selected;
this.option.selected =
typeof val === 'boolean' ? val : !this.option.selected;
if (updateChildren && Array.isArray(this.option.options)) {
const selectFn = (option: SelectFilterOption): SelectFilterOption => {
return {
...option,
selected: this.option.selected,
options: Array.isArray(option.options) ? option.options.map(selectFn) : option.options,
options: Array.isArray(option.options)
? option.options.map(selectFn)
: option.options,
};
};
this.option.options = this.option.options.map(selectFn);
@@ -71,7 +76,9 @@ export class SelectFilterOptionComponent implements OnChanges {
childChanged(option: SelectFilterOption, index: number) {
this.option.options[index] = option;
const selectedTrueChilds = this.option.options.filter((child) => !!child.selected);
const selectedTrueChilds = this.option.options.filter(
(child) => !!child.selected
);
const selected = selectedTrueChilds.length === this.option.options.length;
if (this.option.selected !== selected) {

View File

@@ -6,6 +6,7 @@
<button
type="button"
class="isa-btn isa-p-0 isa-font-lightgrey isa-font-weight-emphasis"
[class.isa-font-color-customer]="module === 'Customer'"
*ngIf="!max"
(click)="selectAll()"
>
@@ -21,6 +22,7 @@
>
<ng-container *ngFor="let option of options; let index = index">
<app-select-filter-option
[module]="module"
[option]="option"
(optionChanged)="optionChanged($event, index)"
>

View File

@@ -14,7 +14,10 @@ import {
} from '@angular/core';
import { SelectFilterOption } from '../../../models';
import { BehaviorSubject, interval, Subscription } from 'rxjs';
import { cloneSelectFilterOption, flattenSelectFilterOption } from '../../../utils';
import {
cloneSelectFilterOption,
flattenSelectFilterOption,
} from '../../../utils';
@Component({
selector: 'app-select-filter',
@@ -22,7 +25,8 @@ import { cloneSelectFilterOption, flattenSelectFilterOption } from '../../../uti
styleUrls: ['select-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestroy {
export class SelectFilterComponent
implements OnChanges, AfterViewInit, OnDestroy {
@Input()
options: SelectFilterOption[];
@@ -32,6 +36,8 @@ export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestro
@Input()
max?: number;
@Input() module: 'Customer' | 'Branch' = 'Branch';
@ViewChild('optionsContainer', { static: false })
optionsContainer: ElementRef;
@@ -51,7 +57,9 @@ export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestro
ngAfterViewInit(): void {
this.updateScrollButton();
this.updateScrollPositionPersantageIntervalSub = interval(500).subscribe(() => this.updateScrollButton());
this.updateScrollPositionPersantageIntervalSub = interval(
500
).subscribe(() => this.updateScrollButton());
}
updateScrollButton() {
@@ -90,7 +98,9 @@ export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestro
return {
...option,
selected: !this.allSelected$.value,
options: Array.isArray(option.options) ? option.options.map(selectFn) : option.options,
options: Array.isArray(option.options)
? option.options.map(selectFn)
: option.options,
};
};
@@ -104,7 +114,9 @@ export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestro
optionChanged(option: SelectFilterOption, index: number) {
let options = [...this.options];
options[index] = option;
const selectedOptions = flattenSelectFilterOption(options).filter((o) => o.selected);
const selectedOptions = flattenSelectFilterOption(options).filter(
(o) => o.selected
);
if (this.max > 0 && selectedOptions.length > this.max) {
const unselectOptions = selectedOptions
@@ -112,7 +124,9 @@ export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestro
.slice(0, selectedOptions.length - this.max);
const updateOption = (o: SelectFilterOption): SelectFilterOption =>
unselectOptions.some((s) => s.key === o.key && s.name === o.name) ? { ...o, selected: false } : o;
unselectOptions.some((s) => s.key === o.key && s.name === o.name)
? { ...o, selected: false }
: o;
options = options.map(updateOption);
}
@@ -147,7 +161,9 @@ export class SelectFilterComponent implements OnChanges, AfterViewInit, OnDestro
const scrollTop = container.scrollTop;
const height = container.clientHeight;
const scrollPositionPersantage = Math.round((100 / (scrollHeight - height)) * scrollTop);
const scrollPositionPersantage = Math.round(
(100 / (scrollHeight - height)) * scrollTop
);
if (this.scrollPositionPersantage !== scrollPositionPersantage) {
this.scrollPositionPersantage = scrollPositionPersantage;
this.cdr.markForCheck();

View File

@@ -1,24 +1,48 @@
<div class="container" *ngIf="value">
<div class="clear-all">
<button class="isa-btn isa-btn-primary-link remove-all isa-pt-0 isa-pb-0 isa-pl-15 isa-pr-15"
(click)="clearAllFilters()">
<button
class="isa-btn isa-btn-primary-link remove-all isa-pt-0 isa-pb-0 isa-pl-15 isa-pr-15"
(click)="clearAllFilters()"
>
Alle Filter Entfernen
</button>
</div>
<ng-container *ngFor="let filter of value">
<ng-container *ngTemplateOutlet="optionTmpl; context: { $implicit: filter }"></ng-container>
<ng-container
*ngTemplateOutlet="optionTmpl; context: { $implicit: filter }"
></ng-container>
</ng-container>
<ng-container [ngSwitch]="isCollapsed" *ngIf="items && items.length > collapsedStateNumberOfSelectedFiltersShown">
<div class="expand c-pointer" *ngSwitchCase="true" (click)="updateCollapsed(false)">
<span>Mehr</span>
<lib-icon name="Arrow_More_Grey" class="isa-mt-0"></lib-icon>
<ng-container
[ngSwitch]="isCollapsed"
*ngIf="items && items.length > collapsedStateNumberOfSelectedFiltersShown"
>
<div
class="expand c-pointer"
*ngSwitchCase="true"
(click)="updateCollapsed(false)"
>
<span [class.isa-font-color-customer]="module === 'Customer'">Mehr</span>
<lib-icon
[name]="module === 'Customer' ? 'Arrow_More' : 'Arrow_More_Grey'"
class="isa-mt-0"
></lib-icon>
</div>
<div class=" collapse c-pointer" *ngSwitchDefault (click)="updateCollapsed(true)">
<lib-icon [rotateBackwards]="true" class="isa-mt-0 isa-ml-12" name="Arrow_More_Grey">
<div
class="collapse c-pointer"
*ngSwitchDefault
(click)="updateCollapsed(true)"
>
<lib-icon
[rotateBackwards]="true"
class="isa-mt-0 isa-ml-12"
[name]="module === 'Customer' ? 'Arrow_More' : 'Arrow_More_Grey'"
>
</lib-icon>
<span>Weniger</span>
<span [class.isa-font-color-customer]="module === 'Customer'"
>Weniger</span
>
</div>
</ng-container>
</div>
@@ -26,10 +50,17 @@
<ng-template #optionTmpl let-filter>
<div *ngIf="filter.selected" class="selected-filter" #filteredItem>
<span class="filter-name">{{ filter.name }}</span>
<lib-icon (click)="clearFilter(filter); (false)" name="close-branch"></lib-icon>
<lib-icon
(click)="clearFilter(filter); (false)"
[name]="module === 'Customer' ? 'close' : 'close-branch'"
></lib-icon>
</div>
<ng-container *ngFor="let option of filter.options | checkAllChildOptionsSelected">
<ng-container *ngTemplateOutlet="optionTmpl; context: { $implicit: option }"></ng-container>
<ng-container
*ngFor="let option of filter.options | checkAllChildOptionsSelected"
>
<ng-container
*ngTemplateOutlet="optionTmpl; context: { $implicit: option }"
></ng-container>
</ng-container>
</ng-template>
</ng-template>

View File

@@ -58,7 +58,7 @@ $font-size: 18px;
}
lib-icon {
margin-top: 2px;
margin-top: 6px;
cursor: pointer;
}

View File

@@ -23,6 +23,7 @@ import { cloneFilter } from '../../utils';
export class SelectedFilterOptionsComponent implements AfterViewInit {
@Input()
value: Filter[];
@Input() module: 'Customer' | 'Branch' = 'Branch';
@Output() filterChanged = new EventEmitter<Filter[]>();

View File

@@ -1,6 +1,7 @@
<div class="input-wrapper" [class.extend-for-content]="mode === 'autocomplete'">
<input
focus
#searchbar
autocomplete="off"
class="isa-input isa-text-input"
[class.error]="!!errorMessage.length"
@@ -28,6 +29,7 @@
<div class="spinner isa-mt-16" *ngSwitchCase="true"></div>
<button
*ngSwitchDefault
#submitButton
class="isa-input-submit"
[class.scan]="!errorMessage && !(searchQuery$ | async) && isIPad"
type="submit"

View File

@@ -6,11 +6,14 @@ import {
Output,
EventEmitter,
ChangeDetectionStrategy,
ViewChild,
ElementRef,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { Subject, Observable } from 'rxjs';
import { isNullOrUndefined } from 'util';
import { FormControl } from '@angular/forms';
import { Button } from 'protractor';
@Component({
selector: 'app-shelf-searchbar',
@@ -22,6 +25,13 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
searchForm: FormControl;
searchQuery$: Observable<string>;
@ViewChild('submitButton', { static: false }) submitButton: ElementRef<
HTMLButtonElement
>;
@ViewChild('searchbar', { static: false }) searchbar: ElementRef<
HTMLInputElement
>;
@Input() isIPad = false;
@Input() isFetchingData = false;
@Input() mode: 'standalone' | 'autocomplete' = 'standalone';
@@ -84,6 +94,19 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
}
}
setFocus(target: 'input' | 'button') {
if (target === 'button') {
if (this.submitButton.nativeElement) {
return this.submitButton.nativeElement.focus();
}
}
if (target === 'input') {
if (this.searchbar.nativeElement) {
return this.searchbar.nativeElement.focus();
}
}
}
private validateSearchInputBeforeSubmit(searchInput: string): boolean {
return !isNullOrUndefined(searchInput) && searchInput.length >= 1;
}

View File

@@ -2,12 +2,13 @@ import {
Component,
OnInit,
ChangeDetectionStrategy,
Renderer2,
Output,
EventEmitter,
} from '@angular/core';
import { ShelfPrimaryFilterOptions } from '../../../defs';
import { Observable } from 'rxjs';
import { SearchStateFacade } from 'apps/sales/src/app/store/customer';
import { map, take } from 'rxjs/operators';
import { take } from 'rxjs/operators';
@Component({
selector: 'app-shelf-primary-filters',
@@ -18,6 +19,8 @@ import { map, take } from 'rxjs/operators';
export class ShelfPrimaryFiltersComponent implements OnInit {
primaryFilters$: Observable<ShelfPrimaryFilterOptions>;
@Output() change = new EventEmitter<void>();
constructor(private searchStateFacade: SearchStateFacade) {}
ngOnInit() {
@@ -38,6 +41,7 @@ export class ShelfPrimaryFiltersComponent implements OnInit {
const updatedValue = !currentFilters[params.identifier];
this.updateSelectedFilters({ [params.identifier]: updatedValue });
this.change.emit();
});
}

View File

@@ -15,7 +15,9 @@
</button>
<h2 class="isa-filter-title">Filter</h2>
<app-shelf-primary-filters></app-shelf-primary-filters>
<app-shelf-primary-filters
(change)="setSearchFocus()"
></app-shelf-primary-filters>
<app-shelf-search-input></app-shelf-search-input>
<div class="d-flex flex-column mt-40">
@@ -23,6 +25,7 @@
<app-selected-filter-options
*ngIf="hasSelectedFilter$ | async"
[value]="filters"
[module]="'Customer'"
(filterChanged)="onFiltersChange($event)"
>
</app-selected-filter-options>
@@ -31,6 +34,7 @@
<app-filter-group
*ngIf="filters"
[value]="filters"
[module]="'Customer'"
[selected]="selectedFilter$ | async"
(valueChanged)="onFiltersChange($event)"
(valueGroupChanged)="updateLastGroupChanged($event)"

View File

@@ -7,14 +7,17 @@ import {
Renderer2,
ElementRef,
ChangeDetectorRef,
QueryList,
} from '@angular/core';
import { IsaOverlayRef } from 'apps/sales/src/app/core/overlay/isa-overlay-ref';
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 { throttleTime, startWith } from 'rxjs/operators';
import { startWith } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { ShelfSearchbarComponent } from '../../components';
import { ShelfSearchInputComponent } from '../../pages/shelf-search/search';
@Component({
selector: 'app-shelf-filter',
@@ -29,6 +32,9 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
@ViewChild('selectionContainer', { static: false })
selectionContainer: ElementRef;
@ViewChild(ShelfSearchInputComponent, { static: false })
searchInput: ShelfSearchInputComponent;
checkHeightTimerSub = interval(100)
.pipe(startWith(0))
.subscribe(() => this.updateHeight());
@@ -72,12 +78,19 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
onFiltersChange(updatedFilters: SelectFilter[]) {
this.pendingFilters$.next(updatedFilters);
this.setSearchFocus();
}
updateLastGroupChanged(updatedGroup: Filter) {
this.filterService.lastFilterGroupChanged$.next(updatedGroup);
}
setSearchFocus() {
if (this.searchInput && this.searchInput.searchbar) {
this.searchInput.searchbar.setFocus('button');
}
}
applyFilters() {
if (this.pendingFilters$.value) {
this.filterService.updateFilters(this.pendingFilters$.value);

View File

@@ -27,6 +27,7 @@ import {
} from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
import { ShelfSearchbarComponent } from '../../../components';
@Component({
selector: 'app-shelf-search-input',
@@ -38,6 +39,9 @@ export class ShelfSearchInputComponent implements OnInit, OnDestroy {
@Input() allowScan = false;
@Input() hasAutocomplete = true;
@ViewChild(ShelfSearchbarComponent, { static: false })
searchbar: ShelfSearchbarComponent;
destroy$ = new Subject();
isFetchingData$ = new BehaviorSubject<boolean>(false);

View File

@@ -1,3 +1,5 @@
@import 'variables';
.isa-font-weight-default {
font-weight: $font-weight;
}
@@ -17,3 +19,7 @@
.isa-font-lightgrey {
color: $text-lightgrey;
}
.isa-font-color-customer {
color: $isa-customer;
}

View File

@@ -14,7 +14,9 @@ $text-black: #000000;
$isa-red: #f70400;
$isa-white: #ffffff;
$isa-lightgray: #e2e2e2;
$isa-branch: #89949e;
$isa-customer: #557596;
$isa-customer-light: #a3b4c8;
$isa-customer-active: #59647a;
$isa-customer-active: #1f466c;

View File

@@ -1,3 +1,5 @@
@import 'variables';
.isa-form-group {
font-family: $font-family;
font-weight: $font-weight;
@@ -37,4 +39,15 @@
background-repeat: no-repeat;
border: 2px solid #89949e;
}
&.customer {
[type='checkbox'] + label::before {
border-color: $isa-customer-light;
}
[type='checkbox']:checked + label::before {
border-color: $isa-customer;
background-image: url(assets/images/Check_f.svg);
}
}
}