From 16243f92f02725cde1702ff1724b26ab7d25ca53 Mon Sep 17 00:00:00 2001 From: Denis Ezhov Date: Fri, 23 Oct 2020 17:45:21 +0300 Subject: [PATCH] currency filter (#305) * something * working something * good working something * currency filter * prettier * save on close * fixes * more fixes * fix * remove test data --- .../analytics-search-filters.module.ts | 3 +- .../currency-filter.component.html | 12 ++ .../currency-filter.component.ts | 17 +++ .../currency-filter/currency-filter.module.ts | 17 +++ .../filters/currency-filter/index.ts | 2 + .../components/filters/filters.module.ts | 3 +- src/assets/i18n/currency-filter/ru.json | 3 + .../filter-button-actions.component.html | 7 +- .../filter-button-actions.component.ts | 5 +- ...pItemsToLabel.ts => map-items-to-label.ts} | 0 .../multiselect-filter.component.html | 2 +- .../multiselect-filter.component.ts | 14 +- .../filters/radio-group-filter/index.ts | 3 + .../radio-group-filter/map-item-to-label.ts | 17 +++ .../radio-group-filter-option/index.ts | 1 + .../radio-group-filter-option.component.html | 3 + .../radio-group-filter-option.component.ts | 30 +++++ .../radio-group-filter.component.html | 12 ++ .../radio-group-filter.component.scss | 3 + .../radio-group-filter.component.ts | 122 ++++++++++++++++++ .../radio-group-filter.module.ts | 34 +++++ 21 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 src/app/shared/components/filters/currency-filter/currency-filter.component.html create mode 100644 src/app/shared/components/filters/currency-filter/currency-filter.component.ts create mode 100644 src/app/shared/components/filters/currency-filter/currency-filter.module.ts create mode 100644 src/app/shared/components/filters/currency-filter/index.ts create mode 100644 src/assets/i18n/currency-filter/ru.json rename src/components/filters/multiselect-filter/{mapItemsToLabel.ts => map-items-to-label.ts} (100%) create mode 100644 src/components/filters/radio-group-filter/index.ts create mode 100644 src/components/filters/radio-group-filter/map-item-to-label.ts create mode 100644 src/components/filters/radio-group-filter/radio-group-filter-option/index.ts create mode 100644 src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.html create mode 100644 src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.ts create mode 100644 src/components/filters/radio-group-filter/radio-group-filter.component.html create mode 100644 src/components/filters/radio-group-filter/radio-group-filter.component.scss create mode 100644 src/components/filters/radio-group-filter/radio-group-filter.component.ts create mode 100644 src/components/filters/radio-group-filter/radio-group-filter.module.ts diff --git a/src/app/sections/payment-section/analytics/analytics-search-filters/analytics-search-filters.module.ts b/src/app/sections/payment-section/analytics/analytics-search-filters/analytics-search-filters.module.ts index c3405f8f..8fb630c2 100644 --- a/src/app/sections/payment-section/analytics/analytics-search-filters/analytics-search-filters.module.ts +++ b/src/app/sections/payment-section/analytics/analytics-search-filters/analytics-search-filters.module.ts @@ -5,10 +5,11 @@ import { FlexModule } from '@angular/flex-layout'; import { FilterShopsModule } from '@dsh/app/shared/*'; import { DaterangeFilterModule } from '@dsh/components/filters/daterange-filter'; +import { CurrencyFilterModule } from '../../../../shared/components/filters/currency-filter'; import { AnalyticsSearchFiltersComponent } from './analytics-search-filters.component'; @NgModule({ - imports: [CommonModule, DaterangeFilterModule, FilterShopsModule, FlexModule], + imports: [CommonModule, DaterangeFilterModule, FilterShopsModule, FlexModule, CurrencyFilterModule], exports: [AnalyticsSearchFiltersComponent], declarations: [AnalyticsSearchFiltersComponent], }) diff --git a/src/app/shared/components/filters/currency-filter/currency-filter.component.html b/src/app/shared/components/filters/currency-filter/currency-filter.component.html new file mode 100644 index 00000000..2438a6fa --- /dev/null +++ b/src/app/shared/components/filters/currency-filter/currency-filter.component.html @@ -0,0 +1,12 @@ + + + {{ currency }} + + diff --git a/src/app/shared/components/filters/currency-filter/currency-filter.component.ts b/src/app/shared/components/filters/currency-filter/currency-filter.component.ts new file mode 100644 index 00000000..568d301f --- /dev/null +++ b/src/app/shared/components/filters/currency-filter/currency-filter.component.ts @@ -0,0 +1,17 @@ +import { getCurrencySymbol } from '@angular/common'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'dsh-currency-filter', + templateUrl: 'currency-filter.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CurrencyFilterComponent { + @Input() currencies: string[]; + @Input() selected?: string; + @Output() selectedChange = new EventEmitter(); + + formatCurrencyLabel(currency: string): string { + return getCurrencySymbol(currency, 'narrow'); + } +} diff --git a/src/app/shared/components/filters/currency-filter/currency-filter.module.ts b/src/app/shared/components/filters/currency-filter/currency-filter.module.ts new file mode 100644 index 00000000..05494b40 --- /dev/null +++ b/src/app/shared/components/filters/currency-filter/currency-filter.module.ts @@ -0,0 +1,17 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { TranslocoModule } from '@ngneat/transloco'; + +import { MultiselectFilterModule } from '@dsh/components/filters/multiselect-filter'; +import { RadioGroupFilterModule } from '@dsh/components/filters/radio-group-filter'; + +import { CurrencyFilterComponent } from './currency-filter.component'; + +const EXPORTED_DECLARATIONS = [CurrencyFilterComponent]; + +@NgModule({ + imports: [MultiselectFilterModule, CommonModule, TranslocoModule, RadioGroupFilterModule], + declarations: EXPORTED_DECLARATIONS, + exports: EXPORTED_DECLARATIONS, +}) +export class CurrencyFilterModule {} diff --git a/src/app/shared/components/filters/currency-filter/index.ts b/src/app/shared/components/filters/currency-filter/index.ts new file mode 100644 index 00000000..dc6a5f95 --- /dev/null +++ b/src/app/shared/components/filters/currency-filter/index.ts @@ -0,0 +1,2 @@ +export * from './currency-filter.component'; +export * from './currency-filter.module'; diff --git a/src/app/shared/components/filters/filters.module.ts b/src/app/shared/components/filters/filters.module.ts index 961cb4bb..8c6244ca 100644 --- a/src/app/shared/components/filters/filters.module.ts +++ b/src/app/shared/components/filters/filters.module.ts @@ -1,8 +1,9 @@ import { NgModule } from '@angular/core'; +import { CurrencyFilterModule } from './currency-filter'; import { FilterShopsModule } from './filter-shops'; -const EXPORTED_MODULES = [FilterShopsModule]; +const EXPORTED_MODULES = [FilterShopsModule, CurrencyFilterModule]; @NgModule({ imports: EXPORTED_MODULES, diff --git a/src/assets/i18n/currency-filter/ru.json b/src/assets/i18n/currency-filter/ru.json new file mode 100644 index 00000000..78b422a0 --- /dev/null +++ b/src/assets/i18n/currency-filter/ru.json @@ -0,0 +1,3 @@ +{ + "label": "Валюта" +} diff --git a/src/components/filters/filter/filter-button-actions/filter-button-actions.component.html b/src/components/filters/filter/filter-button-actions/filter-button-actions.component.html index 21682cd4..a0768cfe 100644 --- a/src/components/filters/filter/filter-button-actions/filter-button-actions.component.html +++ b/src/components/filters/filter/filter-button-actions/filter-button-actions.component.html @@ -1,8 +1,8 @@ -
+
-
+
@@ -10,4 +10,7 @@
+ + +
diff --git a/src/components/filters/filter/filter-button-actions/filter-button-actions.component.ts b/src/components/filters/filter/filter-button-actions/filter-button-actions.component.ts index 01b03be4..4c0a1511 100644 --- a/src/components/filters/filter/filter-button-actions/filter-button-actions.component.ts +++ b/src/components/filters/filter/filter-button-actions/filter-button-actions.component.ts @@ -1,4 +1,6 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +import { coerceBoolean } from '../../../../utils/coerce'; @Component({ selector: 'dsh-filter-button-actions', @@ -7,6 +9,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angul changeDetection: ChangeDetectionStrategy.OnPush, }) export class FilterButtonActionsComponent { + @Input() @coerceBoolean withoutClear = false; @Output() clear = new EventEmitter(); @Output() save = new EventEmitter(); } diff --git a/src/components/filters/multiselect-filter/mapItemsToLabel.ts b/src/components/filters/multiselect-filter/map-items-to-label.ts similarity index 100% rename from src/components/filters/multiselect-filter/mapItemsToLabel.ts rename to src/components/filters/multiselect-filter/map-items-to-label.ts diff --git a/src/components/filters/multiselect-filter/multiselect-filter.component.html b/src/components/filters/multiselect-filter/multiselect-filter.component.html index 8694063d..ec574300 100644 --- a/src/components/filters/multiselect-filter/multiselect-filter.component.html +++ b/src/components/filters/multiselect-filter/multiselect-filter.component.html @@ -22,5 +22,5 @@
- + diff --git a/src/components/filters/multiselect-filter/multiselect-filter.component.ts b/src/components/filters/multiselect-filter/multiselect-filter.component.ts index 141e5c48..f45b9697 100644 --- a/src/components/filters/multiselect-filter/multiselect-filter.component.ts +++ b/src/components/filters/multiselect-filter/multiselect-filter.component.ts @@ -12,10 +12,19 @@ import { } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { BehaviorSubject, combineLatest, merge, Observable, ReplaySubject, Subject } from 'rxjs'; -import { map, mapTo, pluck, shareReplay, startWith, switchMap, withLatestFrom } from 'rxjs/operators'; +import { + distinctUntilChanged, + map, + mapTo, + pluck, + shareReplay, + startWith, + switchMap, + withLatestFrom, +} from 'rxjs/operators'; import { ComponentChanges } from '../../../type-utils'; -import { mapItemsToLabel } from './mapItemsToLabel'; +import { mapItemsToLabel } from './map-items-to-label'; import { MultiselectFilterOptionComponent } from './multiselect-filter-option'; @Component({ @@ -95,6 +104,7 @@ export class MultiselectFilterComponent implements OnInit, OnChanges, A .pipe( withLatestFrom(this.selectedValues$), pluck(1), + distinctUntilChanged(), map((selected) => this.mapInputValuesToOptions(selected)), map((options) => options.map(({ value }) => value)) ) diff --git a/src/components/filters/radio-group-filter/index.ts b/src/components/filters/radio-group-filter/index.ts new file mode 100644 index 00000000..4500b648 --- /dev/null +++ b/src/components/filters/radio-group-filter/index.ts @@ -0,0 +1,3 @@ +export * from './radio-group-filter.component'; +export * from './radio-group-filter-option'; +export * from './radio-group-filter.module'; diff --git a/src/components/filters/radio-group-filter/map-item-to-label.ts b/src/components/filters/radio-group-filter/map-item-to-label.ts new file mode 100644 index 00000000..178b8b2d --- /dev/null +++ b/src/components/filters/radio-group-filter/map-item-to-label.ts @@ -0,0 +1,17 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export const mapItemToLabel = ( + s: Observable<{ + selectedItemLabel: string; + label: string; + formatSelectedLabel: (o: T) => string; + }> +): Observable => + s.pipe( + map(({ selectedItemLabel, label, formatSelectedLabel }) => + selectedItemLabel + ? `${label} · ${formatSelectedLabel ? formatSelectedLabel(selectedItemLabel) : selectedItemLabel}` + : label + ) + ); diff --git a/src/components/filters/radio-group-filter/radio-group-filter-option/index.ts b/src/components/filters/radio-group-filter/radio-group-filter-option/index.ts new file mode 100644 index 00000000..b74886dd --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter-option/index.ts @@ -0,0 +1 @@ +export * from './radio-group-filter-option.component'; diff --git a/src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.html b/src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.html new file mode 100644 index 00000000..4805c7bd --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.html @@ -0,0 +1,3 @@ + + + diff --git a/src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.ts b/src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.ts new file mode 100644 index 00000000..578d3f5c --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter-option/radio-group-filter-option.component.ts @@ -0,0 +1,30 @@ +import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, ViewChild } from '@angular/core'; +import { MatRadioButton } from '@angular/material/radio'; +import { BehaviorSubject } from 'rxjs'; + +@Component({ + selector: 'dsh-radio-group-filter-option', + templateUrl: 'radio-group-filter-option.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RadioGroupFilterOptionComponent { + @Input() value: T; + + toggle = new EventEmitter(); + + @ViewChild(MatRadioButton, { read: ElementRef }) private content: ElementRef; + + get label() { + return (this.content?.nativeElement?.textContent || '').trim(); + } + + get selected() { + return this.selected$.value; + } + + selected$ = new BehaviorSubject(false); + + select(isSelected: boolean) { + this.selected$.next(isSelected); + } +} diff --git a/src/components/filters/radio-group-filter/radio-group-filter.component.html b/src/components/filters/radio-group-filter/radio-group-filter.component.html new file mode 100644 index 00000000..254e75f7 --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter.component.html @@ -0,0 +1,12 @@ + + +
+ +
+
+ +
diff --git a/src/components/filters/radio-group-filter/radio-group-filter.component.scss b/src/components/filters/radio-group-filter/radio-group-filter.component.scss new file mode 100644 index 00000000..6e8481f8 --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter.component.scss @@ -0,0 +1,3 @@ +.content { + width: 360px; +} diff --git a/src/components/filters/radio-group-filter/radio-group-filter.component.ts b/src/components/filters/radio-group-filter/radio-group-filter.component.ts new file mode 100644 index 00000000..cf7bdee9 --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter.component.ts @@ -0,0 +1,122 @@ +import { + AfterContentInit, + ChangeDetectionStrategy, + Component, + ContentChildren, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + QueryList, +} from '@angular/core'; +import { BehaviorSubject, combineLatest, merge, Observable, ReplaySubject, Subject } from 'rxjs'; +import { + distinctUntilChanged, + map, + mapTo, + pluck, + shareReplay, + startWith, + switchMap, + withLatestFrom, +} from 'rxjs/operators'; + +import { ComponentChanges } from '../../../type-utils'; +import { coerceBoolean } from '../../../utils/coerce'; +import { mapItemToLabel } from './map-item-to-label'; +import { RadioGroupFilterOptionComponent } from './radio-group-filter-option'; + +@Component({ + selector: 'dsh-radio-group-filter', + templateUrl: 'radio-group-filter.component.html', + styleUrls: ['radio-group-filter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RadioGroupFilterComponent implements OnInit, OnChanges, AfterContentInit { + @Input() label: string; + @Input() @coerceBoolean withoutClear = false; + + @Input() selected?: T; + @Output() selectedChange = new EventEmitter(); + + @ContentChildren(RadioGroupFilterOptionComponent) options: QueryList>; + + private save$ = new Subject(); + private clear$ = new Subject(); + private selectFromInput$ = new ReplaySubject(1); + + private options$ = new BehaviorSubject[]>([]); + private selectedValue$ = new BehaviorSubject(undefined); + + savedSelectedOption$: Observable> = combineLatest([ + merge(this.selectFromInput$, this.save$.pipe(withLatestFrom(this.selectedValue$), pluck(1))), + this.options$, + ]).pipe( + map(([selected]) => this.mapInputValueToOption(selected)), + startWith(null), + shareReplay(1) + ); + + title$: Observable = this.savedSelectedOption$.pipe( + map((selectedOption) => ({ + selectedItemLabel: selectedOption?.label, + label: this.label, + formatSelectedLabel: this.formatSelectedLabel, + })), + mapItemToLabel, + shareReplay(1) + ); + + @Input() formatSelectedLabel?: (o: T) => string = (o) => o.toString(); + + @Input() compareWith?: (o1: T, o2: T) => boolean = (o1, o2) => o1 === o2; + + ngOnInit() { + combineLatest([this.selectedValue$, this.options$]).subscribe(([selectedValue, options]) => + options.forEach((o) => o.select(this.compareWith(selectedValue, o.value))) + ); + merge(this.selectFromInput$, this.clear$.pipe(mapTo(undefined))).subscribe((selectedValue) => + this.selectedValue$.next(selectedValue) + ); + this.options$ + .pipe(switchMap((options) => merge(...options.map((option) => option.toggle.pipe(mapTo(option.value)))))) + .subscribe((selected) => this.selectedValue$.next(selected)); + this.save$ + .pipe( + withLatestFrom(this.selectedValue$), + pluck(1), + distinctUntilChanged(), + map((selected) => this.mapInputValueToOption(selected)), + map((option) => option?.value) + ) + .subscribe((selectedValue) => this.selectedChange.emit(selectedValue)); + } + + ngAfterContentInit() { + this.options.changes + .pipe( + startWith(this.options), + map((o: RadioGroupFilterComponent['options']) => o.toArray()) + ) + .subscribe((o) => this.options$.next(o)); + } + + ngOnChanges({ selected }: ComponentChanges) { + if (selected && selected.currentValue) { + this.selectFromInput$.next(selected.currentValue); + } + } + + private mapInputValueToOption(inputValue: T) { + return this.options.toArray().find((o) => this.compareWith(inputValue, o.value)); + } + + save() { + this.save$.next(); + } + + clear() { + this.clear$.next(); + } +} diff --git a/src/components/filters/radio-group-filter/radio-group-filter.module.ts b/src/components/filters/radio-group-filter/radio-group-filter.module.ts new file mode 100644 index 00000000..22c1e0b0 --- /dev/null +++ b/src/components/filters/radio-group-filter/radio-group-filter.module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { TranslocoModule } from '@ngneat/transloco'; + +import { ButtonModule } from '../../buttons'; +import { FilterModule } from '../filter'; +import { RadioGroupFilterOptionComponent } from './radio-group-filter-option'; +import { RadioGroupFilterComponent } from './radio-group-filter.component'; + +const EXPORTED_DECLARATIONS = [RadioGroupFilterComponent, RadioGroupFilterOptionComponent]; + +@NgModule({ + imports: [ + CommonModule, + MatDividerModule, + ButtonModule, + TranslocoModule, + FlexLayoutModule, + MatInputModule, + MatCheckboxModule, + ReactiveFormsModule, + FilterModule, + MatRadioModule, + ], + declarations: EXPORTED_DECLARATIONS, + exports: EXPORTED_DECLARATIONS, +}) +export class RadioGroupFilterModule {}