Browse Source

feat(admin-ui): Make new data table with filtering

Michael Bromley 2 years ago
parent
commit
a665ea9d0f
40 changed files with 1366 additions and 408 deletions
  1. 39 39
      packages/admin-ui/i18n-coverage.json
  2. 92 0
      packages/admin-ui/src/lib/core/src/providers/data-table-filter/data-table-filter-collection.ts
  3. 15 0
      packages/admin-ui/src/lib/core/src/providers/data-table-filter/data-table-filter.service.ts
  4. 171 0
      packages/admin-ui/src/lib/core/src/providers/data-table-filter/data-table-filter.ts
  5. 17 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table-column.component.ts
  6. 99 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html
  7. 98 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.scss
  8. 178 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts
  9. 29 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-filter-label/data-table-filter-label.component.html
  10. 4 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-filter-label/data-table-filter-label.component.scss
  11. 12 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-filter-label/data-table-filter-label.component.ts
  12. 75 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-filters/data-table-filters.component.html
  13. 52 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-filters/data-table-filters.component.scss
  14. 131 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-filters/data-table-filters.component.ts
  15. 2 2
      packages/admin-ui/src/lib/core/src/shared/components/datetime-picker/datetime-picker.component.html
  16. 6 0
      packages/admin-ui/src/lib/core/src/shared/components/datetime-picker/datetime-picker.component.scss
  17. 3 0
      packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.scss
  18. 3 1
      packages/admin-ui/src/lib/core/src/shared/components/empty-placeholder/empty-placeholder.component.scss
  19. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/page-body/page-body.component.html
  20. 6 0
      packages/admin-ui/src/lib/core/src/shared/pipes/locale-date.pipe.ts
  21. 11 5
      packages/admin-ui/src/lib/core/src/shared/pipes/state-i18n-token.pipe.ts
  22. 8 0
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  23. 43 37
      packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html
  24. 61 154
      packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.ts
  25. 13 12
      packages/admin-ui/src/lib/static/i18n-messages/cs.json
  26. 15 16
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  27. 14 15
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  28. 13 12
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  29. 13 12
      packages/admin-ui/src/lib/static/i18n-messages/fr.json
  30. 14 13
      packages/admin-ui/src/lib/static/i18n-messages/it.json
  31. 14 13
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  32. 14 13
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  33. 14 13
      packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json
  34. 13 12
      packages/admin-ui/src/lib/static/i18n-messages/ru.json
  35. 14 13
      packages/admin-ui/src/lib/static/i18n-messages/uk.json
  36. 14 13
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  37. 13 12
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json
  38. 27 0
      packages/admin-ui/src/lib/static/styles/global/_buttons.scss
  39. 4 0
      packages/admin-ui/src/lib/static/styles/global/_forms.scss
  40. 1 0
      packages/admin-ui/src/lib/static/styles/theme/default.scss

+ 39 - 39
packages/admin-ui/i18n-coverage.json

@@ -1,70 +1,70 @@
 {
-  "generatedOn": "2023-01-12T12:03:15.456Z",
-  "lastCommit": "0e2e4d4b9b3308477154f60876c7755c57afd2e1",
+  "generatedOn": "2023-04-25T15:17:24.284Z",
+  "lastCommit": "8897a482886c91c5d1326ca76d19f4dcdc4069e4",
   "translationStatus": {
     "cs": {
-      "tokenCount": 700,
-      "translatedCount": 593,
-      "percentage": 85
+      "tokenCount": 702,
+      "translatedCount": 585,
+      "percentage": 83
     },
     "de": {
-      "tokenCount": 700,
-      "translatedCount": 574,
-      "percentage": 82
+      "tokenCount": 702,
+      "translatedCount": 568,
+      "percentage": 81
     },
     "en": {
-      "tokenCount": 700,
-      "translatedCount": 699,
-      "percentage": 100
+      "tokenCount": 702,
+      "translatedCount": 690,
+      "percentage": 98
     },
     "es": {
-      "tokenCount": 700,
-      "translatedCount": 624,
-      "percentage": 89
+      "tokenCount": 702,
+      "translatedCount": 616,
+      "percentage": 88
     },
     "fr": {
-      "tokenCount": 700,
-      "translatedCount": 614,
-      "percentage": 88
+      "tokenCount": 702,
+      "translatedCount": 606,
+      "percentage": 86
     },
     "it": {
-      "tokenCount": 700,
-      "translatedCount": 622,
-      "percentage": 89
+      "tokenCount": 702,
+      "translatedCount": 614,
+      "percentage": 87
     },
     "pl": {
-      "tokenCount": 700,
-      "translatedCount": 407,
+      "tokenCount": 702,
+      "translatedCount": 408,
       "percentage": 58
     },
     "pt_BR": {
-      "tokenCount": 700,
-      "translatedCount": 591,
-      "percentage": 84
+      "tokenCount": 702,
+      "translatedCount": 583,
+      "percentage": 83
     },
     "pt_PT": {
-      "tokenCount": 700,
-      "translatedCount": 635,
-      "percentage": 91
+      "tokenCount": 702,
+      "translatedCount": 627,
+      "percentage": 89
     },
     "ru": {
-      "tokenCount": 700,
-      "translatedCount": 621,
-      "percentage": 89
+      "tokenCount": 702,
+      "translatedCount": 613,
+      "percentage": 87
     },
     "uk": {
-      "tokenCount": 700,
-      "translatedCount": 621,
-      "percentage": 89
+      "tokenCount": 702,
+      "translatedCount": 613,
+      "percentage": 87
     },
     "zh_Hans": {
-      "tokenCount": 700,
-      "translatedCount": 559,
-      "percentage": 80
+      "tokenCount": 702,
+      "translatedCount": 553,
+      "percentage": 79
     },
     "zh_Hant": {
-      "tokenCount": 700,
-      "translatedCount": 387,
+      "tokenCount": 702,
+      "translatedCount": 388,
       "percentage": 55
     }
   }

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

@@ -0,0 +1,92 @@
+import { ActivatedRoute, Router } from '@angular/router';
+import { Subject } from 'rxjs';
+import { DataTableFilter, DataTableFilterType } from './data-table-filter';
+
+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';
+
+    constructor(private router: Router) {}
+
+    get length(): number {
+        return this.filters.length;
+    }
+
+    addFilter<FilterType extends DataTableFilterType>(
+        config: ConstructorParameters<typeof DataTableFilter<FilterInput, FilterType>>[0],
+    ): DataTableFilterCollection<FilterInput> {
+        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()));
+        return this;
+    }
+
+    getFilter(id: string): DataTableFilter<FilterInput> | undefined {
+        return this.filters.find(f => f.id === id);
+    }
+
+    getFilters(): Array<DataTableFilter<FilterInput>> {
+        return this.filters;
+    }
+
+    getActiveFilters(): Array<DataTableFilter<FilterInput>> {
+        return this.filters.filter(f => f.value !== undefined);
+    }
+
+    createFilterInput(): FilterInput {
+        return this.getActiveFilters().reduce(
+            (acc, f) => ({ ...acc, ...(f.value ? f.toFilterInput(f.value) : {}) }),
+            {} as FilterInput,
+        );
+    }
+
+    serialize(): string {
+        return this.getActiveFilters()
+            .map(f => `${f.id}:${f.serializeValue()}`)
+            .join(';');
+    }
+
+    parseQueryString(queryString: string): void {
+        const params = new URLSearchParams(queryString);
+        this.filters.forEach(f => {
+            const value = params.get(f.id);
+            if (value !== null) {
+                f.setValue(value);
+            }
+        });
+    }
+
+    connectToRoute(route: ActivatedRoute) {
+        this.valueChanges.subscribe(value => {
+            this.router.navigate(['./'], {
+                queryParams: { [this.filtersQueryParamName]: this.serialize() },
+                relativeTo: route,
+                queryParamsHandling: 'merge',
+            });
+        });
+        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);
+            if (filter) {
+                filter.deserializeValue(value);
+            }
+        }
+        this.connectedToRouter = true;
+        return this;
+    }
+
+    private onSetValue() {
+        this.valueChanges$.next(
+            this.filters.filter(f => f.value !== undefined).map(f => ({ id: f.id, value: f.value })),
+        );
+    }
+}

+ 15 - 0
packages/admin-ui/src/lib/core/src/providers/data-table-filter/data-table-filter.service.ts

@@ -0,0 +1,15 @@
+import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { DataTableFilterCollection } from './data-table-filter-collection';
+
+@Injectable({
+    providedIn: 'root',
+})
+export class DataTableFilterService {
+    constructor(private router: Router) {}
+
+    createConfig<FilterInput extends Record<string, any>>() {
+        return new DataTableFilterCollection<FilterInput>(this.router);
+    }
+}

+ 171 - 0
packages/admin-ui/src/lib/core/src/providers/data-table-filter/data-table-filter.ts

@@ -0,0 +1,171 @@
+import { DateOperators } from '@vendure/admin-ui/core';
+import { assertNever } from '@vendure/common/lib/shared-utils';
+
+export interface DataTableFilterTextType {
+    kind: 'text';
+    placeholder?: string;
+}
+
+export interface DataTableFilterSelectType {
+    kind: 'select';
+    options: Array<{ value: any; label: string }>;
+}
+
+export interface DataTableFilterBooleanType {
+    kind: 'boolean';
+}
+
+export interface DataTableFilterDateRangeType {
+    kind: 'dateRange';
+}
+
+export type KindValueMap = {
+    text: { operator: 'contains' | 'eq' | 'notContains' | 'notEq' | 'regex'; term: string };
+    select: string[];
+    boolean: boolean;
+    dateRange: { start?: string; end?: string; dateOperators: DateOperators };
+};
+export type DataTableFilterType =
+    | DataTableFilterTextType
+    | DataTableFilterSelectType
+    | DataTableFilterBooleanType
+    | DataTableFilterDateRangeType;
+
+export interface DataTableFilterOptions<
+    FilterInput extends Record<string, any> = any,
+    Type extends DataTableFilterType = DataTableFilterType,
+> {
+    readonly id: string;
+    readonly type: Type;
+    readonly label: string;
+    readonly toFilterInput: (value: KindValueMap[Type['kind']]) => Partial<FilterInput>;
+}
+
+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 _value: any | undefined;
+
+    get value(): KindValueMap[Type['kind']] | undefined {
+        return this._value;
+    }
+
+    get id(): string {
+        return this.options.id;
+    }
+
+    get type(): Type {
+        return this.options.type;
+    }
+
+    get label(): string {
+        return this.options.label;
+    }
+
+    toFilterInput(value: KindValueMap[Type['kind']]): 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);
+        }
+    }
+
+    isText(): this is DataTableFilter<FilterInput, DataTableFilterTextType> {
+        return this.type.kind === 'text';
+    }
+
+    isBoolean(): this is DataTableFilter<FilterInput, DataTableFilterBooleanType> {
+        return this.type.kind === 'boolean';
+    }
+
+    isSelect(): this is DataTableFilter<FilterInput, DataTableFilterSelectType> {
+        return this.type.kind === 'select';
+    }
+
+    isDateRange(): this is DataTableFilter<FilterInput, DataTableFilterDateRangeType> {
+        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);
+        }
+    }
+}

+ 17 - 0
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table-column.component.ts

