Jelajahi Sumber

feat(core): Zones query now returns PaginatedList

BREAKING CHANGE: The `zones` query now returns a PaginatedList rather than a simple array of
`Zone` objects. Likewise, the `ZoneService.findAll()` method also returns a paginated list.
The old behaviour of `ZoneService.findAll()` (all Zones, cached for rapid access) can now be
found under the new `ZoneService.getAllWithMembers()` method.
Michael Bromley 2 tahun lalu
induk
melakukan
afbb408aeb

+ 99 - 118
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html

@@ -1,125 +1,106 @@
-<vdr-action-bar>
-    <vdr-ab-left>
-        <input
-            type="text"
-            name="emailSearchTerm"
-            [formControl]="searchTerm"
-            [placeholder]="'customer.search-by-group-name' | translate"
-            class="search-input"
-        />
-    </vdr-ab-left>
-    <vdr-ab-right>
+<vdr-page-header>
+    <vdr-page-title>
         <vdr-action-bar-items locationId="customer-group-list"></vdr-action-bar-items>
         <button class="btn btn-primary" *vdrIfPermissions="'CreateCustomerGroup'" (click)="create()">
             <clr-icon shape="plus"></clr-icon>
             {{ 'customer.create-new-customer-group' | translate }}
         </button>
-    </vdr-ab-right>
-</vdr-action-bar>
-<div class="group-wrapper">
-    <div class="group-list">
-        <vdr-data-table
-            [class.expanded]="activeGroup$ | async"
-            [items]="items$ | async"
-            [itemsPerPage]="itemsPerPage$ | async"
-            [totalItems]="totalItems$ | async"
-            [currentPage]="currentPage$ | async"
-            (pageChange)="setPageNumber($event)"
-            (itemsPerPageChange)="setItemsPerPage($event)"
-        >
-            <ng-template let-group="item">
-                <td class="left align-middle" [class.active]="group.id === activeGroupId">
-                    <vdr-entity-info [entity]="group"></vdr-entity-info>
-                </td>
-                <td class="left align-middle" [class.active]="group.id === activeGroupId">
-                    <vdr-chip [colorFrom]="group.id">{{ group.name }}</vdr-chip>
-                </td>
-                <td class="left align-middle" [class.active]="group.id === activeGroupId">
-                    <a
-                        class="btn btn-link btn-sm"
-                        [routerLink]="['./', { contents: group.id }]"
-                        queryParamsHandling="preserve"
-                    >
-                        <clr-icon shape="view-list"></clr-icon>
-                        {{ 'customer.view-group-members' | translate }}
-                    </a>
-                </td>
-                <td class="right align-middle" [class.active]="group.id === activeGroupId">
-                    <button class="btn btn-link btn-sm" (click)="update(group)">
-                        <clr-icon shape="edit"></clr-icon>
-                        {{ 'common.edit' | translate }}
-                    </button>
-                </td>
-                <td [class.active]="group.id === activeGroupId" class="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
-                                class="button"
-                                vdrDropdownItem
-                                (click)="delete(group.id)"
-                                [disabled]="!('DeleteCustomerGroup' | hasPermission)"
-                            >
-                                <clr-icon shape="trash" class="is-danger"></clr-icon>
-                                {{ 'common.delete' | translate }}
-                            </button>
-                        </vdr-dropdown-menu>
-                    </vdr-dropdown>
-                </td>
-            </ng-template>
-        </vdr-data-table>
-    </div>
-    <ng-template #emptyPlaceholder>
-        <vdr-empty-placeholder></vdr-empty-placeholder>
-    </ng-template>
-    <div class="group-members" [class.expanded]="activeGroup$ | async">
-        <ng-container *ngIf="activeGroup$ | async as activeGroup">
-            <div class="flex">
-                <div class="header-title-row">{{ activeGroup.name }} ({{ membersTotal$ | async }})</div>
-                <div class="flex-spacer"></div>
-                <button type="button" class="close-button" (click)="closeMembers()">
-                    <clr-icon shape="close"></clr-icon>
-                </button>
-            </div>
-            <div class="controls">
-                <vdr-dropdown>
-                    <button
-                        type="button"
-                        class="btn btn-secondary btn-sm"
-                        vdrDropdownTrigger
-                        [disabled]="selectedCustomerIds.length === 0"
-                    >
-                        {{ 'common.with-selected' | translate: { count: selectedCustomerIds.length } }}
-                        <clr-icon shape="caret down"></clr-icon>
-                    </button>
-                    <vdr-dropdown-menu vdrPosition="bottom-right">
-                        <button
-                            type="button"
-                            class="delete-button"
-                            (click)="removeFromGroup(activeGroup, selectedCustomerIds)"
-                            vdrDropdownItem
-                            [disabled]="!('UpdateCustomerGroup' | hasPermission)"
+    </vdr-page-title>
+</vdr-page-header>
+<vdr-page-body>
+    <vdr-split-view [rightPanelOpen]="activeGroup$ | async" (closeClicked)="closeMembers()">
+        <ng-template vdrSplitViewLeft>
+            <vdr-data-table-2
+                class="mt-2"
+                id="customer-group-list"
+                [items]="items$ | async"
+                [itemsPerPage]="itemsPerPage$ | async"
+                [totalItems]="totalItems$ | async"
+                [currentPage]="currentPage$ | async"
+                [filters]="filters"
+                [activeIndex]="activeIndex$ | async"
+                (pageChange)="setPageNumber($event)"
+                (itemsPerPageChange)="setItemsPerPage($event)"
+            >
+                <vdr-bulk-action-menu
+                    locationId="customer-group-list"
+                    [hostComponent]="this"
+                    [selectionManager]="selectionManager"
+                ></vdr-bulk-action-menu>
+                <vdr-dt2-search
+                    [searchTermControl]="searchTermControl"
+                    [searchTermPlaceholder]="'common.search-by-name' | translate"
+                ></vdr-dt2-search>
+                <vdr-dt2-column [heading]="'common.id' | translate" [hiddenByDefault]="true">
+                    <ng-template let-customerGroup="item">
+                        {{ customerGroup.id }}
+                    </ng-template>
+                </vdr-dt2-column>
+                <vdr-dt2-column
+                    [heading]="'common.created-at' | translate"
+                    [hiddenByDefault]="true"
+                    [sort]="sorts.get('createdAt')"
+                >
+                    <ng-template let-customerGroup="item">
+                        {{ customerGroup.createdAt | localeDate : 'short' }}
+                    </ng-template>
+                </vdr-dt2-column>
+                <vdr-dt2-column
+                    [heading]="'common.updated-at' | translate"
+                    [hiddenByDefault]="true"
+                    [sort]="sorts.get('updatedAt')"
+                >
+                    <ng-template let-customerGroup="item">
+                        {{ customerGroup.updatedAt | localeDate : 'short' }}
+                    </ng-template>
+                </vdr-dt2-column>
+                <vdr-dt2-column
+                    [heading]="'common.name' | translate"
+                    [optional]="false"
+                    [sort]="sorts.get('name')"
+                >
+                    <ng-template let-customerGroup="item">
+                        <a class="button-ghost" [routerLink]="['./', customerGroup.id]"
+                            ><span>{{ customerGroup.name }}</span>
+                            <clr-icon shape="arrow right"></clr-icon>
+                        </a>
+                    </ng-template>
+                </vdr-dt2-column>
+                <vdr-dt2-column
+                    [heading]="'common.view-contents' | translate"
+                    [optional]="false"
+                    [sort]="sorts.get('name')"
+                >
+                    <ng-template let-customerGroup="item">
+                        <a
+                            class="button-small bg-weight-150"
+                            [routerLink]="['./', { contents: customerGroup.id }]"
+                            queryParamsHandling="preserve"
                         >
