Преглед изворни кода

fix(admin-ui): Fix multiple same filter types

Michael Bromley пре 2 година
родитељ
комит
f29aae9781

+ 1 - 0
packages/admin-ui/package.json

@@ -48,6 +48,7 @@
         "core-js": "^3.29.0",
         "dayjs": "^1.10.4",
         "graphql": "16.6.0",
+        "just-extend": "^6.2.0",
         "messageformat": "2.3.0",
         "ngx-pagination": "^6.0.3",
         "ngx-translate-messageformat-compiler": "^6.2.0",

+ 0 - 1
packages/admin-ui/src/lib/core/src/common/base-list.component.ts

@@ -1,6 +1,5 @@
 import { Directive, OnDestroy, OnInit } from '@angular/core';
 import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router';
-import { PaginatedList } from '@vendure/common/lib/shared-types';
 import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
 import { distinctUntilChanged, map, shareReplay, takeUntil } from 'rxjs/operators';
 

+ 147 - 32
packages/admin-ui/src/lib/core/src/providers/data-table/data-table-filter-collection.ts

@@ -1,82 +1,197 @@
 import { ActivatedRoute, Router } from '@angular/router';
+import { assertNever } from '@vendure/common/lib/shared-utils';
 import { Subject } from 'rxjs';
-import { DataTableFilter, DataTableFilterType } from './data-table-filter';
+import extend from 'just-extend';
+import {
+    DataTableFilter,
+    DataTableFilterBooleanType,
+    DataTableFilterDateRangeType,
+    DataTableFilterNumberType,
+    DataTableFilterOptions,
+    DataTableFilterSelectType,
+    DataTableFilterTextType,
+    DataTableFilterType,
+    DataTableFilterValue,
+} from './data-table-filter';
+
+export class FilterWithValue<Type extends DataTableFilterType = DataTableFilterType> {
+    constructor(
+        public readonly filter: DataTableFilter<any, Type>,
+        public value: DataTableFilterValue<Type>,
+        private onUpdate?: (value: DataTableFilterValue<Type>) => void,
+    ) {}
+
+    updateValue(value: DataTableFilterValue<Type>) {
+        this.value = value;
+        if (this.onUpdate) {
+            this.onUpdate(value);
+        }
+    }
+
+    isText(): this is FilterWithValue<DataTableFilterTextType> {
+        return this.filter.type.kind === 'text';
+    }
+
+    isNumber(): this is FilterWithValue<DataTableFilterNumberType> {
+        return this.filter.type.kind === 'number';
+    }
+
+    isBoolean(): this is FilterWithValue<DataTableFilterBooleanType> {
+        return this.filter.type.kind === 'boolean';
+    }
+
+    isSelect(): this is FilterWithValue<DataTableFilterSelectType> {
+        return this.filter.type.kind === 'select';
+    }
+
+    isDateRange(): this is FilterWithValue<DataTableFilterDateRangeType> {
+        return this.filter.type.kind === 'dateRange';
+    }
+}
 
 export class DataTableFilterCollection<FilterInput extends Record<string, any> = Record<string, any>> {
-    private readonly filters: Array<DataTableFilter<FilterInput, any>> = [];
-    private valueChanges$ = new Subject<Array<{ id: string; value: any }>>();
-    private connectedToRouter = false;
-    valueChanges = this.valueChanges$.asObservable();
-    private readonly filtersQueryParamName = 'filters';
+    readonly #filters: Array<DataTableFilter<FilterInput, any>> = [];
+    #activeFilters: FilterWithValue[] = [];
+    #valueChanges$ = new Subject<FilterWithValue[]>();
+    #connectedToRouter = false;
+    valueChanges = this.#valueChanges$.asObservable();
+    readonly #filtersQueryParamName = 'filters';
 
     constructor(private router: Router) {}
 
     get length(): number {
-        return this.filters.length;
+        return this.#filters.length;
+    }
+
+    get activeFilters(): FilterWithValue[] {
+        return this.#activeFilters;
     }
 
     addFilter<FilterType extends DataTableFilterType>(
-        config: ConstructorParameters<typeof DataTableFilter<FilterInput, FilterType>>[0],
+        config: DataTableFilterOptions<FilterInput, FilterType>,
     ): DataTableFilterCollection<FilterInput> {
-        if (this.connectedToRouter) {
+        if (this.#connectedToRouter) {
             throw new Error(
                 'Cannot add filter after connecting to router. Make sure to call addFilter() before connectToRoute()',
             );
         }
-        this.filters.push(new DataTableFilter(config, () => this.onSetValue()));
+        this.#filters.push(
+            new DataTableFilter(config, (filter, value) => this.onActivateFilter(filter, value)),
+        );
         return this;
     }
 
-    getFilter(id: string): DataTableFilter<FilterInput> | undefined {
-        return this.filters.find(f => f.name === id);
+    getFilter(name: string): DataTableFilter<FilterInput> | undefined {
+        return this.#filters.find(f => f.name === name);
     }
 
     getFilters(): Array<DataTableFilter<FilterInput>> {
-        return this.filters;
+        return this.#filters;
     }
 
-    getActiveFilters(): Array<DataTableFilter<FilterInput>> {
-        return this.filters.filter(f => f.value !== undefined);
+    removeActiveFilterAtIndex(index: number) {
+        this.#activeFilters.splice(index, 1);
+        this.#valueChanges$.next(this.#activeFilters);
     }
 
     createFilterInput(): FilterInput {
-        return this.getActiveFilters().reduce(
-            (acc, f) => ({ ...acc, ...(f.value != null ? f.toFilterInput(f.value) : {}) }),
-            {} as FilterInput,
-        );
+        return this.#activeFilters.reduce((acc, { filter, value }) => {
+            const newValue = value != null ? filter.toFilterInput(value) : {};
+            const result = extend(true, acc, newValue);
+            return result as FilterInput;
+        }, {} as FilterInput);
     }
 
     connectToRoute(route: ActivatedRoute) {
         this.valueChanges.subscribe(value => {
             this.router.navigate(['./'], {
-                queryParams: { [this.filtersQueryParamName]: this.serialize() },
+                queryParams: { [this.#filtersQueryParamName]: this.serialize() },
                 relativeTo: route,
                 queryParamsHandling: 'merge',
             });
         });
-        const filterQueryParams = (route.snapshot.queryParamMap.get(this.filtersQueryParamName) ?? '')
+        const filterQueryParams = (route.snapshot.queryParamMap.get(this.#filtersQueryParamName) ?? '')
             .split(';')
             .map(value => value.split(':'))
-            .map(([id, value]) => ({ id, value }));
-        for (const { id, value } of filterQueryParams) {
-            const filter = this.getFilter(id);
+            .map(([name, value]) => ({ name, value }));
+        for (const { name, value } of filterQueryParams) {
+            const filter = this.getFilter(name);
             if (filter) {
-                filter.deserializeValue(value);
+                const val = this.deserializeValue(filter, value);
+                this.#activeFilters.push(this.createFacetWithValue(filter, val));
             }
         }
-        this.connectedToRouter = true;
+        this.#connectedToRouter = true;
         return this;
     }
 
+    serializeValue<Type extends DataTableFilterType>(
+        filterWithValue: FilterWithValue<Type>,
+    ): string | undefined {
+        const valueAsType = <T extends DataTableFilter<any, any>>(
+            _filter: T,
+            _value: DataTableFilterValue<any>,
+        ): T extends DataTableFilter<any, infer R> ? DataTableFilterValue<R> : any => _value;
+
+        if (filterWithValue.isText()) {
+            const val = filterWithValue.value;
+            return `${val?.operator},${val?.term}`;
+        } else if (filterWithValue.isNumber()) {
+            const val = filterWithValue.value;
+            return `${val.operator},${val.amount}`;
+        } else if (filterWithValue.isSelect()) {
+            const val = filterWithValue.value;
+            return val.join(',');
+        } else if (filterWithValue.isBoolean()) {
+            const val = filterWithValue.value;
+            return val ? '1' : '0';
+        } else if (filterWithValue.isDateRange()) {
+            const val = filterWithValue.value;
+            const start = val.start ? new Date(val.start).getTime() : '';
+            const end = val.end ? new Date(val.end).getTime() : '';
+            return `${start},${end}`;
+        }
+    }
+
+    deserializeValue(filter: DataTableFilter, value: string): any {
+        switch (filter.type.kind) {
+            case 'text': {
+                const [operator, term] = value.split(',');
+                return { operator, term };
+            }
+            case 'number': {
+                const [operator, amount] = value.split(',');
+                return { operator, amount };
+            }
+            case 'select':
+                return value.split(',');
+            case 'boolean':
+                return value === '1';
+            case 'dateRange':
+                const [startTimestamp, endTimestamp] = value.split(',');
+                const start = startTimestamp ? new Date(Number(startTimestamp)).toISOString() : '';
+                const end = endTimestamp ? new Date(Number(endTimestamp)).toISOString() : '';
+                return { start, end };
+            default:
+                assertNever(filter.type);
+        }
+    }
+
     private serialize(): string {
-        return this.getActiveFilters()
-            .map(f => `${f.name}:${f.serializeValue()}`)
+        return this.#activeFilters
+            .map(
+                (filterWithValue, i) =>
+                    `${filterWithValue.filter.name}:${this.serializeValue(filterWithValue)}`,
+            )
             .join(';');
     }
 
-    private onSetValue() {
-        this.valueChanges$.next(
-            this.filters.filter(f => f.value !== undefined).map(f => ({ id: f.name, value: f.value })),
-        );
+    private onActivateFilter(filter: DataTableFilter<any, any>, value: DataTableFilterValue<any>) {
+        this.#activeFilters.push(this.createFacetWithValue(filter, value));
+        this.#valueChanges$.next(this.#activeFilters);
+    }
+
+    private createFacetWithValue(filter: DataTableFilter<any, any>, value: DataTableFilterValue<any>) {
+        return new FilterWithValue(filter, value, v => this.#valueChanges$.next(v));
     }
 }

+ 27 - 88
packages/admin-ui/src/lib/core/src/providers/data-table/data-table-filter.ts

@@ -1,5 +1,5 @@
-import { DateOperators } from '@vendure/admin-ui/core';
 import { assertNever } from '@vendure/common/lib/shared-utils';
+import { DateOperators } from '../../common/generated-types';
 
 export interface DataTableFilterTextType {
     kind: 'text';
@@ -15,6 +15,11 @@ export interface DataTableFilterBooleanType {
     kind: 'boolean';
 }
 
+export interface DataTableFilterNumberType {
+    kind: 'number';
+    inputType?: 'number' | 'currency';
+}
+
 export interface DataTableFilterDateRangeType {
     kind: 'dateRange';
 }
@@ -24,12 +29,14 @@ export type KindValueMap = {
     select: string[];
     boolean: boolean;
     dateRange: { start?: string; end?: string; dateOperators: DateOperators };
+    number: { operator: 'eq' | 'gt' | 'lt'; amount: string };
 };
 export type DataTableFilterType =
     | DataTableFilterTextType
     | DataTableFilterSelectType
     | DataTableFilterBooleanType
-    | DataTableFilterDateRangeType;
+    | DataTableFilterDateRangeType
+    | DataTableFilterNumberType;
 
 export interface DataTableFilterOptions<
     FilterInput extends Record<string, any> = any,
@@ -38,22 +45,22 @@ export interface DataTableFilterOptions<
     readonly name: string;
     readonly type: Type;
     readonly label: string;
-    readonly toFilterInput: (value: KindValueMap[Type['kind']]) => Partial<FilterInput>;
+    readonly toFilterInput: (value: DataTableFilterValue<Type>) => Partial<FilterInput>;
 }
 
+export type DataTableFilterValue<Type extends DataTableFilterType> = KindValueMap[Type['kind']];
+
 export class DataTableFilter<
     FilterInput extends Record<string, any> = any,
     Type extends DataTableFilterType = DataTableFilterType,
 > {
     constructor(
         private readonly options: DataTableFilterOptions<FilterInput, Type>,
-        private onSetValue?: (value: KindValueMap[Type['kind']] | undefined) => void,
+        private onActivate?: (
+            filter: DataTableFilter<FilterInput, Type>,
+            value: DataTableFilterValue<Type> | undefined,
+        ) => void,
     ) {}
-    private _value: any | undefined;
-
-    get value(): KindValueMap[Type['kind']] | undefined {
-        return this._value;
-    }
 
     get name(): string {
         return this.options.name;
@@ -67,74 +74,13 @@ export class DataTableFilter<
         return this.options.label;
     }
 
-    toFilterInput(value: KindValueMap[Type['kind']]): Partial<FilterInput> {
+    toFilterInput(value: DataTableFilterValue<Type>): Partial<FilterInput> {
         return this.options.toFilterInput(value);
     }
 
-    setValue(value: KindValueMap[Type['kind']]): void {
-        this._value = value;
-        if (this.onSetValue) {
-            this.onSetValue(value);
-        }
-    }
-
-    clearValue(): void {
-        this._value = undefined;
-        if (this.onSetValue) {
-            this.onSetValue(undefined);
-        }
-    }
-
-    serializeValue(): string | undefined {
-        if (this.value === undefined) {
-            return undefined;
-        }
-        const kind = this.type.kind;
-        switch (kind) {
-            case 'text': {
-                const value = this.getValueForKind(kind);
-                return `${value?.operator},${value?.term}`;
-            }
-            case 'select': {
-                const value = this.getValueForKind(kind);
-                return value?.join(',');
-            }
-            case 'boolean': {
-                const value = this.getValueForKind(kind);
-                return value ? '1' : '0';
-            }
-            case 'dateRange': {
-                const value = this.getValueForKind(kind);
-                const start = value?.start ? new Date(value.start).getTime() : '';
-                const end = value?.end ? new Date(value.end).getTime() : '';
-                return `${start},${end}`;
-            }
-            default:
-                assertNever(this.type);
-        }
-    }
-
-    deserializeValue(value: string): void {
-        switch (this.type.kind) {
-            case 'text': {
-                const [operator, term] = value.split(',');
-                this._value = { operator, term };
-                break;
-            }
-            case 'select':
-                this._value = value.split(',');
-                break;
-            case 'boolean':
-                this._value = value === '1';
-                break;
-            case 'dateRange':
-                const [startTimestamp, endTimestamp] = value.split(',');
-                const start = startTimestamp ? new Date(Number(startTimestamp)).toISOString() : '';
-                const end = endTimestamp ? new Date(Number(endTimestamp)).toISOString() : '';
-                this._value = { start, end };
-                break;
-            default:
-                assertNever(this.type);
+    activate(value: DataTableFilterValue<Type>) {
+        if (this.onActivate) {
+            this.onActivate(this, value);
         }
     }
 
@@ -142,6 +88,10 @@ export class DataTableFilter<
         return this.type.kind === 'text';
     }
 
+    isNumber(): this is DataTableFilter<FilterInput, DataTableFilterNumberType> {
+        return this.type.kind === 'number';
+    }
+
     isBoolean(): this is DataTableFilter<FilterInput, DataTableFilterBooleanType> {
         return this.type.kind === 'boolean';
     }
@@ -154,18 +104,7 @@ export class DataTableFilter<
         return this.type.kind === 'dateRange';
     }
 
-    private getValueForKind<Kind extends Type['kind']>(kind: Kind): KindValueMap[Kind] | undefined {
-        switch (kind) {
-            case 'text':
-                return this.value as any;
-            case 'select':
-                return this.value as any;
-            case 'boolean':
-                return this.value as any;
-            case 'dateRange':
-                return this.value as any;
-            default:
-                assertNever(kind);
-        }
-    }
+    // private getValueForKind<Kind extends Type['kind']>(kind: Kind): KindValueMap[Kind] | undefined {
+    //     return this.value as any;
+    // }
 }

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html

@@ -41,8 +41,8 @@
                 <ng-container *ngIf="filters">
                     <div class="filters">
                         <vdr-data-table-filters
-                            *ngFor="let activeFilter of filters.getActiveFilters()"
-                            [filter]="activeFilter"
+                            *ngFor="let activeFilter of filters.activeFilters"
+                            [filterWithValue]="activeFilter"
                             [filters]="filters"
                             class="mt-1"
                         ></vdr-data-table-filters>

+ 25 - 18
packages/admin-ui/src/lib/core/src/shared/components/data-table-filter-label/data-table-filter-label.component.html

@@ -1,29 +1,36 @@
-<span>{{ filter.label | translate }}:</span>
+<span>{{ filterWithValue.filter.label | translate }}:</span>
 <div>
-    <ng-container *ngIf="filter.isSelect()">
-        {{ filter.value?.join(', ') }}
+    <ng-container *ngIf="filterWithValue.isSelect()">
+        {{ filterWithValue.value?.join(', ') }}
     </ng-container>
-    <ng-container *ngIf="filter.isText()">
-        <span *ngIf="filter.value?.operator === 'contains'">{{
+    <ng-container *ngIf="filterWithValue.isText()">
+        <span *ngIf="filterWithValue.value?.operator === 'contains'">{{
             'common.operator-contains' | translate
         }}</span>
-        <span *ngIf="filter.value?.operator === 'eq'">{{ 'common.operator-eq' | translate }}</span>
-        <span *ngIf="filter.value?.operator === 'notContains'">{{
+        <span *ngIf="filterWithValue.value?.operator === 'eq'">{{ 'common.operator-eq' | translate }}</span>
+        <span *ngIf="filterWithValue.value?.operator === 'notContains'">{{
             'common.operator-notContains' | translate
         }}</span>
-        <span *ngIf="filter.value?.operator === 'notEq'">{{ 'common.operator-not-eq' | translate }}</span>
-        <span *ngIf="filter.value?.operator === 'regex'">{{ 'common.operator-regex' | translate }}</span>
-        <span> "{{ filter.value?.term }}"</span>
+        <span *ngIf="filterWithValue.value?.operator === 'notEq'">{{ 'common.operator-not-eq' | translate }}</span>
+        <span *ngIf="filterWithValue.value?.operator === 'regex'">{{ 'common.operator-regex' | translate }}</span>
+        <span> "{{ filterWithValue.value?.term }}"</span>
     </ng-container>
-    <ng-container *ngIf="filter.isBoolean()">
-        <span *ngIf="filter?.value">{{ 'common.boolean-true' | translate }}</span>
-        <span *ngIf="!filter?.value">{{ 'common.boolean-false' | translate }}</span>
+    <ng-container *ngIf="filterWithValue.isBoolean()">
+        <span *ngIf="filterWithValue?.value">{{ 'common.boolean-true' | translate }}</span>
+        <span *ngIf="!filterWithValue?.value">{{ 'common.boolean-false' | translate }}</span>
     </ng-container>
-    <ng-container *ngIf="filter.isDateRange()">
-        <span *ngIf="filter.value?.start && filter.value?.end">
-            {{ filter.value?.start | localeDate : 'shortDate' }} - {{ filter.value?.end | localeDate : 'shortDate' }}
+    <ng-container *ngIf="filterWithValue.isDateRange()">
+        <span *ngIf="filterWithValue.value?.start && filterWithValue.value?.end">
+            {{ filterWithValue.value?.start | localeDate : 'shortDate' }} - {{ filterWithValue.value?.end | localeDate : 'shortDate' }}
         </span>
-        <span *ngIf="filter.value?.start && !filter.value?.end"> > {{ filter.value?.start | localeDate : 'shortDate' }} </span>
-        <span *ngIf="filter.value?.end && !filter.value?.start"> < {{ filter.value?.end | localeDate : 'shortDate' }} </span>
+        <span *ngIf="filterWithValue.value?.start && !filterWithValue.value?.end"> > {{ filterWithValue.value?.start | localeDate : 'shortDate' }} </span>
+        <span *ngIf="filterWithValue.value?.end && !filterWithValue.value?.start"> < {{ filterWithValue.value?.end | localeDate : 'shortDate' }} </span>
+    </ng-container>
+    <ng-container *ngIf="filterWithValue.isNumber()">
+        <span *ngIf="filterWithValue.value?.operator === 'eq'"> = </span>
+        <span *ngIf="filterWithValue.value?.operator === 'gt'"> > </span>
+        <span *ngIf="filterWithValue.value?.operator === 'lt'"> < </span>
+        <span *ngIf="$any(filterWithValue.filter.type).inputType === 'currency'">{{ +filterWithValue.value?.amount | localeCurrency }}</span>
+        <span *ngIf="$any(filterWithValue.filter.type).inputType !== 'currency'">{{ +filterWithValue.value?.amount }}</span>
     </ng-container>
 </div>

+ 2 - 1
packages/admin-ui/src/lib/core/src/shared/components/data-table-filter-label/data-table-filter-label.component.ts

@@ -1,5 +1,6 @@
 import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
 import { DataTableFilter } from '../../../providers/data-table/data-table-filter';
+import { FilterWithValue } from '../../../providers/data-table/data-table-filter-collection';
 
 @Component({
     selector: 'vdr-data-table-filter-label',
@@ -8,5 +9,5 @@ import { DataTableFilter } from '../../../providers/data-table/data-table-filter
     changeDetection: ChangeDetectionStrategy.Default,
 })
 export class DataTableFilterLabelComponent {
-    @Input() filter: DataTableFilter;
+    @Input() filterWithValue: FilterWithValue;
 }

+ 19 - 1
packages/admin-ui/src/lib/core/src/shared/components/data-table-filters/data-table-filters.component.html

@@ -7,7 +7,7 @@
         <button vdrDropdownTrigger class="">
             <span *ngIf="state === 'new'">{{ 'common.add-filter' | translate }}</span>
             <span *ngIf="state === 'active'">
-                <vdr-data-table-filter-label [filter]="filter"></vdr-data-table-filter-label>
+                <vdr-data-table-filter-label [filterWithValue]="filterWithValue"></vdr-data-table-filter-label>
             </span>
             <clr-icon shape="ellipsis-vertical" size="12"></clr-icon>
         </button>
@@ -51,6 +51,24 @@
                         <input type="text" formControlName="term" />
                     </div>
                 </div>
+                <div *ngSwitchCase="'number'">
+                    <div [formGroup]="formControl">
+                        <select clrSelect name="options" formControlName="operator" class="mb-1">
+                            <option value="eq">{{ 'common.operator-eq' | translate }}</option>
+                            <option value="gt">{{ 'common.operator-gt' | translate }}</option>
+                            <option value="lt">{{ 'common.operator-lt' | translate }}</option>
+                        </select>
+                        <input
+                            *ngIf="$any(selectedFilter.type).inputType !== 'currency'"
+                            type="text"
+                            formControlName="amount"
+                        />
+                        <vdr-currency-input
+                            *ngIf="$any(selectedFilter.type).inputType === 'currency'"
+                            formControlName="amount"
+                        />
+                    </div>
+                </div>
                 <div *ngSwitchCase="'dateRange'">
                     <div [formGroup]="formControl">
                         <label>

+ 41 - 21
packages/admin-ui/src/lib/core/src/shared/components/data-table-filters/data-table-filters.component.ts

@@ -6,7 +6,10 @@ import { assertNever } from '@vendure/common/lib/shared-utils';
 import { DropdownComponent } from '../dropdown/dropdown.component';
 import { I18nService } from '../../../providers/i18n/i18n.service';
 import { DataTableFilter, DataTableFilterSelectType } from '../../../providers/data-table/data-table-filter';
-import { DataTableFilterCollection } from '../../../providers/data-table/data-table-filter-collection';
+import {
+    DataTableFilterCollection,
+    FilterWithValue,
+} from '../../../providers/data-table/data-table-filter-collection';
 
 @Component({
     selector: 'vdr-data-table-filters',
@@ -16,7 +19,7 @@ import { DataTableFilterCollection } from '../../../providers/data-table/data-ta
 })
 export class DataTableFiltersComponent implements AfterViewInit, OnInit {
     @Input() filters: DataTableFilterCollection;
-    @Input() filter?: DataTableFilter;
+    @Input() filterWithValue?: FilterWithValue;
     @ViewChild('dropdown', { static: true }) dropdown: DropdownComponent;
     protected state: 'new' | 'active' = 'new';
     protected formControl: AbstractControl;
@@ -25,12 +28,10 @@ export class DataTableFiltersComponent implements AfterViewInit, OnInit {
     constructor(private i18nService: I18nService) {}
 
     ngOnInit() {
-        if (this.filter) {
-            const filterConfig = this.filters.getFilter(this.filter?.name);
-            if (filterConfig) {
-                this.selectFilter(filterConfig);
-                this.state = 'active';
-            }
+        if (this.filterWithValue) {
+            const { filter, value } = this.filterWithValue;
+            this.selectFilter(filter, value);
+            this.state = 'active';
         }
     }
 
@@ -42,13 +43,13 @@ export class DataTableFiltersComponent implements AfterViewInit, OnInit {
         });
     }
 
-    selectFilter(filter: DataTableFilter) {
+    selectFilter(filter: DataTableFilter, value?: any) {
         this.selectedFilter = filter;
         if (filter.isText()) {
             this.formControl = new FormGroup(
                 {
-                    operator: new FormControl(filter.value?.operator ?? 'contains'),
-                    term: new FormControl(filter.value?.term ?? ''),
+                    operator: new FormControl(value?.operator ?? 'contains'),
+                    term: new FormControl(value?.term ?? ''),
                 },
                 control => {
                     if (!control.value.term) {
@@ -57,25 +58,39 @@ export class DataTableFiltersComponent implements AfterViewInit, OnInit {
                     return null;
                 },
             );
+        }
+        if (filter.isNumber()) {
+            this.formControl = new FormGroup(
+                {
+                    operator: new FormControl(value?.operator ?? 'gt'),
+                    amount: new FormControl(value?.amount ?? ''),
+                },
+                control => {
+                    if (!control.value.amount) {
+                        return { noSelection: true };
+                    }
+                    return null;
+                },
+            );
         } else if (filter.isSelect()) {
             this.formControl = new FormArray(
-                filter.type.options.map(o => new FormControl(filter.value?.includes(o.value) ?? false)),
+                filter.type.options.map(o => new FormControl(value?.includes(o.value) ?? false)),
                 control => (control.value.some(Boolean) ? null : { noSelection: true }),
             );
         } else if (filter.isBoolean()) {
-            this.formControl = new FormControl(filter.value ?? false);
+            this.formControl = new FormControl(value ?? false);
         } else if (filter.isDateRange()) {
             this.formControl = new FormGroup(
                 {
-                    start: new FormControl(filter.value?.start ?? null),
-                    end: new FormControl(filter.value?.end ?? null),
+                    start: new FormControl(value?.start ?? null),
+                    end: new FormControl(value?.end ?? null),
                 },
                 control => {
-                    const value = control.value;
-                    if (value.start && value.end && value.start > value.end) {
+                    const val = control.value;
+                    if (val.start && val.end && val.start > val.end) {
                         return { invalidRange: true };
                     }
-                    if (!value.start && !value.end) {
+                    if (!val.start && !val.end) {
                         return { noSelection: true };
                     }
                     return null;
@@ -116,13 +131,18 @@ export class DataTableFiltersComponent implements AfterViewInit, OnInit {
                 dateOperators,
             };
         }
-        this.selectedFilter.setValue(value);
+        if (this.state === 'new') {
+            this.selectedFilter.activate(value);
+        } else {
+            this.filterWithValue?.updateValue(value);
+        }
         this.dropdown.toggleOpen();
     }
 
     deactivate() {
-        if (this.filter) {
-            this.filter.clearValue();
+        if (this.filterWithValue) {
+            const index = this.filters.activeFilters.indexOf(this.filterWithValue);
+            this.filters.removeActiveFilterAtIndex(index);
         }
     }
 }

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/pipes/locale-currency.pipe.ts

@@ -27,13 +27,13 @@ export class LocaleCurrencyPipe extends LocaleBasePipe implements PipeTransform
 
     transform(value: unknown, ...args: unknown[]): string | unknown {
         const [currencyCode, locale] = args;
-        if (typeof value === 'number' && typeof currencyCode === 'string') {
+        if (typeof value === 'number') {
             const activeLocale = this.getActiveLocale(locale);
             const majorUnits = value / 100;
             try {
                 return new Intl.NumberFormat(activeLocale, {
                     style: 'currency',
-                    currency: currencyCode,
+                    currency: currencyCode as any,
                 }).format(majorUnits);
             } catch (e: any) {
                 return majorUnits.toFixed(2);

+ 10 - 0
packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.ts

@@ -56,6 +56,16 @@ export class OrderListComponent
                 },
             }),
         })
+        .addFilter({
+            name: 'totalWithTax',
+            type: { kind: 'number', inputType: 'currency', currencyCode: 'USD' },
+            label: _('order.total'),
+            toFilterInput: value => ({
+                totalWithTax: {
+                    [value.operator]: +value.amount,
+                },
+            }),
+        })
         .addFilter({
             name: 'state',
             type: {

+ 5 - 0
yarn.lock

@@ -12211,6 +12211,11 @@ just-diff@^6.0.0:
   resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285"
   integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==
 
+just-extend@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947"
+  integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==
+
 jwa@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"