@@ -0,0 +1,17 @@
+import { Component, ContentChild, Input, OnInit, TemplateRef, ViewChild } from '@angular/core';
+
+@Component({
+    selector: 'vdr-dt2-column',
+    template: ``,
+    exportAs: 'row',
+})
+export class DataTable2ColumnComponent<T> {
+    /**
+     * When set to true, this column will expand to use available width
+     */
+    @Input() expand = false;
+    @Input() heading: string;
+    @Input() align: 'left' | 'right' | 'center' = 'left';
+    @ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
+    item: T;
+}

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

@@ -0,0 +1,99 @@
+<div class="bulk-actions">
+    <ng-content select="vdr-bulk-action-menu"></ng-content>
+</div>
+<table class="" [class.no-select]="disableSelect" [style.table-layout]="!items?.length ? 'fixed' : 'inherit'">
+    <thead [class.items-selected]="selectionManager?.selection.length">
+        <tr>
+            <th *ngIf="selectionManager" class="align-middle">
+                <input
+                    type="checkbox"
+                    clrCheckbox
+                    [checked]="selectionManager?.areAllCurrentItemsSelected()"
+                    (change)="onToggleAllClick()"
+                />
+            </th>
+            <th *ngFor="let column of columns?.toArray()" class="align-middle" [class.expand]="column.expand">
+                <div class="cell-content" [ngClass]="column.align">
+                    {{ column.heading }}
+                </div>
+            </th>
+        </tr>
+        <tr *ngIf="searchTermControl || filters?.length">
+            <th [attr.colspan]="columns.length + (selectionManager ? 1 : 0)" class="filter-row">
+                <input [formControl]="searchTermControl" [placeholder]="searchTermPlaceholder" />
+                <div class="filters">
+                    <vdr-data-table-filters
+                        *ngFor="let activeFilter of filters.getActiveFilters()"
+                        [filter]="activeFilter"
+                        [filters]="filters"
+                        class="mt-1"
+                    ></vdr-data-table-filters>
+                    <vdr-data-table-filters
+                        *ngIf="filters.length"
+                        [filters]="filters"
+                        class="mt-1"
+                    ></vdr-data-table-filters>
+                </div>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr
+            *ngFor="
+                let item of items
+                    | paginate
+                        : {
+                              itemsPerPage: itemsPerPage,
+                              currentPage: currentPage,
+                              totalItems: totalItems
+                          };
+                index as i;
+                trackBy: trackByFn
+            "
+        >
+            <td *ngIf="selectionManager" class="align-middle selection-col">
+                <input
+                    type="checkbox"
+                    clrCheckbox
+                    [checked]="selectionManager?.isSelected(item)"
+                    (click)="onRowClick(item, $event)"
+                />
+            </td>
+            <td *ngFor="let column of columns?.toArray()">
+                <div class="cell-content" [ngClass]="column.align">
+                    <ng-container
+                        *ngTemplateOutlet="column.template; context: { item: item, index: i }"
+                    ></ng-container>
+                </div>
+            </td>
+        </tr>
+        <ng-container>
+            <tr *ngIf="!items?.length">
+                <td [attr.colspan]="columns.length + (selectionManager ? 1 : 0)">
+                    <vdr-empty-placeholder [emptyStateLabel]="emptyStateLabel"></vdr-empty-placeholder>
+                </td>
+            </tr>
+            <tr>
+                <td *ngFor="let column of columns?.toArray()" class="left align-middle"></td>
+            </tr>
+        </ng-container>
+    </tbody>
+</table>
+<div class="table-footer ml-4">
+    <vdr-items-per-page-controls
+        *ngIf="totalItems"
+        [itemsPerPage]="itemsPerPage"
+        (itemsPerPageChange)="itemsPerPageChange.emit($event)"
+    ></vdr-items-per-page-controls>
+    <div *ngIf="totalItems" class="p5">
+        {{ 'common.total-items' | translate : { currentStart, currentEnd, totalItems } }}
+    </div>
+
+    <vdr-pagination-controls
+        *ngIf="totalItems"
+        [currentPage]="currentPage"
+        [itemsPerPage]="itemsPerPage"
+        [totalItems]="totalItems"
+        (pageChange)="pageChange.emit($event)"
+    ></vdr-pagination-controls>
+</div>

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

@@ -0,0 +1,98 @@
+:host {
+    display: block;
+    max-width: 100%;
+    overflow: auto;
+    position: relative;
+}
+
+.bulk-actions {
+    position: absolute;
+    left: 50px;
+    top: 30px;
+    z-index: 2;
+}
+
+table {
+    max-width: 100vw;
+    overflow-x: auto;
+    width: 100%;
+}
+
+table.no-select {
+    user-select: none;
+}
+
+//thead th {
+//    position: sticky;
+//    top: 24px;
+//    z-index: 1;
+//
+//    &.expand {
+//        width: 100%;
+//    }
+//}
+
+th {
+    font-size: var(--font-size-xs);
+    font-weight: 600;
+    text-transform: uppercase;
+}
+
+.filter-row {
+    font-size: var(--font-size-base);
+    font-weight: 400;
+    background-color: var(--color-weight-100);
+    padding: calc(var(--space-unit) * 4) calc(var(--space-unit) * 4);
+    box-shadow: inset -1px 6px 5px -3px #a5a5a540;
+    input {
+        width: 100%;
+    }
+    .filters {
+        display: flex;
+        flex-wrap: wrap;
+        gap: calc(var(--space-unit) * 0.5);
+    }
+}
+
+th,
+td {
+    padding: calc(var(--space-unit) * 1) calc(var(--space-unit) * 1);
+}
+
+tr td:first-of-type,
+tr th:first-of-type {
+    padding-left: calc(var(--space-unit) * 4);
+}
+
+.cell-content {
+    display: flex;
+    align-items: center;
+    &.left {
+        justify-content: flex-start;
+    }
+    &.center {
+        justify-content: center;
+    }
+    &.right {
+        justify-content: flex-end;
+    }
+}
+
+thead.items-selected tr th {
+    color: transparent;
+}
+
+.selection-col {
+    width: 24px;
+}
+
+vdr-empty-placeholder {
+    width: 100%;
+}
+
+.table-footer {
+    display: flex;
+    align-items: baseline;
+    justify-content: space-between;
+    margin-top: 6px;
+}

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