-                            <clr-icon shape="trash" class="is-danger"></clr-icon>
-                            {{ 'customer.remove-from-group' | translate }}
-                        </button>
-                    </vdr-dropdown-menu>
-                </vdr-dropdown>
-                <button class="btn btn-secondary btn-sm" (click)="addToGroup(activeGroup)">
-                    {{ 'customer.add-customers-to-group' | translate: { groupName: activeGroup.name } }}
+                            <span>{{ 'customer.view-group-members' | translate }}</span>
+                            <clr-icon shape="file-group"></clr-icon>
+                        </a>
+                    </ng-template>
+                </vdr-dt2-column>
+            </vdr-data-table-2>
+        </ng-template>
+        <ng-template vdrSplitViewRight [splitViewTitle]="(activeGroup$ | async)?.name">
+            <ng-container *ngIf="activeGroup$ | async as activeGroup">
+                <button class="button-ghost ml-4" (click)="addToGroup(activeGroup)">
+                    <clr-icon shape="plus"></clr-icon>
+                    <span>{{
+                        'customer.add-customers-to-group' | translate : { groupName: activeGroup.name }
+                    }}</span>
                 </button>
-            </div>
-            <vdr-customer-group-member-list
-                [members]="members$ | async"
-                [route]="route"
-                [totalItems]="membersTotal$ | async"
-                [selectedMemberIds]="selectedCustomerIds"
-                (selectionChange)="selectedCustomerIds = $event"
-                (fetchParamsChange)="fetchGroupMembers$.next($event)"
-            ></vdr-customer-group-member-list>
-        </ng-container>
-    </div>
-</div>
+                <vdr-customer-group-member-list
+                    locationId="customer-group-members-list"
+                    [members]="members$ | async"
+                    [route]="route"
+                    [totalItems]="membersTotal$ | async"
+                    [activeGroup]="activeGroup$ | async"
+                    (fetchParamsChange)="fetchGroupMembers$.next($event)"
+                />
+            </ng-container>
+        </ng-template>
+    </vdr-split-view>
+</vdr-page-body>

+ 32 - 88
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.ts

@@ -1,30 +1,20 @@
 import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
-import { UntypedFormControl } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import {
     BaseListComponent,
+    CustomerGroupFilterParameter,
+    CustomerGroupSortParameter,
     DataService,
-    DeletionResult,
+    DataTableService,
     GetCustomerGroupsQuery,
     GetCustomerGroupWithCustomersQuery,
     ItemOf,
-    LogicalOperator,
     ModalService,
     NotificationService,
-    SortOrder,
 } from '@vendure/admin-ui/core';
 import { BehaviorSubject, combineLatest, EMPTY, Observable, of } from 'rxjs';
-import {
-    debounceTime,
-    distinctUntilChanged,
-    filter,
-    map,
-    mapTo,
-    switchMap,
-    takeUntil,
-    tap,
-} from 'rxjs/operators';
+import { distinctUntilChanged, map, mapTo, switchMap, tap } from 'rxjs/operators';
 
 import { AddCustomerToGroupDialogComponent } from '../add-customer-to-group-dialog/add-customer-to-group-dialog.component';
 import { CustomerGroupDetailDialogComponent } from '../customer-group-detail-dialog/customer-group-detail-dialog.component';
