mirror of
https://github.com/valitydev/dashboard.git
synced 2024-11-06 10:35:21 +00:00
currency filter (#305)
* something * working something * good working something * currency filter * prettier * save on close * fixes * more fixes * fix * remove test data
This commit is contained in:
parent
9abfed21c7
commit
16243f92f0
@ -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],
|
||||
})
|
||||
|
@ -0,0 +1,12 @@
|
||||
<dsh-radio-group-filter
|
||||
*transloco="let t; scope: 'currency-filter'; read: 'currencyFilter'"
|
||||
[label]="t.label"
|
||||
[selected]="selected"
|
||||
withoutClear
|
||||
[formatSelectedLabel]="formatCurrencyLabel"
|
||||
(selectedChange)="this.selectedChange.emit($event)"
|
||||
>
|
||||
<dsh-radio-group-filter-option *ngFor="let currency of currencies" [value]="currency">
|
||||
{{ currency }}
|
||||
</dsh-radio-group-filter-option>
|
||||
</dsh-radio-group-filter>
|
@ -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<string>();
|
||||
|
||||
formatCurrencyLabel(currency: string): string {
|
||||
return getCurrencySymbol(currency, 'narrow');
|
||||
}
|
||||
}
|
@ -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 {}
|
@ -0,0 +1,2 @@
|
||||
export * from './currency-filter.component';
|
||||
export * from './currency-filter.module';
|
@ -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,
|
||||
|
3
src/assets/i18n/currency-filter/ru.json
Normal file
3
src/assets/i18n/currency-filter/ru.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"label": "Валюта"
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="actions">
|
||||
<div *transloco="let t" class="actions">
|
||||
<ng-content></ng-content>
|
||||
<div *transloco="let t" fxFlex fxLayoutAlign="space-between" fxLayoutGap="20px">
|
||||
<div *ngIf="!withoutClear; else saveOnly" fxFlex fxLayoutAlign="space-between" fxLayoutGap="20px">
|
||||
<div>
|
||||
<button dsh-button (click)="clear.emit($event)">{{ t.clear }}</button>
|
||||
</div>
|
||||
@ -10,4 +10,7 @@
|
||||
<button dsh-button color="accent" (click)="save.emit($event)">{{ t.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #saveOnly>
|
||||
<button fxFlex dsh-button color="accent" (click)="save.emit($event)">{{ t.save }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
@ -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<MouseEvent>();
|
||||
@Output() save = new EventEmitter<MouseEvent>();
|
||||
}
|
||||
|
@ -22,5 +22,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</dsh-filter-button-content>
|
||||
<dsh-filter-button-actions (clear)="clear()" (save)="save(); filter.close()"></dsh-filter-button-actions>
|
||||
<dsh-filter-button-actions (clear)="clear()" (save)="filter.close()"></dsh-filter-button-actions>
|
||||
</dsh-filter>
|
||||
|
@ -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<T = any> implements OnInit, OnChanges, A
|
||||
.pipe(
|
||||
withLatestFrom(this.selectedValues$),
|
||||
pluck(1),
|
||||
distinctUntilChanged(),
|
||||
map((selected) => this.mapInputValuesToOptions(selected)),
|
||||
map((options) => options.map(({ value }) => value))
|
||||
)
|
||||
|
3
src/components/filters/radio-group-filter/index.ts
Normal file
3
src/components/filters/radio-group-filter/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './radio-group-filter.component';
|
||||
export * from './radio-group-filter-option';
|
||||
export * from './radio-group-filter.module';
|
@ -0,0 +1,17 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export const mapItemToLabel = (
|
||||
s: Observable<{
|
||||
selectedItemLabel: string;
|
||||
label: string;
|
||||
formatSelectedLabel: <T>(o: T) => string;
|
||||
}>
|
||||
): Observable<string> =>
|
||||
s.pipe(
|
||||
map(({ selectedItemLabel, label, formatSelectedLabel }) =>
|
||||
selectedItemLabel
|
||||
? `${label} · ${formatSelectedLabel ? formatSelectedLabel(selectedItemLabel) : selectedItemLabel}`
|
||||
: label
|
||||
)
|
||||
);
|
@ -0,0 +1 @@
|
||||
export * from './radio-group-filter-option.component';
|
@ -0,0 +1,3 @@
|
||||
<mat-radio-button [checked]="selected$ | async" (change)="toggle.emit()">
|
||||
<ng-content></ng-content>
|
||||
</mat-radio-button>
|
@ -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<T = any> {
|
||||
@Input() value: T;
|
||||
|
||||
toggle = new EventEmitter<void>();
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<dsh-filter [title]="title$ | async" [active]="!!(savedSelectedOption$ | async)" (closed)="save()" #filter>
|
||||
<dsh-filter-button-content class="content">
|
||||
<div fxLayout="column" fxLayoutGap="16px">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</dsh-filter-button-content>
|
||||
<dsh-filter-button-actions
|
||||
[withoutClear]="withoutClear"
|
||||
(save)="filter.close()"
|
||||
(clear)="clear()"
|
||||
></dsh-filter-button-actions>
|
||||
</dsh-filter>
|
@ -0,0 +1,3 @@
|
||||
.content {
|
||||
width: 360px;
|
||||
}
|
@ -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<T = any> implements OnInit, OnChanges, AfterContentInit {
|
||||
@Input() label: string;
|
||||
@Input() @coerceBoolean withoutClear = false;
|
||||
|
||||
@Input() selected?: T;
|
||||
@Output() selectedChange = new EventEmitter<T>();
|
||||
|
||||
@ContentChildren(RadioGroupFilterOptionComponent) options: QueryList<RadioGroupFilterOptionComponent<T>>;
|
||||
|
||||
private save$ = new Subject<void>();
|
||||
private clear$ = new Subject<void>();
|
||||
private selectFromInput$ = new ReplaySubject<T>(1);
|
||||
|
||||
private options$ = new BehaviorSubject<RadioGroupFilterOptionComponent<T>[]>([]);
|
||||
private selectedValue$ = new BehaviorSubject<T>(undefined);
|
||||
|
||||
savedSelectedOption$: Observable<RadioGroupFilterOptionComponent<T>> = 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<string> = 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<T>['options']) => o.toArray())
|
||||
)
|
||||
.subscribe((o) => this.options$.next(o));
|
||||
}
|
||||
|
||||
ngOnChanges({ selected }: ComponentChanges<RadioGroupFilterComponent>) {
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 {}
|
Loading…
Reference in New Issue
Block a user