@@ -0,0 +1,178 @@
+import {
+    AfterContentInit,
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    ContentChildren,
+    EventEmitter,
+    Input,
+    OnChanges,
+    OnDestroy,
+    OnInit,
+    Output,
+    QueryList,
+    SimpleChanges,
+    TemplateRef,
+} from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { PaginationService } from 'ngx-pagination';
+import { Subscription } from 'rxjs';
+import { SelectionManager } from '../../../common/utilities/selection-manager';
+import { DataTableFilter } from '../../../providers/data-table-filter/data-table-filter';
+import { DataTableFilterCollection } from '../../../providers/data-table-filter/data-table-filter-collection';
+
+import { DataTable2ColumnComponent } from './data-table-column.component';
+
+/**
+ * @description
+ * A table for displaying PaginatedList results. It is designed to be used inside components which
+ * extend the {@link BaseListComponent} class.
+ *
+ * @example
+ * ```HTML
+ * <vdr-data-table
+ *   [items]="items$ | async"
+ *   [itemsPerPage]="itemsPerPage$ | async"
+ *   [totalItems]="totalItems$ | async"
+ *   [currentPage]="currentPage$ | async"
+ *   (pageChange)="setPageNumber($event)"
+ *   (itemsPerPageChange)="setItemsPerPage($event)"
+ * >
+ *   <!-- The header columns are defined first -->
+ *   <vdr-dt-column>{{ 'common.name' | translate }}</vdr-dt-column>
+ *   <vdr-dt-column></vdr-dt-column>
+ *   <vdr-dt-column></vdr-dt-column>
+ *
+ *   <!-- Then we define how a row is rendered -->
+ *   <ng-template let-taxRate="item">
+ *     <td class="left align-middle">{{ taxRate.name }}</td>
+ *     <td class="left align-middle">{{ taxRate.category.name }}</td>
+ *     <td class="left align-middle">{{ taxRate.zone.name }}</td>
+ *     <td class="left align-middle">{{ taxRate.value }}%</td>
+ *     <td class="right align-middle">
+ *       <vdr-table-row-action
+ *         iconShape="edit"
+ *         [label]="'common.edit' | translate"
+ *         [linkTo]="['./', taxRate.id]"
+ *       ></vdr-table-row-action>
+ *     </td>
+ *     <td class="right align-middle">
+ *       <vdr-dropdown>
+ *         <button type="button" class="btn btn-link btn-sm" vdrDropdownTrigger>
+ *           {{ 'common.actions' | translate }}
+ *           <clr-icon shape="caret down"></clr-icon>
+ *         </button>
+ *         <vdr-dropdown-menu vdrPosition="bottom-right">
+ *           <button
+ *               type="button"
+ *               class="delete-button"
+ *               (click)="deleteTaxRate(taxRate)"
+ *               [disabled]="!(['DeleteSettings', 'DeleteTaxRate'] | hasPermission)"
+ *               vdrDropdownItem
+ *           >
+ *               <clr-icon shape="trash" class="is-danger"></clr-icon>
+ *               {{ 'common.delete' | translate }}
+ *           </button>
+ *         </vdr-dropdown-menu>
+ *       </vdr-dropdown>
+ *     </td>
+ *   </ng-template>
+ * </vdr-data-table>
+ * ```
+ *
+ * @docsCategory components
+ */
+@Component({
+    selector: 'vdr-data-table-2',
+    templateUrl: 'data-table2.component.html',
+    styleUrls: ['data-table2.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+    providers: [PaginationService],
+})
+export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnInit, OnDestroy {
+    @Input() items: T[];
+    @Input() itemsPerPage: number;
+    @Input() currentPage: number;
+    @Input() totalItems: number;
+    @Input() emptyStateLabel: string;
+    @Input() selectionManager?: SelectionManager<T>;
+    @Input() searchTermControl?: FormControl<string>;
+    @Input() searchTermPlaceholder?: string;
+    @Input() filters: DataTableFilterCollection;
+    @Input() activeFilters: DataTableFilter[] = [];
+    @Output() pageChange = new EventEmitter<number>();
+    @Output() itemsPerPageChange = new EventEmitter<number>();
+
+    @ContentChildren(DataTable2ColumnComponent) columns: QueryList<DataTable2ColumnComponent<T>>;
+    @ContentChildren(TemplateRef) templateRefs: QueryList<TemplateRef<any>>;
+    rowTemplate: TemplateRef<any>;
+    currentStart: number;
+    currentEnd: number;
+    // This is used to apply a `user-select: none` CSS rule to the table,
+    // which allows shift-click multi-row selection
+    disableSelect = false;
+    private subscription: Subscription | undefined;
+
+    constructor(private changeDetectorRef: ChangeDetectorRef) {}
+
+    private shiftDownHandler = (event: KeyboardEvent) => {
+        if (event.shiftKey && !this.disableSelect) {
+            this.disableSelect = true;
+            this.changeDetectorRef.markForCheck();
+        }
+    };
+
+    private shiftUpHandler = (event: KeyboardEvent) => {
+        if (this.disableSelect) {
+            this.disableSelect = false;
+            this.changeDetectorRef.markForCheck();
+        }
+    };
+
+    ngOnInit() {
+        if (this.selectionManager) {
+            document.addEventListener('keydown', this.shiftDownHandler, { passive: true });
+            document.addEventListener('keyup', this.shiftUpHandler, { passive: true });
+        }
+
+        this.subscription = this.selectionManager?.selectionChanges$.subscribe(() =>
+            this.changeDetectorRef.markForCheck(),
+        );
+    }
+
+    ngOnChanges(changes: SimpleChanges) {
+        if (changes.items) {
+            this.currentStart = this.itemsPerPage * (this.currentPage - 1);
+            this.currentEnd = this.currentStart + changes.items.currentValue?.length;
+            this.selectionManager?.setCurrentItems(this.items);
+        }
+    }
+
+    ngOnDestroy() {
+        if (this.selectionManager) {
+            document.removeEventListener('keydown', this.shiftDownHandler);
+            document.removeEventListener('keyup', this.shiftUpHandler);
+        }
+        this.subscription?.unsubscribe();
+    }
+
+    ngAfterContentInit(): void {
+        this.rowTemplate = this.templateRefs.last;
+    }
+
+    trackByFn(index: number, item: any) {
+        if ((item as any).id != null) {
+            return (item as any).id;
+        } else {
+            return index;
+        }
+    }
+
+    onToggleAllClick() {
+        this.selectionManager?.toggleSelectAll();
+    }
+
+    onRowClick(item: T, event: MouseEvent) {
+        this.selectionManager?.toggleSelection(item, event);
+    }
+}

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

@@ -0,0 +1,29 @@
+<span>{{ filter.label | translate }}:</span>
+<div>
+    <ng-container *ngIf="filter.isSelect()">
+        {{ filter.value?.join(', ') }}
+    </ng-container>
+    <ng-container *ngIf="filter.isText()">
+        <span *ngIf="filter.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'">{{
+            'common.operator-notContains' | translate
+        }}</span>
+        <span *ngIf="filter.value?.operator === 'not-eq'">{{ 'common.operator-notEq' | translate }}</span>
+        <span *ngIf="filter.value?.operator === 'regex'">{{ 'common.operator-regex' | translate }}</span>
+        <span> "{{ filter.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>
+    <ng-container *ngIf="filter.isDateRange()">
+        <span *ngIf="filter.value?.start && filter.value?.end">
+            {{ filter.value?.start | localeDate : 'shortDate' }} - {{ filter.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>
+    </ng-container>
+</div>

+ 4 - 0
packages/admin-ui/src/lib/core/src/shared/components/data-table-filter-label/data-table-filter-label.component.scss

@@ -0,0 +1,4 @@
+:host {
+    display: flex;
+    gap: calc(var(--space-unit) / 2);
+}

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

@@ -0,0 +1,12 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { DataTableFilter } from '../../../providers/data-table-filter/data-table-filter';
+
+@Component({
+    selector: 'vdr-data-table-filter-label',
+    templateUrl: './data-table-filter-label.component.html',
+    styleUrls: ['./data-table-filter-label.component.scss'],
+    changeDetection: ChangeDetectionStrategy.Default,
+})
+export class DataTableFilterLabelComponent {
+    @Input() filter: DataTableFilter;
+}

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

@@ -0,0 +1,75 @@
+<vdr-dropdown [manualToggle]="true" #dropdown>
+    <div class="filter-button" [ngClass]="state">
+        <clr-icon shape="plus" size="12" *ngIf="state === 'new'"></clr-icon>
+        <button *ngIf="state === 'active'" class="remove" (click)="deactivate()">
+            <clr-icon shape="times" size="12"></clr-icon>
+        </button>
+        <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>
+            </span>
+            <clr-icon shape="ellipsis-vertical" size="12"></clr-icon>
+        </button>
+    </div>
+    <vdr-dropdown-menu vdrPosition="bottom-left">
+        <div *ngIf="!selectedFilter">
+            <div class="filter-heading">Filter by:</div>
+            <div *ngFor="let filter of filters.getFilters()">
+                <button vdrDropdownItem (click)="selectFilter(filter)">
+                    {{ filter.label | translate }}
+                </button>
+            </div>
+        </div>
+        <div *ngIf="selectedFilter" class="">
+            <div class="filter-heading">Filter by {{ selectedFilter.label | translate }}:</div>
+            <div class="mx-2 mt-1" [ngSwitch]="selectedFilter.type.kind">
+                <div *ngSwitchCase="'select'" [formGroup]="formControl">
+                    <label *ngFor="let option of $any(selectedFilter.type).options; index as i">
+                        <input type="checkbox" [formControlName]="i" />
+                        <span>{{ option.label | translate }}</span>
+                    </label>
+                </div>
+                <div *ngSwitchCase="'boolean'">
+                    <label
+                        ><input type="checkbox" [formControl]="formControl" clrToggle />
+                        <span *ngIf="formControl.value">{{ 'common.boolean-true' | translate }}</span>
+                        <span *ngIf="!formControl.value">{{ 'common.boolean-false' | translate }}</span>
+                    </label>
+                </div>
+                <div *ngSwitchCase="'text'">
+                    <div [formGroup]="formControl">
+                        <select clrSelect name="options" formControlName="operator" class="mb-1">
+                            <option value="contains">{{ 'common.operator-contains' | translate }}</option>
+                            <option value="eq">{{ 'common.operator-eq' | translate }}</option>
+                            <option value="notContains">
+                                {{ 'common.operator-not-contains' | translate }}
+                            </option>
+                            <option value="notEq">{{ 'common.operator-not-eq' | translate }}</option>
+                            <option value="regex">{{ 'common.operator-regex' | translate }}</option>
+                        </select>
+                        <input type="text" formControlName="term" />
+                    </div>
+                </div>
+                <div *ngSwitchCase="'dateRange'">
+                    <div [formGroup]="formControl">
+                        <label>
+                            <div>{{ 'common.start-date' | translate }}</div>
+                        </label>
+                        <vdr-datetime-picker formControlName="start"></vdr-datetime-picker>
+                        <label>
+                            <div>{{ 'common.end-date' | translate }}</div>
+                        </label>
+                        <vdr-datetime-picker formControlName="end"></vdr-datetime-picker>
+                    </div>
+                </div>
+            </div>
+            <div class="apply-wrapper mt-2">
+                <button class="button" (click)="activate()" [disabled]="!formControl.valid">
+                    <span>{{ 'common.apply' | translate }}</span>
+                    <clr-icon shape="check"></clr-icon>
+                </button>
+            </div>
+        </div>
+    </vdr-dropdown-menu>
+</vdr-dropdown>

+ 52 - 0
packages/admin-ui/src/lib/core/src/shared/components/data-table-filters/data-table-filters.component.scss

@@ -0,0 +1,52 @@
+:host {
+    display: block;
+}
+
+.filter-button {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-end;
+    align-items: center;
+    padding: 0 var(--space-unit);
+    height: calc(var(--space-unit) * 3);
+    font-size: var(--font-size-xs);
+    border-radius: var(--border-radius-lg);
+    background-color: var(--color-weight-150);
+    > button {
+        border: none;
+        gap: 12px;
+        cursor: pointer;
+        background-color: transparent;
+        display: flex;
+        align-items: center;
+        height: calc(var(--space-unit) * 2);
+        border-radius: var(--border-radius-lg);
+    }
+    &.active {
+        background-color: var(--color-primary-700);
+        color: var(--color-primary-100);
+        > button {
+            color: var(--color-primary-100);
+        }
+    }
+    button.remove {
+    }
+}
+
+label {
+    display: flex;
+    gap: var(--space-unit);
+    margin-bottom: calc(var(--space-unit) * 0.5);
+}
+
+.filter-heading {
+    font-size: var(--font-size-xs);
+    text-transform: uppercase;
+    color: var(--color-weight-600);
+    margin: 0 calc(var(--space-unit) * 2);
+}
+.apply-wrapper {
+    display: flex;
+    justify-content: flex-end;
+    padding-right : calc(var(--space-unit) * 2);
+}

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

@@ -0,0 +1,131 @@
+import { AfterViewInit, ChangeDetectionStrategy, Component, Input, OnInit, ViewChild } from '@angular/core';
+import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { DateOperators } from '@vendure/admin-ui/core';
+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-filter/data-table-filter';
+import { DataTableFilterCollection } from '../../../providers/data-table-filter/data-table-filter-collection';
+
+@Component({
+    selector: 'vdr-data-table-filters',
+    templateUrl: './data-table-filters.component.html',
+    styleUrls: ['./data-table-filters.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DataTableFiltersComponent implements AfterViewInit, OnInit {
+    @Input() filters: DataTableFilterCollection;
+    @Input() filter?: DataTableFilter;
+    @ViewChild('dropdown', { static: true }) dropdown: DropdownComponent;
+    protected state: 'new' | 'active' = 'new';
+    protected formControl: AbstractControl;
+    protected selectedFilter: DataTableFilter | undefined;
+
+    constructor(private i18nService: I18nService) {}
+
+    ngOnInit() {
+        if (this.filter) {
+            const filterConfig = this.filters.getFilter(this.filter?.id);
+            if (filterConfig) {
+                this.selectFilter(filterConfig);
+                this.state = 'active';
+            }
+        }
+    }
+
+    ngAfterViewInit() {
+        this.dropdown.onOpenChange(isOpen => {
+            if (!isOpen && this.state === 'new') {
+                this.selectedFilter = undefined;
+            }
+        });
+    }
+
+    selectFilter(filter: DataTableFilter) {
+        this.selectedFilter = filter;
+        if (filter.isText()) {
+            this.formControl = new FormGroup(
+                {
+                    operator: new FormControl(filter.value?.operator ?? 'contains'),
+                    term: new FormControl(filter.value?.term ?? ''),
+                },
+                control => {
+                    if (!control.value.term) {
+                        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)),
+                control => (control.value.some(Boolean) ? null : { noSelection: true }),
+            );
+        } else if (filter.isBoolean()) {
+            this.formControl = new FormControl(filter.value ?? false);
+        } else if (filter.isDateRange()) {
+            this.formControl = new FormGroup(
+                {
+                    start: new FormControl(filter.value?.start ?? null),
+                    end: new FormControl(filter.value?.end ?? null),
+                },
+                control => {
+                    const value = control.value;
+                    if (value.start && value.end && value.start > value.end) {
+                        return { invalidRange: true };
+                    }
+                    if (!value.start && !value.end) {
+                        return { noSelection: true };
+                    }
+                    return null;
+                },
+            );
+        }
+    }
+
+    activate() {
+        if (!this.selectedFilter) {
+            return;
+        }
+        let value = this.formControl.value;
+        const type = this.selectedFilter?.type;
+        if (type.kind === 'select' && Array.isArray(value)) {
+            value = value.map((o, i) => (o ? type.options[i].value : undefined)).filter(v => !!v);
+        }
+        if (type.kind === 'dateRange') {
+            let dateOperators: DateOperators;
+            const start = value.start ?? undefined;
+            const end = value.end ?? undefined;
+            if (start && end) {
+                dateOperators = {
+                    between: { start, end },
+                };
+            } else if (start) {
+                dateOperators = {
+                    after: start,
+                };
+            } else {
+                dateOperators = {
+                    before: end,
+                };
+            }
+            value = {
+                start,
+                end,
+                dateOperators,
+            };
+        }
+        this.selectedFilter.setValue(value);
+        this.dropdown.toggleOpen();
+    }
+
+    deactivate() {
+        if (this.filter) {
+            this.filter.clearValue();
+        }
+    }
+}

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/components/datetime-picker/datetime-picker.component.html

@@ -7,12 +7,12 @@
         (keydown.space)="dropdownComponent.toggleOpen()"
         #datetimeInput
     />
-    <button class="clear-value-button btn" [class.visible]="!disabled && !readonly && (selected$ | async)" (click)="clearValue()">
+    <button class="clear-value-button" [class.visible]="!disabled && !readonly && (selected$ | async)" (click)="clearValue()">
         <clr-icon shape="times"></clr-icon>
     </button>
 </div>
 <vdr-dropdown #dropdownComponent>
-    <button class="btn btn-outline calendar-button" vdrDropdownTrigger [disabled]="readonly || disabled">
+    <button class="calendar-button" vdrDropdownTrigger [disabled]="readonly || disabled">
         <clr-icon shape="calendar"></clr-icon>
     </button>
     <vdr-dropdown-menu>

+ 6 - 0
packages/admin-ui/src/lib/core/src/shared/components/datetime-picker/datetime-picker.component.scss

@@ -20,6 +20,8 @@ input.selected-datetime {
     margin: 0;
     border-radius: 0;
     border-left: none;
+    border-top: 1px solid var(--color-weight-200);
+    border-bottom: 1px solid var(--color-weight-200);
     border-color: var(--color-component-border-200);
     background-color: white;
     color: var(--color-grey-500);
@@ -33,6 +35,10 @@ input.selected-datetime {
     margin: 0;
     border-top-left-radius: 0;
     border-bottom-left-radius: 0;
+    border: 1px solid var(--color-weight-200);
+    border-radius: var(--border-radius-sm);
+    border-left: none;
+    height: 100%;
 }
 
 .datetime-picker {

+ 3 - 0
packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.scss

@@ -6,6 +6,9 @@
 }
 
 ::ng-deep {
+    .dropdown-menu {
+        max-width: initial;
+    }
     .dropdown-menu .dropdown-item {
         display: flex;
         align-items: center;

+ 3 - 1
packages/admin-ui/src/lib/core/src/shared/components/empty-placeholder/empty-placeholder.component.scss

@@ -1,4 +1,6 @@
-
+:host {
+    display: block;
+}
 .empty-state {
     text-align: center;
     padding: 60px;

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/page-body/page-body.component.html

@@ -1,3 +1,3 @@
-<div class="max-w-layout px-4">
+<div class="max-w-layout">
     <ng-content />
 </div>

+ 6 - 0
packages/admin-ui/src/lib/core/src/shared/pipes/locale-date.pipe.ts

@@ -62,6 +62,12 @@ export class LocaleDatePipe extends LocaleBasePipe implements PipeTransform {
                     month: 'long',
                     day: 'numeric',
                 };
+            case 'shortDate':
+                return {
+                    day: 'numeric',
+                    month: 'numeric',
+                    year: '2-digit',
+                };
             case 'short':
                 return {
                     day: 'numeric',

+ 11 - 5
packages/admin-ui/src/lib/core/src/shared/pipes/state-i18n-token.pipe.ts

@@ -32,12 +32,18 @@ export class StateI18nTokenPipe implements PipeTransform {
             if (defaultStateToken) {
                 return defaultStateToken;
             }
-            return ('state.' +
-                value
-                    .replace(/([a-z])([A-Z])/g, '$1-$2')
-                    .replace(/ +/g, '-')
-                    .toLowerCase()) as any;
+            return getOrderStateTranslationToken(value as string) as T;
         }
         return value;
     }
 }
+
+export function getOrderStateTranslationToken(state: string): string {
+    return (
+        'state.' +
+        state
+            .replace(/([a-z])([A-Z])/g, '$1-$2')
+            .replace(/ +/g, '-')
+            .toLowerCase()
+    );
+}

+ 8 - 0
packages/admin-ui/src/lib/core/src/shared/shared.module.ts

@@ -39,6 +39,8 @@ import { CurrencyInputComponent } from './components/currency-input/currency-inp
 import { CustomDetailComponentHostComponent } from './components/custom-detail-component-host/custom-detail-component-host.component';
 import { CustomFieldControlComponent } from './components/custom-field-control/custom-field-control.component';
 import { CustomerLabelComponent } from './components/customer-label/customer-label.component';
+import { DataTable2ColumnComponent } from './components/data-table-2/data-table-column.component';
+import { DataTable2Component } from './components/data-table-2/data-table2.component';
 import { DataTableColumnComponent } from './components/data-table/data-table-column.component';
 import { DataTableComponent } from './components/data-table/data-table.component';
 import { DatetimePickerComponent } from './components/datetime-picker/datetime-picker.component';
@@ -145,6 +147,8 @@ import { PageHeaderComponent } from './components/page-header/page-header.compon
 import { PageHeaderDescriptionComponent } from './components/page-header-description/page-header-description.component';
 import { PageHeaderTabsComponent } from './components/page-header-tabs/page-header-tabs.component';
 import { PageBodyComponent } from './components/page-body/page-body.component';
+import { DataTableFiltersComponent } from './components/data-table-filters/data-table-filters.component';
+import { DataTableFilterLabelComponent } from './components/data-table-filter-label/data-table-filter-label.component';
 
 const IMPORTS = [
     ClarityModule,
@@ -258,6 +262,10 @@ const DECLARATIONS = [
     BulkActionMenuComponent,
     RadioCardComponent,
     RadioCardFieldsetComponent,
+    DataTable2Component,
+    DataTable2ColumnComponent,
+    DataTableFiltersComponent,
+    DataTableFilterLabelComponent,
 ];
 
 const DYNAMIC_FORM_INPUTS = [

+ 43 - 37
packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html

@@ -94,51 +94,57 @@
     ></vdr-page-header-tabs>
 </vdr-page-header>
 <vdr-page-body>
-    <vdr-data-table
+    <vdr-data-table-2
+        class="mt-2"
         [items]="items$ | async"
         [itemsPerPage]="itemsPerPage$ | async"
         [totalItems]="totalItems$ | async"
         [currentPage]="currentPage$ | async"
+        [searchTermControl]="searchControl"
+        [searchTermPlaceholder]="'order.search-by-order-filters' | translate"
+        [filters]="filters"
         (pageChange)="setPageNumber($event)"
         (itemsPerPageChange)="setItemsPerPage($event)"
     >
-        <vdr-dt-column>{{ 'common.code' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'order.customer' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'order.order-type' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'order.state' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'order.total' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'common.updated-at' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'order.placed-at' | translate }}</vdr-dt-column>
-        <vdr-dt-column>{{ 'order.shipping' | translate }}</vdr-dt-column>
-        <vdr-dt-column></vdr-dt-column>
-        <ng-template let-order="item">
-            <td class="left align-middle">{{ order.code }}</td>
-            <td class="left align-middle">
+        <vdr-dt2-column [heading]="'common.code' | translate">
+            <ng-template let-order="item">
+                {{ order.code }}
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'order.customer' | translate">
+            <ng-template let-order="item">
                 <vdr-customer-label [customer]="order.customer"></vdr-customer-label>
-            </td>
-            <td class="left align-middle">
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'order.order-type' | translate">
+            <ng-template let-order="item">
                 <vdr-chip>{{ order.type }}</vdr-chip>
-            </td>
-            <td class="left align-middle">
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'order.state' | translate">
+            <ng-template let-order="item">
                 <vdr-order-state-label [state]="order.state"></vdr-order-state-label>
-            </td>
-            <td class="left align-middle">{{ order.totalWithTax | localeCurrency : order.currencyCode }}</td>
-            <td class="left align-middle">{{ order.updatedAt | timeAgo }}</td>
-            <td class="left align-middle">{{ order.orderPlacedAt | localeDate : 'medium' }}</td>
-            <td class="left align-middle">{{ getShippingNames(order) }}</td>
-            <td class="right align-middle">
-                <vdr-table-row-action
-                    iconShape="shopping-cart"
-                    [label]="'common.open' | translate"
-                    [linkTo]="
-                        order.state === 'Modifying'
-                            ? ['./', order.id, 'modify']
-                            : order.state === 'Draft'
-                            ? ['./draft', order.id]
-                            : ['./', order.id]
-                    "
-                ></vdr-table-row-action>
-            </td>
-        </ng-template>
-    </vdr-data-table>
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'order.total' | translate">
+            <ng-template let-order="item">
+                {{ order.totalWithTax | localeCurrency : order.currencyCode }}
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'common.updated-at' | translate">
+            <ng-template let-order="item">
+                {{ order.updatedAt | timeAgo }}
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'order.placed-at' | translate">
+            <ng-template let-order="item">
+                {{ order.orderPlacedAt | localeDate : 'short' }}
+            </ng-template>
+        </vdr-dt2-column>
+        <vdr-dt2-column [heading]="'order.shipping' | translate">
+            <ng-template let-order="item">
+                {{ getShippingNames(order) }}
+            </ng-template>
+        </vdr-dt2-column>
+    </vdr-data-table-2>
 </vdr-page-body>

+ 61 - 154
packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.ts

@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
-import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
+import { UntypedFormControl } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import {
@@ -7,6 +7,7 @@ import {
     ChannelService,
     DataService,
     GetOrderListQuery,
+    getOrderStateTranslationToken,
     ItemOf,
     LocalStorageService,
     LogicalOperator,
@@ -15,21 +16,13 @@ import {
     OrderType,
     ServerConfigService,
     SortOrder,
+    StateI18nTokenPipe,
 } from '@vendure/admin-ui/core';
 import { Order } from '@vendure/common/lib/generated-types';
-import { merge, Observable } from 'rxjs';
-import { debounceTime, distinctUntilChanged, filter, map, takeUntil, tap } from 'rxjs/operators';
-
-interface OrderFilterConfig {
-    active?: boolean;
-    states?: string[];
-}
-
-interface FilterPreset {
-    name: string;
-    label: string;
-    config: OrderFilterConfig;
-}
+import { merge } from 'rxjs';
+import { debounceTime, filter, takeUntil, tap } from 'rxjs/operators';
+import { DataTableFilter } from '../../../../core/src/providers/data-table-filter/data-table-filter';
+import { DataTableFilterService } from '../../../../core/src/providers/data-table-filter/data-table-filter.service';
 
 @Component({
     selector: 'vdr-order-list',
@@ -42,54 +35,54 @@ export class OrderListComponent
     implements OnInit
 {
     searchControl = new UntypedFormControl('');
-    searchOrderCodeControl = new UntypedFormControl('');
-    searchLastNameControl = new UntypedFormControl('');
-    customFilterForm: UntypedFormGroup;
     orderStates = this.serverConfigService.getOrderProcessStates().map(item => item.name);
-    filterPresets: FilterPreset[] = [
-        {
-            name: 'open',
-            label: _('order.filter-preset-open'),
-            config: {
-                active: false,
-                states: this.orderStates.filter(
-                    s => s !== 'Delivered' && s !== 'Cancelled' && s !== 'Shipped' && s !== 'Draft',
-                ),
-            },
-        },
-        {
-            name: 'shipped',
-            label: _('order.filter-preset-shipped'),
-            config: {
-                active: false,
-                states: ['Shipped'],
-            },
-        },
-        {
-            name: 'completed',
-            label: _('order.filter-preset-completed'),
-            config: {
-                active: false,
-                states: ['Delivered', 'Cancelled'],
-            },
-        },
-        {
-            name: 'active',
-            label: _('order.filter-preset-active'),
-            config: {
-                active: true,
+    activeFilters: DataTableFilter[] = [];
+    readonly filters = this.dataTableFilterService
+        .createConfig<OrderFilterParameter>()
+        .addFilter({
+            id: 'active',
+            type: { kind: 'boolean' },
+            label: _('order.filter-is-active'),
+            toFilterInput: value => ({
+                active: {
+                    eq: value,
+                },
+            }),
+        })
+        .addFilter({
+            id: 'state',
+            type: {
+                kind: 'select',
+                options: this.orderStates.map(s => ({ value: s, label: getOrderStateTranslationToken(s) })),
             },
-        },
-        {
-            name: 'draft',
-            label: _('order.filter-preset-draft'),
-            config: {
-                active: false,
-                states: ['Draft'],
+            label: _('order.state'),
+            toFilterInput: value => ({
+                state: {
+                    in: value,
+                },
+            }),
+        })
+        .addFilter({
+            id: 'orderPlacedAt',
+            type: {
+                kind: 'dateRange',
             },
-        },
-    ];
-    activePreset$: Observable<string>;
+            label: _('order.placed-at'),
+            toFilterInput: value => ({
+                orderPlacedAt: value.dateOperators,
+            }),
+        })
+        .addFilter({
+            id: 'customerLastName',
+            type: { kind: 'text' },
+            label: _('customer.last-name'),
+            toFilterInput: value => ({
+                customerLastName: {
+                    [value.operator]: value.term,
+                },
+            }),
+        })
+        .connectToRoute(this.route);
     canCreateDraftOrder = false;
     private activeChannelIsDefaultChannel = false;
 
@@ -98,6 +91,7 @@ export class OrderListComponent
         private dataService: DataService,
         private localStorageService: LocalStorageService,
         private channelService: ChannelService,
+        private dataTableFilterService: DataTableFilterService,
         router: Router,
         route: ActivatedRoute,
     ) {
@@ -107,21 +101,12 @@ export class OrderListComponent
             (take, skip) => this.dataService.order.getOrders({ take, skip }).refetchOnChannelChange(),
             data => data.orders,
             // eslint-disable-next-line @typescript-eslint/no-shadow
-            (skip, take) =>
-                this.createQueryOptions(
-                    skip,
-                    take,
-                    this.searchControl.value,
-                    this.route.snapshot.queryParamMap.get('filter') || 'open',
-                ),
+            (skip, take) => this.createQueryOptions(skip, take, this.searchControl.value, this.activeFilters),
         );
         this.canCreateDraftOrder = !!this.serverConfigService
             .getOrderProcessStates()
             .find(state => state.name === 'Created')
             ?.to.includes('Draft');
-        if (!this.canCreateDraftOrder) {
-            this.filterPresets = this.filterPresets.filter(p => p.name !== 'draft');
-        }
     }
 
     ngOnInit() {
@@ -130,10 +115,6 @@ export class OrderListComponent
         if (lastFilters) {
             this.setQueryParam(lastFilters, { replaceUrl: true });
         }
-        this.activePreset$ = this.route.queryParamMap.pipe(
-            map(qpm => qpm.get('filter') || 'open'),
-            distinctUntilChanged(),
-        );
         const searchTerms$ = merge(this.searchControl.valueChanges).pipe(
             filter(value => 2 < value.length || value.length === 0),
             debounceTime(250),
@@ -141,47 +122,13 @@ export class OrderListComponent
         const isDefaultChannel$ = this.channelService.defaultChannelIsActive$.pipe(
             tap(isDefault => (this.activeChannelIsDefaultChannel = isDefault)),
         );
-        merge(searchTerms$, isDefaultChannel$, this.route.queryParamMap)
+        merge(searchTerms$, isDefaultChannel$, this.route.queryParamMap, this.filters.valueChanges)
             .pipe(debounceTime(50), takeUntil(this.destroy$))
             .subscribe(val => {
                 this.refresh();
             });
 
         const queryParamMap = this.route.snapshot.queryParamMap;
-        this.customFilterForm = new UntypedFormGroup({
-            states: new UntypedFormControl(queryParamMap.getAll('states') ?? []),
-            placedAtStart: new UntypedFormControl(queryParamMap.get('placedAtStart')),
-            placedAtEnd: new UntypedFormControl(queryParamMap.get('placedAtEnd')),
-        });
-    }
-
-    selectFilterPreset(presetName: string) {
-        const lastCustomFilters = this.localStorageService.get('orderListLastCustomFilters') ?? {};
-        const emptyCustomFilters = { states: undefined, placedAtStart: undefined, placedAtEnd: undefined };
-        const filters = presetName === 'custom' ? lastCustomFilters : emptyCustomFilters;
-        this.setQueryParam(
-            {
-                filter: presetName,
-                page: 1,
-                ...filters,
-            },
-            { replaceUrl: true },
-        );
-    }
-
-    applyCustomFilters() {
-        const formValue = this.customFilterForm.value;
-        const customFilters = {
-            states: formValue.states,
-            placedAtStart: formValue.placedAtStart,
-            placedAtEnd: formValue.placedAtEnd,
-        };
-        this.setQueryParam({
-            filter: 'custom',
-            ...customFilters,
-        });
-        this.customFilterForm.markAsPristine();
-        this.localStorageService.set('orderListLastCustomFilters', customFilters);
     }
 
     private createQueryOptions(
@@ -189,60 +136,20 @@ export class OrderListComponent
         skip: number,
         take: number,
         searchTerm: string,
-        activeFilterPreset?: string,
+        activeFilters: DataTableFilter[] = [],
     ): { options: OrderListOptions } {
-        const filterConfig = this.filterPresets.find(p => p.name === activeFilterPreset);
-        // eslint-disable-next-line @typescript-eslint/no-shadow
-        let filter: OrderFilterParameter = {};
         let filterOperator: LogicalOperator = LogicalOperator.AND;
-        if (filterConfig) {
-            if (filterConfig.config.active != null) {
-                filter.active = {
-                    eq: filterConfig.config.active,
-                };
-            }
-            if (filterConfig.config.states) {
-                filter.state = {
-                    in: filterConfig.config.states,
-                };
-            }
-        } else if (activeFilterPreset === 'custom') {
-            const queryParams = this.route.snapshot.queryParamMap;
-            const states = queryParams.getAll('states') ?? [];
-            const placedAtStart = queryParams.get('placedAtStart');
-            const placedAtEnd = queryParams.get('placedAtEnd');
-            if (states.length) {
-                filter.state = {
-                    in: states,
-                };
-            }
-            if (placedAtStart && placedAtEnd) {
-                filter.orderPlacedAt = {
-                    between: {
-                        start: placedAtStart,
-                        end: placedAtEnd,
-                    },
-                };
-            } else if (placedAtStart) {
-                filter.orderPlacedAt = {
-                    after: placedAtStart,
-                };
-            } else if (placedAtEnd) {
-                filter.orderPlacedAt = {
-                    before: placedAtEnd,
-                };
-            }
-        }
+        let filterInput = this.filters.createFilterInput();
         if (this.activeChannelIsDefaultChannel) {
-            filter = {
-                ...(filter ?? {}),
+            filterInput = {
+                ...(filterInput ?? {}),
                 type: {
                     notEq: OrderType.Seller,
                 },
             };
         }
         if (searchTerm) {
-            filter = {
+            filterInput = {
                 customerLastName: {
                     contains: searchTerm,
                 },
@@ -260,7 +167,7 @@ export class OrderListComponent
                 skip,
                 take,
                 filter: {
-                    ...(filter ?? {}),
+                    ...(filterInput ?? {}),
                 },
                 sort: {
                     updatedAt: SortOrder.DESC,

+ 13 - 12
packages/admin-ui/src/lib/static/i18n-messages/cs.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Smazat zónu?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "Vytvořit kolekci",
     "create-new-facet": "Vytvořit nový atribut",
     "create-new-product": "Nový produkt",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Akce",
+    "add-filter": "",
     "add-item-to-list": "Přidat položku do seznamu",
     "add-new-variants": "Přidat {count, plural, one {variantu} few {{count} varianty} other {{count} variant}}",
     "add-note": "Přidat poznámku",
+    "apply": "",
     "available-languages": "Dostupné jazyky",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Zrušit",
     "cancel-navigation": "Zrušit navigaci",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "",
     "okay": "",
     "open": "Otevřít",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Heslo",
     "price": "Cena",
     "price-with-tax": "Cena s daní",
@@ -481,7 +491,6 @@
     "add-surcharge": "Přidat příplatek",
     "added-items": "Přidat položky",
     "amount": "Částka",
-    "apply-filters": "Filtrovat",
     "arrange-additional-payment": "",
     "billing-address": "Fakturační adresa",
     "cancel": "Zrušit",
@@ -508,12 +517,7 @@
     "error-message": "",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Vlastní",
-    "filter-preset-active": "Aktivní",
-    "filter-preset-completed": "Uzavřené",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Otevřené",
-    "filter-preset-shipped": "Expedované",
+    "filter-is-active": "",
     "fulfill": "Zpracovat",
     "fulfill-order": "Zpracovat objednávku",
     "fulfillment": "Zpracování",
@@ -566,8 +570,6 @@
     "payment-state": "Stav",
     "payment-to-refund": "Platba k refundaci",
     "placed-at": "Datum objednávky",
-    "placed-at-end": "Objednávky do",
-    "placed-at-start": "Objednávky od",
     "preview-changes": "Náhled změn",
     "product-name": "Název produktu",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Košík",
-    "all-orders": "Všechny objednávky",
     "arranging-additional-payment": "Sjednávání doplatku",
     "arranging-payment": "Zřizování platby",
     "authorized": "Autorizováno",
@@ -732,4 +733,4 @@
     "job-result": "Výsledek úlohy",
     "job-state": "Stav úlohy"
   }
-}
+}

+ 15 - 16
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Zone löschen?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "Neue Kollektion anlegen",
     "create-new-facet": "Neue Facette erstellen",
     "create-new-product": "Neues Produkt",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Aktionen",
+    "add-filter": "",
     "add-item-to-list": "Artikel zur Wunschliste hinzufügen",
     "add-new-variants": "{count, plural, one {1 Variante} other {{count} Varianten}} hinzufügen",
     "add-note": "Notiz hinzufügen",
+    "apply": "",
     "available-languages": "Verfügbare Sprachen",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Abbrechen",
     "cancel-navigation": "Navigation abbrechen",
@@ -243,7 +246,7 @@
     "log-out": "Abmelden",
     "login": "Anmelden",
     "login-image-title": "Hallo! Willkommen zurück. Schön, dich zu sehen.",
-    "login-title": "Anmelden bei Vendure",
+    "login-title": "Anmelden bei {brand}",
     "manage-tags": "Tags verwalten",
     "manage-tags-description": "Tagbeschreibungen verwalten",
     "medium-date": "",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Tags aktualisiert",
     "okay": "",
     "open": "Öffnen",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Passwort",
     "price": "Preis",
     "price-with-tax": "Preis mit Steuer",
@@ -296,9 +306,7 @@
     "username": "Benutzername",
     "view-next-month": "Nächsten Monat anzeigen",
     "view-previous-month": "Vorherigen Monat anzeigen",
-    "with-selected": "Auswahl...",
-    "login-title": "Anmelden bei {brand}",
-    "login-image-title": "Hallo! Willkommen zurück. Schön, dich zu sehen."
+    "with-selected": "Auswahl..."
   },
   "customer": {
     "add-customer-to-group": "Kunde zu Gruppe hinzufügen",
@@ -483,7 +491,6 @@
     "add-surcharge": "Aufschlag hinzufügen",
     "added-items": "Hinzugefügte Artikel",
     "amount": "Betrag",
-    "apply-filters": "Filter anwenden",
     "arrange-additional-payment": "Zusätzliche Zahlung veranlassen",
     "billing-address": "Rechnungsadresse",
     "cancel": "Abbrechen",
@@ -510,12 +517,7 @@
     "error-message": "",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Benutzerdefiniert",
-    "filter-preset-active": "Aktiv",
-    "filter-preset-completed": "Abgeschlossen",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Ausstehend",
-    "filter-preset-shipped": "Versandt",
+    "filter-is-active": "",
     "fulfill": "Ausführen",
     "fulfill-order": "Auftragsausführung",
     "fulfillment": "Ausführung",
@@ -568,8 +570,6 @@
     "payment-state": "Status",
     "payment-to-refund": "Zu erstattende Zahlung",
     "placed-at": "",
-    "placed-at-end": "",
-    "placed-at-start": "",
     "preview-changes": "Änderungsvorschau",
     "product-name": "Produktname",
     "product-sku": "Artikelnummer",
@@ -696,7 +696,6 @@
   },
   "state": {
     "adding-items": "Artikel hinzufügen",
-    "all-orders": "Alle Bestellungsstatus",
     "arranging-additional-payment": "Zusätzliche Zahlung einrichten",
     "arranging-payment": "Zahlung einrichten",
     "authorized": "Autorisiert",
@@ -734,4 +733,4 @@
     "job-result": "Job-Ergebnis",
     "job-state": "Job-Status"
   }
-}
+}

+ 14 - 15
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Delete zone?",
     "confirm-deletion-of-unused-variants-body": "The following product variants have been made obsolete due to the addition of new options. They will be deleted during the creation of the new product variants.",
     "confirm-deletion-of-unused-variants-title": "Delete obsolete product variants?",
-    "create-draft-order": "Create draft order",
     "create-new-collection": "Create new collection",
     "create-new-facet": "Create new facet",
     "create-new-product": "New product",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Actions",
+    "add-filter": "Add filter",
     "add-item-to-list": "Add item to list",
     "add-new-variants": "Add {count, plural, one {1 variant} other {{count} variants}}",
     "add-note": "Add note",
+    "apply": "Apply",
     "available-languages": "Available languages",
     "boolean-and": "and",
+    "boolean-false": "false",
     "boolean-or": "or",
+    "boolean-true": "true",
     "browser-default": "Browser default",
     "cancel": "Cancel",
     "cancel-navigation": "Cancel navigation",
@@ -243,7 +246,7 @@
     "log-out": "Log out",
     "login": "Log in",
     "login-image-title": "Hi! Welcome back. Good to see you.",
-    "login-title": "Log in to Vendure",
+    "login-title": "Log in to {brand}",
     "manage-tags": "Manage tags",
     "manage-tags-description": "Update or delete tags globally.",
     "medium-date": "Medium date",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Successfully updated tags",
     "okay": "Okay",
     "open": "Open",
+    "operator-contains": "contains",
+    "operator-eq": "equals",
+    "operator-not-contains": "does not contain",
+    "operator-not-eq": "does not equal",
+    "operator-notContains": "does not contain",
+    "operator-notEq": "",
+    "operator-regex": "matches regex",
     "password": "Password",
     "price": "Price",
     "price-with-tax": "Price with tax",
@@ -296,9 +306,7 @@
     "username": "Username",
     "view-next-month": "View next month",
     "view-previous-month": "View previous month",
-    "with-selected": "With {count} selected...",
-    "login-title": "Log in to {brand}",
-    "login-image-title": "Hi! Welcome back. Good to see you."
+    "with-selected": "With {count} selected..."
   },
   "customer": {
     "add-customer-to-group": "Add customer to group",
@@ -483,7 +491,6 @@
     "add-surcharge": "Add surcharge",
     "added-items": "Added items",
     "amount": "Amount",
-    "apply-filters": "Apply filters",
     "arrange-additional-payment": "Arrange additional payment",
     "billing-address": "Billing address",
     "cancel": "Cancel",
@@ -510,12 +517,7 @@
     "error-message": "Error message",
     "existing-address": "Existing address",
     "existing-customer": "Existing customer",
-    "filter-custom": "Custom",
-    "filter-preset-active": "Active",
-    "filter-preset-completed": "Completed",
-    "filter-preset-draft": "Draft",
-    "filter-preset-open": "Open",
-    "filter-preset-shipped": "Shipped",
+    "filter-is-active": "Is active",
     "fulfill": "Fulfill",
     "fulfill-order": "Fulfill order",
     "fulfillment": "Fulfillment",
@@ -568,8 +570,6 @@
     "payment-state": "State",
     "payment-to-refund": "Payment to refund",
     "placed-at": "Placed at",
-    "placed-at-end": "Placed at - until",
-    "placed-at-start": "Placed at - from",
     "preview-changes": "Preview changes",
     "product-name": "Product name",
     "product-sku": "SKU",
@@ -696,7 +696,6 @@
   },
   "state": {
     "adding-items": "Adding items",
-    "all-orders": "All order states",
     "arranging-additional-payment": "Arranging additional payment",
     "arranging-payment": "Arranging payment",
     "authorized": "Authorized",

+ 13 - 12
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "¿Eliminar zona?",
     "confirm-deletion-of-unused-variants-body": "Las siguientes variantes de producto han quedado obsoletas debido a la incorporación de nuevas opciones. Se eliminarán durante la creación de las nuevas variantes de producto.",
     "confirm-deletion-of-unused-variants-title": "¿Eliminar variantes de producto obsoletas?",
-    "create-draft-order": "",
     "create-new-collection": "Crear nueva colección",
     "create-new-facet": "Crear nueva faceta",
     "create-new-product": "Crear nuevo producto",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Acciones",
+    "add-filter": "",
     "add-item-to-list": "Añadir artículo a la lista",
     "add-new-variants": "Añadir {count, plural, one {1 variante} other {{count} variantes}}",
     "add-note": "Añadir nota",
+    "apply": "",
     "available-languages": "Idiomas disponibles",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Cancelar",
     "cancel-navigation": "Cancelar navegación",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Etiquetas actualizadas con éxito",
     "okay": "",
     "open": "Abrir",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Contraseña",
     "price": "Precio",
     "price-with-tax": "Precio (impuesto incluido)",
@@ -481,7 +491,6 @@
     "add-surcharge": "Añadir recargo",
     "added-items": "Añadir artículos",
     "amount": "Precio",
-    "apply-filters": "Aplicar filtros",
     "arrange-additional-payment": "Ordenar pagos adicionales",
     "billing-address": "Dirección de facturación",
     "cancel": "Cancelar",
@@ -508,12 +517,7 @@
     "error-message": "Mensaje de error",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Personalizado",
-    "filter-preset-active": "Activo",
-    "filter-preset-completed": "Completado",
-    "filter-preset-draft": "",
-    "filter-preset-open": "En curso",
-    "filter-preset-shipped": "Enviado",
+    "filter-is-active": "",
     "fulfill": "Completar",
     "fulfill-order": "Completar pedido",
     "fulfillment": "Fulfillment",
@@ -566,8 +570,6 @@
     "payment-state": "Estado",
     "payment-to-refund": "Pago por reembolso",
     "placed-at": "Realizado en",
-    "placed-at-end": "Realizado en - hasta",
-    "placed-at-start": "Realizado en - desde",
     "preview-changes": "Previsualizar cambios",
     "product-name": "Nombre del producto",
     "product-sku": "Código de referencia",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Añadiendo artículos",
-    "all-orders": "Todos los estados del pedido",
     "arranging-additional-payment": "Gestionar pago adicional",
     "arranging-payment": "Gestionar pago",
     "authorized": "Autorizado",
@@ -732,4 +733,4 @@
     "job-result": "Resultado",
     "job-state": "Estado"
   }
-}
+}

+ 13 - 12
packages/admin-ui/src/lib/static/i18n-messages/fr.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Supprimer zone ?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "Créer nouvelle collection",
     "create-new-facet": "Créer nouveau composant",
     "create-new-product": "Nouveau produit",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Actions",
+    "add-filter": "",
     "add-item-to-list": "Ajouter un article à la liste",
     "add-new-variants": "Ajout {count, plural, one {d'une variation} other {de {count} variations}}",
     "add-note": "Ajouter une note",
+    "apply": "",
     "available-languages": "Langues disponibles",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Annuler",
     "cancel-navigation": "Annuler la navigation",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Mots-clés mis à jour avec succès",
     "okay": "",
     "open": "Ouvert",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Mot de passe",
     "price": "Prix",
     "price-with-tax": "Prix avec taxe",
@@ -481,7 +491,6 @@
     "add-surcharge": "Ajouter surcharge",
     "added-items": "Ajouter des éléments",
     "amount": "Quantité",
-    "apply-filters": "Applique les filtres",
     "arrange-additional-payment": "Arranger un paiment additionnel",
     "billing-address": "Adresse de facturation",
     "cancel": "Annuler",
@@ -508,12 +517,7 @@
     "error-message": "Message d'erreur",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Personnalisé",
-    "filter-preset-active": "Active",
-    "filter-preset-completed": "Terminée",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Ouverte",
-    "filter-preset-shipped": "Expédiée",
+    "filter-is-active": "",
     "fulfill": "Préparer",
     "fulfill-order": "Préparer la commande",
     "fulfillment": "Préparation",
@@ -566,8 +570,6 @@
     "payment-state": "Etat",
     "payment-to-refund": "Montant à rembourser",
     "placed-at": "Placé à",
-    "placed-at-end": "Placé à - jusqu'à",
-    "placed-at-start": "Placé à - depuis",
     "preview-changes": "Aperçu des modifications",
     "product-name": "Nom du produit",
     "product-sku": "UGS",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Ajout d'articles",
-    "all-orders": "Tous les états de commande",
     "arranging-additional-payment": "Paiement additionel en cours",
     "arranging-payment": "Paiement en cours",
     "authorized": "Autorisé",
@@ -732,4 +733,4 @@
     "job-result": "Résultat de la tâche",
     "job-state": "Etat de la tâche"
   }
-}
+}

+ 14 - 13
packages/admin-ui/src/lib/static/i18n-messages/it.json

@@ -1,7 +1,7 @@
 {
   "admin": {
     "create-new-administrator": "Crea nuovo amministratore",
-    "search-administrator":"Cerca per nome / cognome / email"
+    "search-administrator": "Cerca per nome / cognome / email"
   },
   "asset": {
     "add-asset": "Aggiungi immagine",
@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Eliminare la zona?",
     "confirm-deletion-of-unused-variants-body": "Le seguenti varianti sono diventate obsolete a seguito dell'aggiunta di nuove opzioni. Queste verranno cancellate durante la creazione delle nuove varianti.",
     "confirm-deletion-of-unused-variants-title": "Cancellare le varianti obsolete?",
-    "create-draft-order": "",
     "create-new-collection": "Crea nuova Collezione",
     "create-new-facet": "Crea nuovo attributo",
     "create-new-product": "Crea nuovo prodotto",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Azioni",
+    "add-filter": "",
     "add-item-to-list": "Aggiungi elemento alla lista",
     "add-new-variants": "Aggiungi {count, plural, one {1 variante} other {{count} varianti}}",
     "add-note": "Aggiungi nota",
+    "apply": "",
     "available-languages": "Lingue disponibili",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Annulla",
     "cancel-navigation": "Annulla navigazione",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Tags aggiornati con successo",
     "okay": "",
     "open": "Apri",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Password",
     "price": "Prezzo",
     "price-with-tax": "Prezzo tasse incluse",
@@ -481,7 +491,6 @@
     "add-surcharge": "Aggiungi sovrapprezzo",
     "added-items": "Aggiunti prodotti",
     "amount": "Importo",
-    "apply-filters": "Applica filtri",
     "arrange-additional-payment": "Effettua un ulteriore pagamento",
     "billing-address": "Indirizzo di fatturazione",
     "cancel": "Annulla",
@@ -508,12 +517,7 @@
     "error-message": "Messaggio di errore",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Personalizzato",
-    "filter-preset-active": "Attivi",
-    "filter-preset-completed": "Completati",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Aperti",
-    "filter-preset-shipped": "Spediti",
+    "filter-is-active": "",
     "fulfill": "Consegna",
     "fulfill-order": "Consegna ordine",
     "fulfillment": "Consegna",
@@ -566,8 +570,6 @@
     "payment-state": "Stato",
     "payment-to-refund": "Pagamento da rimborsare",
     "placed-at": "Effettuato a",
-    "placed-at-end": "Effettuato a - fino",
-    "placed-at-start": "Effettuato a - da",
     "preview-changes": "Anteprima modifiche",
     "product-name": "Nome prodotto",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Aggiunta prodotti",
-    "all-orders": "Tutti gli stati ordine",
     "arranging-additional-payment": "Scelta pagamento ulteriore",
     "arranging-payment": "Scelta pagamento",
     "authorized": "Autorizzato",
@@ -732,4 +733,4 @@
     "job-result": "Risultato operazione",
     "job-state": "Stato operazione"
   }
-}
+}

+ 14 - 13
packages/admin-ui/src/lib/static/i18n-messages/pl.json

@@ -1,7 +1,7 @@
 {
   "admin": {
     "create-new-administrator": "Utwórz konto administratora",
-    "search-administrator":"Szukaj według imienia / nazwiska / adresu e-mail"
+    "search-administrator": "Szukaj według imienia / nazwiska / adresu e-mail"
   },
   "asset": {
     "add-asset": "Dodaj zasób",
@@ -94,7 +94,6 @@
     "confirm-delete-zone": "",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "Utwórz nową kolekcje",
     "create-new-facet": "Utwórz faset",
     "create-new-product": "Nowy produkt",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Akcje",
+    "add-filter": "",
     "add-item-to-list": "",
     "add-new-variants": "Dodaj {count, plural, one {1 wariant} other {{count} wariantów}}",
     "add-note": "",
+    "apply": "",
     "available-languages": "Dostępne języki",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Anuluj",
     "cancel-navigation": "Anuluj nawigacje",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "",
     "okay": "",
     "open": "Otwórz",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Hasło",
     "price": "Cena",
     "price-with-tax": "Cena z podatkiem",
@@ -481,7 +491,6 @@
     "add-surcharge": "",
     "added-items": "",
     "amount": "Ilość",
-    "apply-filters": "",
     "arrange-additional-payment": "",
     "billing-address": "",
     "cancel": "Anuluj",
@@ -508,12 +517,7 @@
     "error-message": "",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "",
-    "filter-preset-active": "",
-    "filter-preset-completed": "",
-    "filter-preset-draft": "",
-    "filter-preset-open": "",
-    "filter-preset-shipped": "",
+    "filter-is-active": "",
     "fulfill": "Zrealizuj",
     "fulfill-order": "Zrealizuj zamówienie",
     "fulfillment": "Realizacja",
@@ -566,8 +570,6 @@
     "payment-state": "Status",
     "payment-to-refund": "Płatność do zwrotu",
     "placed-at": "",
-    "placed-at-end": "",
-    "placed-at-start": "",
     "preview-changes": "",
     "product-name": "Nazwa produktu",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Dodawanie",
-    "all-orders": "",
     "arranging-additional-payment": "",
     "arranging-payment": "Oczekiwanie na płatność",
     "authorized": "",
@@ -732,4 +733,4 @@
     "job-result": "Rezultat zlecenia",
     "job-state": "Status zlecenia"
   }
-}
+}

+ 14 - 13
packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json

@@ -1,7 +1,7 @@
 {
   "admin": {
     "create-new-administrator": "Criar novo usuário administrador",
-    "search-administrator":"Pesquisar por nome / sobrenome / e-mail"
+    "search-administrator": "Pesquisar por nome / sobrenome / e-mail"
   },
   "asset": {
     "add-asset": "Adicionar imagens",
@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Excluir zona?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "Criar nova categoria",
     "create-new-facet": "Criar nova etiqueta",
     "create-new-product": "Novo produto",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Ações",
+    "add-filter": "",
     "add-item-to-list": "Adicionar item à lista",
     "add-new-variants": "Adicionar {count, plural, one {1 variant} other {{count} variants}}",
     "add-note": "Adicionar nota",
+    "apply": "",
     "available-languages": "Idiomas disponíveis",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Cancelar",
     "cancel-navigation": "Cancelar navegação",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "",
     "okay": "",
     "open": "Aberto",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Senha",
     "price": "Preço",
     "price-with-tax": "Preço com impostos",
@@ -481,7 +491,6 @@
     "add-surcharge": "Adicionar sobretaxa",
     "added-items": "Itens adicionados",
     "amount": "Total",
-    "apply-filters": "Aplicar filtros",
     "arrange-additional-payment": "",
     "billing-address": "Endereço de cobrança",
     "cancel": "Cancelar",
@@ -508,12 +517,7 @@
     "error-message": "",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Customizar",
-    "filter-preset-active": "Ativo",
-    "filter-preset-completed": "Concluído",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Aberto",
-    "filter-preset-shipped": "Enviado",
+    "filter-is-active": "",
     "fulfill": "Executar",
     "fulfill-order": "Executar o pedido",
     "fulfillment": "Execução",
@@ -566,8 +570,6 @@
     "payment-state": "Estado",
     "payment-to-refund": "Pagamento para reembolso",
     "placed-at": "Posicionado em",
-    "placed-at-end": "Posicionado no final",
-    "placed-at-start": "Posicionado no início",
     "preview-changes": "Revisar mudanças",
     "product-name": "Nome do produto",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Criando itens",
-    "all-orders": "Todos os pedidos",
     "arranging-additional-payment": "",
     "arranging-payment": "Organização de pagamento",
     "authorized": "Autorizada",
@@ -732,4 +733,4 @@
     "job-result": "Resultado do trabalho",
     "job-state": "Estado do trabalho"
   }
-}
+}

+ 14 - 13
packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json

@@ -1,7 +1,7 @@
 {
   "admin": {
     "create-new-administrator": "Adicionar novo administrador",
-    "search-administrator":"Procurar por primeiro nome / último nome / e-mail"
+    "search-administrator": "Procurar por primeiro nome / último nome / e-mail"
   },
   "asset": {
     "add-asset": "Adicionar imagens",
@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Eliminar região?",
     "confirm-deletion-of-unused-variants-body": "As variantes listadas abaixo estão obsoletas e serão eliminadas devido à adição de novas opções.",
     "confirm-deletion-of-unused-variants-title": "Eliminar as variantes obsoletas?",
-    "create-draft-order": "",
     "create-new-collection": "Criar nova categoria",
     "create-new-facet": "Criar nova etiqueta",
     "create-new-product": "Novo produto",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Acções",
+    "add-filter": "",
     "add-item-to-list": "Adicionar item à lista",
     "add-new-variants": "Adicionar {count, plural, one {variante} other {{count} variantes}}",
     "add-note": "Adicionar nota",
+    "apply": "",
     "available-languages": "Idiomas disponíveis",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "Navegador padrão",
     "cancel": "Cancelar",
     "cancel-navigation": "Continuar a editar",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Tags actualizadas com sucesso",
     "okay": "",
     "open": "Visualizar",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Palavra passe",
     "price": "Preço",
     "price-with-tax": "Preço com impostos",
@@ -481,7 +491,6 @@
     "add-surcharge": "Adicionar sobretaxa",
     "added-items": "Itens adicionados",
     "amount": "Total",
-    "apply-filters": "Aplicar filtros",
     "arrange-additional-payment": "Configurar pagamento adicional",
     "billing-address": "Morada de faturação",
     "cancel": "Cancelar",
@@ -508,12 +517,7 @@
     "error-message": "Mensagem de erro",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Customizar",
-    "filter-preset-active": "Activa",
-    "filter-preset-completed": "Concluída",
-    "filter-preset-draft": "",
-    "filter-preset-open": "A processar",
-    "filter-preset-shipped": "Enviada",
+    "filter-is-active": "",
     "fulfill": "Enviar",
     "fulfill-order": "Enviar a encomenda",
     "fulfillment": "Entrega",
@@ -566,8 +570,6 @@
     "payment-state": "Estado",
     "payment-to-refund": "Pagamento para reembolso",
     "placed-at": "Adicionada em",
-    "placed-at-end": "Adicionada até",
-    "placed-at-start": "Adicionada a partir de",
     "preview-changes": "Revisar mudanças",
     "product-name": "Nome do produto",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "A adicionar itens",
-    "all-orders": "Todas as encomendas",
     "arranging-additional-payment": "A configurar pagamento adicional",
     "arranging-payment": "Pagamento pendente",
     "authorized": "Autorizado",
@@ -732,4 +733,4 @@
     "job-result": "Resultado do trabalho",
     "job-state": "Estado do trabalho"
   }
-}
+}

+ 13 - 12
packages/admin-ui/src/lib/static/i18n-messages/ru.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Удалить зону?",
     "confirm-deletion-of-unused-variants-body": "Следующие варианты товаров устарели из за добавления новых опций. Они будут удалены во время создания новых вариантов товара.",
     "confirm-deletion-of-unused-variants-title": "Удалить устаревшие варианты товара?",
-    "create-draft-order": "",
     "create-new-collection": "Создать новую коллекцию",
     "create-new-facet": "Создать новый тег",
     "create-new-product": "Создать новый товар",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Действия",
+    "add-filter": "",
     "add-item-to-list": "Добавить позицию в список",
     "add-new-variants": "Добавить {count, plural, one {1 вариант} other {{count} вариантов}}",
     "add-note": "Добавить заметку",
+    "apply": "",
     "available-languages": "Доступные языки",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Отмена",
     "cancel-navigation": "Отменить навигацию",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Успешно обновлены теги",
     "okay": "",
     "open": "Открыть",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Пароль",
     "price": "Цена",
     "price-with-tax": "Цена с налогом",
@@ -481,7 +491,6 @@
     "add-surcharge": "Добавить доплату",
     "added-items": "Добавленные позиции",
     "amount": "Количество",
-    "apply-filters": "Применить фильтры",
     "arrange-additional-payment": "Организовать доплату",
     "billing-address": "Платежный адрес",
     "cancel": "Отмена",
@@ -508,12 +517,7 @@
     "error-message": "Сообщение об ошибке",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Под заказ",
-    "filter-preset-active": "Активные",
-    "filter-preset-completed": "Завершенные",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Открытые",
-    "filter-preset-shipped": "Отправленные",
+    "filter-is-active": "",
     "fulfill": "Выполнить",
     "fulfill-order": "Выполнить заказ",
     "fulfillment": "Выполнение",
@@ -566,8 +570,6 @@
     "payment-state": "Состояние",
     "payment-to-refund": "Платеж к возврату",
     "placed-at": "Размещено в",
-    "placed-at-end": "Размещено в - до",
-    "placed-at-start": "Размещено в - от",
     "preview-changes": "Предварительный просмотр изменений",
     "product-name": "Наименование товара",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Добавление позиций",
-    "all-orders": "Все состояния заказа",
     "arranging-additional-payment": "Организация дополнительной оплаты",
     "arranging-payment": "Организация оплаты",
     "authorized": "Авторизовано",
@@ -732,4 +733,4 @@
     "job-result": "Результат задания",
     "job-state": "Состояние задания"
   }
-}
+}

+ 14 - 13
packages/admin-ui/src/lib/static/i18n-messages/uk.json

@@ -1,7 +1,7 @@
 {
   "admin": {
     "create-new-administrator": "Створити нового адміністратора",
-    "search-administrator":"Пошук за ім'ям / прізвищем / електронною поштою"
+    "search-administrator": "Пошук за ім'ям / прізвищем / електронною поштою"
   },
   "asset": {
     "add-asset": "Додати медіа-об'єкт",
@@ -94,7 +94,6 @@
     "confirm-delete-zone": "Видалити зону?",
     "confirm-deletion-of-unused-variants-body": "Наступні варіанти товару застаріли через додавання нових опцій. Вони будуть видалені під час створення нових варіантів товару.",
     "confirm-deletion-of-unused-variants-title": "Видалити застарілі варіанти товару?",
-    "create-draft-order": "",
     "create-new-collection": "Створити нову колекцію",
     "create-new-facet": "Створити новий тег",
     "create-new-product": "Створити новий товар",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "Дії",
+    "add-filter": "",
     "add-item-to-list": "Додати позицію в список",
     "add-new-variants": "Додати {count, plural, one {1 варіант} other {{count} варіантів}}",
     "add-note": "Додати замітку",
+    "apply": "",
     "available-languages": "Доступні мови",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "Скасування",
     "cancel-navigation": "Скасувати навігацію",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "Успішно оновлені теги",
     "okay": "",
     "open": "Відкрити",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "Пароль",
     "price": "Ціна",
     "price-with-tax": "Ціна з податком",
@@ -481,7 +491,6 @@
     "add-surcharge": "Додати доплату",
     "added-items": "Додані позиції",
     "amount": "Кількість",
-    "apply-filters": "Застосувати фільтри",
     "arrange-additional-payment": "Організувати доплату",
     "billing-address": "Платіжна адреса",
     "cancel": "Скасування",
@@ -508,12 +517,7 @@
     "error-message": "Повідомлення про помилку",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "Під замовлення",
-    "filter-preset-active": "Активні",
-    "filter-preset-completed": "Завершені",
-    "filter-preset-draft": "",
-    "filter-preset-open": "Відкриті",
-    "filter-preset-shipped": "Відправлені",
+    "filter-is-active": "",
     "fulfill": "Виконати",
     "fulfill-order": "Виконати замовлення",
     "fulfillment": "Виконання",
@@ -566,8 +570,6 @@
     "payment-state": "Стан",
     "payment-to-refund": "Платіж до повернення",
     "placed-at": "Розміщено в",
-    "placed-at-end": "Розміщено в - до",
-    "placed-at-start": "Розміщено в - від",
     "preview-changes": "Попередній перегляд змін",
     "product-name": "Найменування товару",
     "product-sku": "SKU",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "Додавання позицій",
-    "all-orders": "Всі стани замовлення",
     "arranging-additional-payment": "Організація додаткової оплати",
     "arranging-payment": "Організація оплати",
     "authorized": "Авторизовано",
@@ -732,4 +733,4 @@
     "job-result": "Результат завдання",
     "job-state": "Стан завдання"
   }
-}
+}

+ 14 - 13
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -1,7 +1,7 @@
 {
   "admin": {
     "create-new-administrator": "添加管理员",
-    "search-administrator":"通过名字/姓氏/电子邮件搜索"
+    "search-administrator": "通过名字/姓氏/电子邮件搜索"
   },
   "asset": {
     "add-asset": "添加资源",
@@ -94,7 +94,6 @@
     "confirm-delete-zone": "确认删除分区么?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "添加系列",
     "create-new-facet": "添加特征",
     "create-new-product": "添加商品",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "操作",
+    "add-filter": "",
     "add-item-to-list": "添加到列表",
     "add-new-variants": "添加{count}个商品规格",
     "add-note": "添加注释",
+    "apply": "",
     "available-languages": "可用语言",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "取消",
     "cancel-navigation": "取消",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "成功更新标签",
     "okay": "",
     "open": "详情",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "密码",
     "price": "价格",
     "price-with-tax": "价格(含税)",
@@ -481,7 +491,6 @@
     "add-surcharge": "添加附加费",
     "added-items": "添加项目",
     "amount": "金额",
-    "apply-filters": "过滤",
     "arrange-additional-payment": "添加额外付款",
     "billing-address": "账单地址",
     "cancel": "取消",
@@ -508,12 +517,7 @@
     "error-message": "错误消息",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "自定义",
-    "filter-preset-active": "正在选择商品",
-    "filter-preset-completed": "已完成",
-    "filter-preset-draft": "",
-    "filter-preset-open": "已下单",
-    "filter-preset-shipped": "已发货",
+    "filter-is-active": "",
     "fulfill": "已配货",
     "fulfill-order": "接受订单",
     "fulfillment": "配货记录",
@@ -566,8 +570,6 @@
     "payment-state": "付款状态",
     "payment-to-refund": "退款订单付款信息",
     "placed-at": "",
-    "placed-at-end": "",
-    "placed-at-start": "",
     "preview-changes": "",
     "product-name": "产品名称",
     "product-sku": "库存编码",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "正在选择商品",
-    "all-orders": "所以订单状态",
     "arranging-additional-payment": "在付款",
     "arranging-payment": "正在付款",
     "authorized": "已授权",
@@ -732,4 +733,4 @@
     "job-result": "任务结果",
     "job-state": "任务状态"
   }
-}
+}

+ 13 - 12
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -94,7 +94,6 @@
     "confirm-delete-zone": "",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
-    "create-draft-order": "",
     "create-new-collection": "新增系列",
     "create-new-facet": "新增特徵",
     "create-new-product": "新增商品",
@@ -192,12 +191,16 @@
   "common": {
     "ID": "ID",
     "actions": "操作",
+    "add-filter": "",
     "add-item-to-list": "",
     "add-new-variants": "新增{count}個商品規格",
     "add-note": "",
+    "apply": "",
     "available-languages": "可用語言",
     "boolean-and": "",
+    "boolean-false": "",
     "boolean-or": "",
+    "boolean-true": "",
     "browser-default": "",
     "cancel": "取消",
     "cancel-navigation": "取消",
@@ -266,6 +269,13 @@
     "notify-updated-tags-success": "",
     "okay": "",
     "open": "詳情",
+    "operator-contains": "",
+    "operator-eq": "",
+    "operator-not-contains": "",
+    "operator-not-eq": "",
+    "operator-notContains": "",
+    "operator-notEq": "",
+    "operator-regex": "",
     "password": "密碼",
     "price": "價格",
     "price-with-tax": "價格(連税)",
@@ -481,7 +491,6 @@
     "add-surcharge": "",
     "added-items": "",
     "amount": "金額",
-    "apply-filters": "",
     "arrange-additional-payment": "",
     "billing-address": "",
     "cancel": "取消",
@@ -508,12 +517,7 @@
     "error-message": "",
     "existing-address": "",
     "existing-customer": "",
-    "filter-custom": "",
-    "filter-preset-active": "",
-    "filter-preset-completed": "",
-    "filter-preset-draft": "",
-    "filter-preset-open": "",
-    "filter-preset-shipped": "",
+    "filter-is-active": "",
     "fulfill": "已配貨",
     "fulfill-order": "接受訂單",
     "fulfillment": "配貨記錄",
@@ -566,8 +570,6 @@
     "payment-state": "付款狀態",
     "payment-to-refund": "退款訂單付款信息",
     "placed-at": "",
-    "placed-at-end": "",
-    "placed-at-start": "",
     "preview-changes": "",
     "product-name": "產品名稱",
     "product-sku": "庫存編碼",
@@ -694,7 +696,6 @@
   },
   "state": {
     "adding-items": "正在選擇商品",
-    "all-orders": "",
     "arranging-additional-payment": "",
     "arranging-payment": "正在付款",
     "authorized": "",
@@ -732,4 +733,4 @@
     "job-result": "",
     "job-state": ""
   }
-}
+}