@@ -43,20 +33,30 @@ export class CustomerGroupListComponent
     >
     implements OnInit
 {
-    searchTerm = new UntypedFormControl('');
     activeGroup$: Observable<ItemOf<GetCustomerGroupsQuery, 'customerGroups'> | undefined>;
-    activeGroupId: string | undefined;
+    activeIndex$: Observable<number>;
     listIsEmpty$: Observable<boolean>;
     members$: Observable<
         NonNullable<GetCustomerGroupWithCustomersQuery['customerGroup']>['customers']['items']
     >;
     membersTotal$: Observable<number>;
-    selectedCustomerIds: string[] = [];
     fetchGroupMembers$ = new BehaviorSubject<CustomerGroupMemberFetchParams>({
         skip: 0,
         take: 0,
         filterTerm: '',
     });
+    readonly filters = this.dataTableService
+        .createFilterCollection<CustomerGroupFilterParameter>()
+        .addDateFilters()
+        .connectToRoute(this.route);
+
+    readonly sorts = this.dataTableService
+        .createSortCollection<CustomerGroupSortParameter>()
+        .defaultSort('createdAt', 'DESC')
+        .addSort({ name: 'createdAt' })
+        .addSort({ name: 'updatedAt' })
+        .addSort({ name: 'name' })
+        .connectToRoute(this.route);
     private refreshActiveGroupMembers$ = new BehaviorSubject<void>(undefined);
 
     constructor(
@@ -65,6 +65,7 @@ export class CustomerGroupListComponent
         private modalService: ModalService,
         public route: ActivatedRoute,
         protected router: Router,
+        private dataTableService: DataTableService,
     ) {
         super(router, route);
         super.setQueryFn(
@@ -76,8 +77,10 @@ export class CustomerGroupListComponent
                     skip,
                     take,
                     filter: {
-                        name: { contains: this.searchTerm.value },
+                        name: { contains: this.searchTermControl.value },
+                        ...this.filters.createFilterInput(),
                     },
+                    sort: this.sorts.createSortInput(),
                 },
             }),
         );
@@ -85,17 +88,9 @@ export class CustomerGroupListComponent
 
     ngOnInit(): void {
         super.ngOnInit();
-        this.searchTerm.valueChanges
-            .pipe(
-                filter(value => 2 < value.length || value.length === 0),
-                debounceTime(250),
-                takeUntil(this.destroy$),
-            )
-            .subscribe(() => this.refresh());
         const activeGroupId$ = this.route.paramMap.pipe(
             map(pm => pm.get('contents')),
             distinctUntilChanged(),
-            tap(() => (this.selectedCustomerIds = [])),
         );
         this.listIsEmpty$ = this.items$.pipe(map(groups => groups.length === 0));
         this.activeGroup$ = combineLatest(this.items$, activeGroupId$).pipe(
@@ -104,7 +99,15 @@ export class CustomerGroupListComponent
                     return groups.find(g => g.id === activeGroupId);
                 }
             }),
-            tap(val => (this.activeGroupId = val?.id)),
+        );
+        this.activeIndex$ = combineLatest(this.items$, activeGroupId$).pipe(
+            map(([groups, activeGroupId]) => {
+                if (activeGroupId) {
+                    return groups.findIndex(g => g.id === activeGroupId);
+                } else {
+                    return -1;
+                }
+            }),
         );
         const membersResult$ = combineLatest(
             this.activeGroup$,
@@ -132,6 +135,8 @@ export class CustomerGroupListComponent
 
         this.members$ = membersResult$.pipe(map(res => res?.items ?? []));
         this.membersTotal$ = membersResult$.pipe(map(res => res?.totalItems ?? 0));
+
+        super.refreshListOnChanges(this.filters.valueChanges, this.sorts.valueChanges);
     }
 
     create() {
@@ -159,50 +164,6 @@ export class CustomerGroupListComponent
             );
     }
 
-    delete(groupId: string) {
-        this.modalService
-            .dialog({
-                title: _('customer.confirm-delete-customer-group'),
-                buttons: [
-                    { type: 'secondary', label: _('common.cancel') },
-                    { type: 'danger', label: _('common.delete'), returnValue: true },
-                ],
-            })
-            .pipe(
-                switchMap(response =>
-                    response ? this.dataService.customer.deleteCustomerGroup(groupId) : EMPTY,
-                ),
-
-                switchMap(result => {
-                    if (result.deleteCustomerGroup.result === DeletionResult.DELETED) {
-                        // refresh list
-                        return this.dataService.customer
-                            .getCustomerGroupList()
-                            .mapSingle(() => ({ errorMessage: false }));
-                    } else {
-                        return of({ errorMessage: result.deleteCustomerGroup.message });
-                    }
-                }),
-            )
-            .subscribe(
-                result => {
-                    if (typeof result.errorMessage === 'string') {
-                        this.notificationService.error(result.errorMessage);
-                    } else {
-                        this.refresh();
-                        this.notificationService.success(_('common.notify-delete-success'), {
-                            entity: 'CustomerGroup',
-                        });
-                    }
-                },
-                err => {
-                    this.notificationService.error(_('common.notify-delete-error'), {
-                        entity: 'CustomerGroup',
-                    });
-                },
-            );
-    }
-
     update(group: ItemOf<GetCustomerGroupsQuery, 'customerGroups'>) {
         this.modalService
             .fromComponent(CustomerGroupDetailDialogComponent, { locals: { group } })
@@ -260,24 +221,7 @@ export class CustomerGroupListComponent
                         groupName: group.name,
                     });
                     this.refreshActiveGroupMembers$.next();
-                    this.selectedCustomerIds = [];
                 },
             });
     }
-
-    removeFromGroup(
-        group: NonNullable<GetCustomerGroupWithCustomersQuery['customerGroup']>,
-        customerIds: string[],
-    ) {
-        this.dataService.customer.removeCustomersFromGroup(group.id, customerIds).subscribe({
-            complete: () => {
-                this.notificationService.success(_(`customer.remove-customers-from-group-success`), {
-                    customerCount: customerIds.length,
-                    groupName: group.name,
-                });
-                this.refreshActiveGroupMembers$.next();
-                this.selectedCustomerIds = [];
-            },
-        });
-    }
 }

+ 45 - 30
packages/admin-ui/src/lib/customer/src/components/customer-group-member-list/customer-group-member-list.component.html

@@ -1,37 +1,52 @@
-<input
-    type="text"
-    name="searchTerm"
-    [formControl]="filterTermControl"
-    [placeholder]="'customer.search-customers-by-email' | translate"
-    class="search-input"
-/>
-
-<vdr-data-table
+<vdr-data-table-2
+    [id]="locationId"
     [items]="members"
     [itemsPerPage]="membersItemsPerPage$ | async"
     [totalItems]="totalItems"
     [currentPage]="membersCurrentPage$ | async"
     (pageChange)="setContentsPageNumber($event)"
     (itemsPerPageChange)="setContentsItemsPerPage($event)"
-    [allSelected]="areAllSelected()"
-    [isRowSelectedFn]="('UpdateCustomerGroup' | hasPermission) && isMemberSelected"
-    (rowSelectChange)="toggleSelectMember($event)"
-    (allSelectChange)="toggleSelectAll()"
 >
-    <vdr-dt-column [expand]="true">{{ 'customer.name' | translate }}</vdr-dt-column>
-    <vdr-dt-column [expand]="true">{{ 'customer.email-address' | translate }}</vdr-dt-column>
-    <vdr-dt-column></vdr-dt-column>
-    <ng-template let-customer="item">
-        <td class="left align-middle">
-            {{ customer.title }} {{ customer.firstName }} {{ customer.lastName }}
-        </td>
-        <td class="left align-middle">{{ customer.emailAddress }}</td>
-        <td class="right align-middle">
-            <vdr-table-row-action
-                iconShape="edit"
-                [label]="'common.edit' | translate"
-                [linkTo]="['/customer', 'customers', customer.id]"
-            ></vdr-table-row-action>
-        </td>
-    </ng-template>
-</vdr-data-table>
+    <vdr-bulk-action-menu
+        [locationId]="locationId"
+        [hostComponent]="this"
+        [selectionManager]="selectionManager"
+    ></vdr-bulk-action-menu>
+    <vdr-dt2-search
+        [searchTermControl]="filterTermControl"
+        [searchTermPlaceholder]="'customer.search-customers-by-email' | translate"
+    ></vdr-dt2-search>
+    <vdr-dt2-column [heading]="'common.id' | translate" [hiddenByDefault]="true">
+        <ng-template let-customerGroup="item">
+            {{ customerGroup.id }}
+        </ng-template>
+    </vdr-dt2-column>
+    <vdr-dt2-column [heading]="'common.created-at' | translate" [hiddenByDefault]="true">
+        <ng-template let-customer="item">
+            {{ customer.createdAt | localeDate : 'short' }}
+        </ng-template>
+    </vdr-dt2-column>
+    <vdr-dt2-column [heading]="'common.updated-at' | translate" [hiddenByDefault]="true">
+        <ng-template let-customer="item">
+            {{ customer.createdAt | localeDate : 'short' }}
+        </ng-template>
+    </vdr-dt2-column>
+    <vdr-dt2-column [heading]="'customer.name' | translate" [optional]="false">
+        <ng-template let-customer="item">
+            <a class="button-ghost" [routerLink]="['/customer/customers', customer.id]"
+                ><span> {{ customer.title }} {{ customer.firstName }} {{ customer.lastName }} </span>
+                <clr-icon shape="arrow right"></clr-icon>
+            </a>
+        </ng-template>
+    </vdr-dt2-column>
+    <vdr-dt2-column [heading]="'common.status' | translate" [hiddenByDefault]="true">
+        <ng-template let-customer="item">
+            <vdr-customer-status-label [customer]="customer" />
+        </ng-template>
+    </vdr-dt2-column>
+    <vdr-dt2-column [heading]="'customer.email-address' | translate">
+        <ng-template let-customer="item">
+            {{ customer.emailAddress }}
+        </ng-template>
+    </vdr-dt2-column>
+</vdr-data-table-2>

+ 25 - 31
packages/admin-ui/src/lib/customer/src/components/customer-group-member-list/customer-group-member-list.component.ts

@@ -7,9 +7,16 @@ import {
     OnInit,
     Output,
 } from '@angular/core';
-import { UntypedFormControl } from '@angular/forms';
+import { FormControl } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
-import { Customer, DataService } from '@vendure/admin-ui/core';
+import {
+    BulkActionLocationId,
+    Customer,
+    DataService,
+    GetCustomerGroupsQuery,
+    ItemOf,
+    SelectionManager,
+} from '@vendure/admin-ui/core';
 import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
 import { debounceTime, distinctUntilChanged, map, startWith, takeUntil, tap } from 'rxjs/operators';
 