+ 27 - 0
packages/admin-ui/src/lib/static/styles/global/_buttons.scss

@@ -1,3 +1,29 @@
+.button {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-end;
+    align-items: center;
+    padding: var(--space-unit);
+    font-size: var(--font-size-sm);
+    gap: 12px;
+    border: none;
+    border-radius: var(--border-radius-sm);
+    cursor: pointer;
+    box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.06), 0px 1px 4px rgba(0, 0, 0, 0.03),
+        0px 2px 6px rgba(0, 0, 0, 0.03), 0px 2px 11px rgba(0, 0, 0, 0.04);
+    background-color: white;
+    color: var(--color-weight-700);
+    &:disabled {
+        background-color: var(--color-weight-100);
+        color: var(--color-weight-500);
+        cursor: not-allowed;
+    }
+    &:not(:disabled):hover {
+        background-color: var(--color-weight-100);
+        color: var(--color-weight-800);
+    }
+}
+
 .button-small {
     display: flex;
     flex-direction: row;
@@ -5,6 +31,7 @@
     align-items: center;
     padding: 0 var(--space-unit);
     height: calc(var(--space-unit) * 3);
+    font-size: var(--font-size-xs);
     gap: 12px;
     border: none;
     border-radius: var(--border-radius-lg);

+ 4 - 0
packages/admin-ui/src/lib/static/styles/global/_forms.scss

@@ -42,6 +42,10 @@ textarea,
             background-size: 0% 100%;
         }
     }
+    font-weight: 400;
+    &::placeholder {
+        color: var(--color-weight-400);
+    }
 }
 
 input,

+ 1 - 0
packages/admin-ui/src/lib/static/styles/theme/default.scss

@@ -163,6 +163,7 @@
     --page-header-color: hsl(0 0% 98%);
 
     // Border radius
+    --clr-global-borderradius: 4px;
     --border-radius-sm: div(var(--clr-global-borderradius), 2);
     --border-radius: var(--clr-global-borderradius);
     --border-radius-lg: calc(var(--space-unit) * 3);