@@ -19,7 +26,7 @@ export interface CustomerGroupMemberFetchParams {
     filterTerm: string;
 }
 
-type CustomerGroupMember = Pick<
+export type CustomerGroupMember = Pick<
     Customer,
     'id' | 'createdAt' | 'updatedAt' | 'title' | 'firstName' | 'lastName' | 'emailAddress'
 >;
@@ -31,16 +38,23 @@ type CustomerGroupMember = Pick<
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class CustomerGroupMemberListComponent implements OnInit, OnDestroy {
+    @Input() locationId: BulkActionLocationId;
     @Input() members: CustomerGroupMember[];
     @Input() totalItems: number;
     @Input() route: ActivatedRoute;
     @Input() selectedMemberIds: string[] = [];
+    @Input() activeGroup: ItemOf<GetCustomerGroupsQuery, 'customerGroups'>;
     @Output() selectionChange = new EventEmitter<string[]>();
     @Output() fetchParamsChange = new EventEmitter<CustomerGroupMemberFetchParams>();
 
     membersItemsPerPage$: Observable<number>;
     membersCurrentPage$: Observable<number>;
-    filterTermControl = new UntypedFormControl('');
+    filterTermControl = new FormControl('');
+    selectionManager = new SelectionManager<CustomerGroupMember>({
+        multiSelect: true,
+        itemsAreEqual: (a, b) => a.id === b.id,
+        additiveMode: true,
+    });
     private refresh$ = new BehaviorSubject<boolean>(true);
     private destroy$ = new Subject<void>();
 
@@ -73,11 +87,17 @@ export class CustomerGroupMemberListComponent implements OnInit, OnDestroy {
                 const take = itemsPerPage;
                 const skip = (currentPage - 1) * itemsPerPage;
                 this.fetchParamsChange.emit({
-                    filterTerm,
+                    filterTerm: filterTerm ?? '',
                     skip,
                     take,
                 });
             });
+        this.selectionManager.setCurrentItems(
+            this.members?.filter(m => this.selectedMemberIds.includes(m.id)) ?? [],
+        );
+        this.selectionManager.selectionChanges$.pipe(takeUntil(this.destroy$)).subscribe(selection => {
+            this.selectionChange.emit(selection.map(s => s.id));
+        });
     }
 
     ngOnDestroy() {
@@ -103,30 +123,4 @@ export class CustomerGroupMemberListComponent implements OnInit, OnDestroy {
             queryParamsHandling: 'merge',
         });
     }
-
-    areAllSelected(): boolean {
-        if (this.members) {
-            return this.selectedMemberIds.length === this.members.length;
-        } else {
-            return false;
-        }
-    }
-
-    toggleSelectAll() {
-        if (this.areAllSelected()) {
-            this.selectionChange.emit([]);
-        } else {
-            this.selectionChange.emit(this.members.map(v => v.id));
-        }
-    }
-
-    toggleSelectMember({ item: member }: { item: { id: string } }) {
-        if (this.selectedMemberIds.includes(member.id)) {
-            this.selectionChange.emit(this.selectedMemberIds.filter(id => id !== member.id));
-        } else {
-            this.selectionChange.emit([...this.selectedMemberIds, member.id]);
-        }
-    }
-
-    isMemberSelected = (member: { id: string }): boolean => -1 < this.selectedMemberIds.indexOf(member.id);
 }

+ 37 - 1
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -4568,7 +4568,7 @@ export type Query = {
     testEligibleShippingMethods: Array<ShippingMethodQuote>;
     testShippingMethod: TestShippingMethodResult;
     zone?: Maybe<Zone>;
-    zones: Array<Zone>;
+    zones: ZoneList;
 };
 
 export type QueryAdministratorArgs = {
@@ -4795,6 +4795,10 @@ export type QueryZoneArgs = {
     id: Scalars['ID'];
 };
 
+export type QueryZonesArgs = {
+    options?: InputMaybe<ZoneListOptions>;
+};
+
 export type Refund = Node & {
     adjustment: Scalars['Money'];
     createdAt: Scalars['DateTime'];
@@ -5912,6 +5916,38 @@ export type Zone = Node & {
     updatedAt: Scalars['DateTime'];
 };
 
+export type ZoneFilterParameter = {
+    createdAt?: InputMaybe<DateOperators>;
+    id?: InputMaybe<IdOperators>;
+    name?: InputMaybe<StringOperators>;
+    updatedAt?: InputMaybe<DateOperators>;
+};
+
+export type ZoneList = PaginatedList & {
+    items: Array<Zone>;
+    totalItems: Scalars['Int'];
+};
+
+export type ZoneListOptions = {
+    /** Allows the results to be filtered */
+    filter?: InputMaybe<ZoneFilterParameter>;
+    /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+    filterOperator?: InputMaybe<LogicalOperator>;
+    /** Skips the first n results, for use in pagination */
+    skip?: InputMaybe<Scalars['Int']>;
+    /** Specifies which properties to sort the results by */
+    sort?: InputMaybe<ZoneSortParameter>;
+    /** Takes n results, for use in pagination */
+    take?: InputMaybe<Scalars['Int']>;
+};
+
+export type ZoneSortParameter = {
+    createdAt?: InputMaybe<SortOrder>;
+    id?: InputMaybe<SortOrder>;
+    name?: InputMaybe<SortOrder>;
+    updatedAt?: InputMaybe<SortOrder>;
+};
+
 export type CreateAssetsMutationVariables = Exact<{
     input: Array<CreateAssetInput> | CreateAssetInput;
 }>;

+ 39 - 1
packages/common/src/generated-types.ts

@@ -4810,7 +4810,7 @@ export type Query = {
   testEligibleShippingMethods: Array<ShippingMethodQuote>;
   testShippingMethod: TestShippingMethodResult;
   zone?: Maybe<Zone>;
-  zones: Array<Zone>;
+  zones: ZoneList;
 };
 
 
@@ -5092,6 +5092,11 @@ export type QueryZoneArgs = {
   id: Scalars['ID'];
 };
 
+
+export type QueryZonesArgs = {
+  options?: InputMaybe<ZoneListOptions>;
+};
+
 export type Refund = Node & {
   __typename?: 'Refund';
   adjustment: Scalars['Money'];
@@ -6229,3 +6234,36 @@ export type Zone = Node & {
   name: Scalars['String'];
   updatedAt: Scalars['DateTime'];
 };
+
+export type ZoneFilterParameter = {
+  createdAt?: InputMaybe<DateOperators>;
+  id?: InputMaybe<IdOperators>;
+  name?: InputMaybe<StringOperators>;
+  updatedAt?: InputMaybe<DateOperators>;
+};
+
+export type ZoneList = PaginatedList & {
+  __typename?: 'ZoneList';
+  items: Array<Zone>;
+  totalItems: Scalars['Int'];
+};
+
+export type ZoneListOptions = {
+  /** Allows the results to be filtered */
+  filter?: InputMaybe<ZoneFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: InputMaybe<LogicalOperator>;
+  /** Skips the first n results, for use in pagination */
+  skip?: InputMaybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: InputMaybe<ZoneSortParameter>;
+  /** Takes n results, for use in pagination */
+  take?: InputMaybe<Scalars['Int']>;
+};
+
+export type ZoneSortParameter = {
+  createdAt?: InputMaybe<SortOrder>;
+  id?: InputMaybe<SortOrder>;
+  name?: InputMaybe<SortOrder>;
+  updatedAt?: InputMaybe<SortOrder>;
+};

+ 41 - 3
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -4568,7 +4568,7 @@ export type Query = {
     testEligibleShippingMethods: Array<ShippingMethodQuote>;
     testShippingMethod: TestShippingMethodResult;
     zone?: Maybe<Zone>;
-    zones: Array<Zone>;
+    zones: ZoneList;
 };
 
 export type QueryAdministratorArgs = {
@@ -4795,6 +4795,10 @@ export type QueryZoneArgs = {
     id: Scalars['ID'];
 };
 
+export type QueryZonesArgs = {
+    options?: InputMaybe<ZoneListOptions>;
+};
+
 export type Refund = Node & {
     adjustment: Scalars['Money'];
     createdAt: Scalars['DateTime'];
@@ -5912,6 +5916,38 @@ export type Zone = Node & {
     updatedAt: Scalars['DateTime'];
 };
 
+export type ZoneFilterParameter = {
+    createdAt?: InputMaybe<DateOperators>;
+    id?: InputMaybe<IdOperators>;
+    name?: InputMaybe<StringOperators>;
+    updatedAt?: InputMaybe<DateOperators>;
+};
+
+export type ZoneList = PaginatedList & {
+    items: Array<Zone>;
+    totalItems: Scalars['Int'];
+};
+
+export type ZoneListOptions = {
+    /** Allows the results to be filtered */
+    filter?: InputMaybe<ZoneFilterParameter>;
+    /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+    filterOperator?: InputMaybe<LogicalOperator>;
+    /** Skips the first n results, for use in pagination */
+    skip?: InputMaybe<Scalars['Int']>;
+    /** Specifies which properties to sort the results by */
+    sort?: InputMaybe<ZoneSortParameter>;
+    /** Takes n results, for use in pagination */
+    take?: InputMaybe<Scalars['Int']>;
+};
+
+export type ZoneSortParameter = {
+    createdAt?: InputMaybe<SortOrder>;
+    id?: InputMaybe<SortOrder>;
+    name?: InputMaybe<SortOrder>;
+    updatedAt?: InputMaybe<SortOrder>;
+};
+
 export type GetAdministratorsQueryVariables = Exact<{
     options?: InputMaybe<AdministratorListOptions>;
 }>;
@@ -11423,9 +11459,11 @@ export type DeleteZoneMutationVariables = Exact<{
 
 export type DeleteZoneMutation = { deleteZone: { result: DeletionResult; message?: string | null } };
 
-export type GetZonesQueryVariables = Exact<{ [key: string]: never }>;
+export type GetZonesQueryVariables = Exact<{
+    options?: InputMaybe<ZoneListOptions>;
+}>;
 
-export type GetZonesQuery = { zones: Array<{ id: string; name: string }> };
+export type GetZonesQuery = { zones: { totalItems: number; items: Array<{ id: string; name: string }> } };
 
 export type GetZoneQueryVariables = Exact<{
     id: Scalars['ID'];

+ 7 - 4
packages/core/e2e/zone.e2e-spec.ts

@@ -227,10 +227,13 @@ const DELETE_ZONE = gql`
 `;
 
 const GET_ZONE_LIST = gql`
-    query GetZones {
-        zones {
-            id
-            name
+    query GetZones($options: ZoneListOptions) {
+        zones(options: $options) {
+            items {
+                id
+                name
+            }
+            totalItems
         }
     }
 `;

+ 4 - 2
packages/core/src/api/resolvers/admin/zone.resolver.ts

@@ -9,7 +9,9 @@ import {
     MutationUpdateZoneArgs,
     Permission,
     QueryZoneArgs,
+    QueryZonesArgs,
 } from '@vendure/common/lib/generated-types';
+import { PaginatedList } from '@vendure/common/lib/shared-types';
 
 import { Zone } from '../../../entity/zone/zone.entity';
 import { ZoneService } from '../../../service/services/zone.service';
@@ -24,8 +26,8 @@ export class ZoneResolver {
 
     @Query()
     @Allow(Permission.ReadSettings, Permission.ReadZone)
-    zones(@Ctx() ctx: RequestContext): Promise<Zone[]> {
-        return this.zoneService.findAll(ctx);
+    zones(@Ctx() ctx: RequestContext, @Args() args: QueryZonesArgs): Promise<PaginatedList<Zone>> {
+        return this.zoneService.findAll(ctx, args.options || undefined);
     }
 
     @Query()

+ 9 - 1
packages/core/src/api/schema/admin-api/zone.api.graphql

@@ -1,8 +1,16 @@
 type Query {
-    zones: [Zone!]!
+    zones(options: ZoneListOptions): ZoneList!
     zone(id: ID!): Zone
 }
 
+type ZoneList implements PaginatedList {
+    items: [Zone!]!
+    totalItems: Int!
+}
+
+input ZoneListOptions
+
+
 type Mutation {
     "Create a new Zone"
     createZone(input: CreateZoneInput!): Zone!

+ 1 - 1
packages/core/src/data-import/providers/populator/populator.ts

@@ -232,7 +232,7 @@ export class Populator {
 
     private async populateCountries(ctx: RequestContext, countries: CountryDefinition[]): Promise<ZoneMap> {
         const zoneMap: ZoneMap = new Map();
-        const existingZones = await this.zoneService.findAll(ctx);
+        const existingZones = await this.zoneService.getAllWithMembers(ctx);
         for (const zone of existingZones) {
             zoneMap.set(zone.name, { entity: zone, members: zone.members.map(m => m.id) });
         }

+ 1 - 1
packages/core/src/service/helpers/order-calculator/order-calculator.ts

@@ -57,7 +57,7 @@ export class OrderCalculator {
         // We reset the promotions array as all promotions
         // must be revalidated on any changes to an Order.
         order.promotions = [];
-        const zones = await this.zoneService.findAll(ctx);
+        const zones = await this.zoneService.getAllWithMembers(ctx);
         const activeTaxZone = await this.requestContextCache.get(ctx, 'activeTaxZone', () =>
             taxZoneStrategy.determineTaxZone(ctx, zones, ctx.channel, order),
         );

+ 3 - 1
packages/core/src/service/helpers/product-price-applicator/product-price-applicator.ts

@@ -70,7 +70,9 @@ export class ProductPriceApplicator {
             });
         }
         const { taxZoneStrategy } = this.configService.taxOptions;
-        const zones = await this.requestCache.get(ctx, 'allZones', () => this.zoneService.findAll(ctx));
+        const zones = await this.requestCache.get(ctx, 'allZones', () =>
+            this.zoneService.getAllWithMembers(ctx),
+        );
         const activeTaxZone = await this.requestCache.get(ctx, 'activeTaxZone', () =>
             taxZoneStrategy.determineTaxZone(ctx, zones, ctx.channel, order),
         );

+ 28 - 8
packages/core/src/service/services/zone.service.ts

@@ -7,11 +7,12 @@ import {
     MutationRemoveMembersFromZoneArgs,
     UpdateZoneInput,
 } from '@vendure/common/lib/generated-types';
-import { ID } from '@vendure/common/lib/shared-types';
+import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
 import { In } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
+import { ListQueryOptions } from '../../common/index';
 import { createSelfRefreshingCache, SelfRefreshingCache } from '../../common/self-refreshing-cache';
 import { assertFound } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
@@ -22,6 +23,7 @@ import { Zone } from '../../entity/zone/zone.entity';
 import { EventBus } from '../../event-bus';
 import { ZoneEvent } from '../../event-bus/events/zone-event';
 import { ZoneMembersEvent } from '../../event-bus/events/zone-members-event';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { TranslatorService } from '../helpers/translator/translator.service';
 import { patchEntity } from '../helpers/utils/patch-entity';
 
@@ -42,6 +44,7 @@ export class ZoneService {
         private configService: ConfigService,
         private eventBus: EventBus,
         private translator: TranslatorService,
+        private listQueryBuilder: ListQueryBuilder,
     ) {}
 
     /** @internal */
@@ -68,14 +71,21 @@ export class ZoneService {
         });
     }
 
-    async findAll(ctx: RequestContext): Promise<Zone[]> {
-        return this.zones.memoize([], [ctx], zones => {
-            return zones.map((zone, i) => {
-                const cloneZone = { ...zone };
-                cloneZone.members = zone.members.map(country => this.translator.translate(country, ctx));
-                return cloneZone;
+    async findAll(ctx: RequestContext, options?: ListQueryOptions<Zone>): Promise<PaginatedList<Zone>> {
+        return this.listQueryBuilder
+            .build(Zone, options, { relations: ['members'] })
+            .getManyAndCount()
+            .then(([items, totalItems]) => {
+                const translated = items.map((zone, i) => {
+                    const cloneZone = { ...zone };
+                    cloneZone.members = zone.members.map(country => this.translator.translate(country, ctx));
+                    return cloneZone;
+                });
+                return {
+                    items: translated,
+                    totalItems,
+                };
             });
-        });
     }
 
     findOne(ctx: RequestContext, zoneId: ID): Promise<Zone | undefined> {
@@ -93,6 +103,16 @@ export class ZoneService {
             });
     }
 
+    async getAllWithMembers(ctx: RequestContext): Promise<Zone[]> {
+        return this.zones.memoize([], [ctx], zones => {
+            return zones.map((zone, i) => {
+                const cloneZone = { ...zone };
+                cloneZone.members = zone.members.map(country => this.translator.translate(country, ctx));
+                return cloneZone;
+            });
+        });
+    }
+
     async create(ctx: RequestContext, input: CreateZoneInput): Promise<Zone> {
         const zone = new Zone(input);
         if (input.memberIds) {

+ 37 - 1
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -4568,7 +4568,7 @@ export type Query = {
     testEligibleShippingMethods: Array<ShippingMethodQuote>;
     testShippingMethod: TestShippingMethodResult;
     zone?: Maybe<Zone>;
-    zones: Array<Zone>;
+    zones: ZoneList;
 };
 
 export type QueryAdministratorArgs = {
@@ -4795,6 +4795,10 @@ export type QueryZoneArgs = {
     id: Scalars['ID'];
 };
 
+export type QueryZonesArgs = {
+    options?: InputMaybe<ZoneListOptions>;
+};
+
 export type Refund = Node & {
     adjustment: Scalars['Money'];
     createdAt: Scalars['DateTime'];
@@ -5912,6 +5916,38 @@ export type Zone = Node & {
     updatedAt: Scalars['DateTime'];
 };
 
+export type ZoneFilterParameter = {
+    createdAt?: InputMaybe<DateOperators>;
+    id?: InputMaybe<IdOperators>;
+    name?: InputMaybe<StringOperators>;
+    updatedAt?: InputMaybe<DateOperators>;
+};
+
+export type ZoneList = PaginatedList & {
+    items: Array<Zone>;
+    totalItems: Scalars['Int'];
+};
+
+export type ZoneListOptions = {
+    /** Allows the results to be filtered */
+    filter?: InputMaybe<ZoneFilterParameter>;
+    /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+    filterOperator?: InputMaybe<LogicalOperator>;
+    /** Skips the first n results, for use in pagination */
+    skip?: InputMaybe<Scalars['Int']>;
+    /** Specifies which properties to sort the results by */
+    sort?: InputMaybe<ZoneSortParameter>;
+    /** Takes n results, for use in pagination */
+    take?: InputMaybe<Scalars['Int']>;
+};
+
+export type ZoneSortParameter = {
+    createdAt?: InputMaybe<SortOrder>;
+    id?: InputMaybe<SortOrder>;
+    name?: InputMaybe<SortOrder>;
+    updatedAt?: InputMaybe<SortOrder>;
+};
+
 export type ReindexMutationVariables = Exact<{ [key: string]: never }>;
 
 export type ReindexMutation = {

+ 37 - 1
packages/payments-plugin/e2e/graphql/generated-admin-types.ts

@@ -4568,7 +4568,7 @@ export type Query = {
     testEligibleShippingMethods: Array<ShippingMethodQuote>;
     testShippingMethod: TestShippingMethodResult;
     zone?: Maybe<Zone>;
-    zones: Array<Zone>;
+    zones: ZoneList;
 };
 
 export type QueryAdministratorArgs = {
@@ -4795,6 +4795,10 @@ export type QueryZoneArgs = {
     id: Scalars['ID'];
 };
 
+export type QueryZonesArgs = {
+    options?: InputMaybe<ZoneListOptions>;
+};
+
 export type Refund = Node & {
     adjustment: Scalars['Money'];
     createdAt: Scalars['DateTime'];
@@ -5912,6 +5916,38 @@ export type Zone = Node & {
     updatedAt: Scalars['DateTime'];
 };
 
+export type ZoneFilterParameter = {
+    createdAt?: InputMaybe<DateOperators>;
+    id?: InputMaybe<IdOperators>;
+    name?: InputMaybe<StringOperators>;
+    updatedAt?: InputMaybe<DateOperators>;
+};
+
+export type ZoneList = PaginatedList & {
+    items: Array<Zone>;
+    totalItems: Scalars['Int'];
+};
+
+export type ZoneListOptions = {
+    /** Allows the results to be filtered */
+    filter?: InputMaybe<ZoneFilterParameter>;
+    /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+    filterOperator?: InputMaybe<LogicalOperator>;
+    /** Skips the first n results, for use in pagination */
+    skip?: InputMaybe<Scalars['Int']>;
+    /** Specifies which properties to sort the results by */
+    sort?: InputMaybe<ZoneSortParameter>;
+    /** Takes n results, for use in pagination */
+    take?: InputMaybe<Scalars['Int']>;
+};
+
+export type ZoneSortParameter = {
+    createdAt?: InputMaybe<SortOrder>;
+    id?: InputMaybe<SortOrder>;
+    name?: InputMaybe<SortOrder>;
+    updatedAt?: InputMaybe<SortOrder>;
+};
+
 export type PaymentMethodFragment = {
     id: string;
     code: string;

File diff ditekan karena terlalu besar
+ 0 - 0
schema-admin.json


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini