Jelajahi Sumber

feat(admin-ui): Allow groups admin from CustomerDetailComponent

Relates to #330
Michael Bromley 5 tahun lalu
induk
melakukan
8dca9a3f3e

+ 5 - 1
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -4272,7 +4272,10 @@ export type GetCustomerQuery = (
   { __typename?: 'Query' }
   & { customer?: Maybe<(
     { __typename?: 'Customer' }
-    & { orders: (
+    & { groups: Array<(
+      { __typename?: 'CustomerGroup' }
+      & Pick<CustomerGroup, 'id' | 'name'>
+    )>, orders: (
       { __typename?: 'OrderList' }
       & Pick<OrderList, 'totalItems'>
       & { items: Array<(
@@ -6787,6 +6790,7 @@ export namespace GetCustomer {
   export type Variables = GetCustomerQueryVariables;
   export type Query = GetCustomerQuery;
   export type Customer = CustomerFragment;
+  export type Groups = (NonNullable<(NonNullable<GetCustomerQuery['customer']>)['groups'][0]>);
   export type Orders = (NonNullable<GetCustomerQuery['customer']>)['orders'];
   export type Items = (NonNullable<(NonNullable<GetCustomerQuery['customer']>)['orders']['items'][0]>);
 }

+ 4 - 0
packages/admin-ui/src/lib/core/src/data/definitions/customer-definitions.ts

@@ -71,6 +71,10 @@ export const GET_CUSTOMER = gql`
     query GetCustomer($id: ID!, $orderListOptions: OrderListOptions) {
         customer(id: $id) {
             ...Customer
+            groups {
+                id
+                name
+            }
             orders(options: $orderListOptions) {
                 items {
                     id

+ 24 - 2
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html

@@ -10,7 +10,7 @@
         <vdr-action-bar-items locationId="customer-detail"></vdr-action-bar-items>
         <button
             class="btn btn-primary"
-            *ngIf="(isNew$ | async); else updateButton"
+            *ngIf="isNew$ | async; else updateButton"
             (click)="create()"
             [disabled]="!(addressDefaultsUpdated || (detailForm.valid && detailForm.dirty))"
         >
@@ -53,7 +53,7 @@
     >
         <input id="emailAddress" type="text" formControlName="emailAddress" />
     </vdr-form-field>
-    <vdr-form-field [label]="'customer.password' | translate" for="password" *ngIf="(isNew$ | async)">
+    <vdr-form-field [label]="'customer.password' | translate" for="password" *ngIf="isNew$ | async">
         <input id="password" type="password" formControlName="password" />
     </vdr-form-field>
 
@@ -70,6 +70,28 @@
     </section>
 </form>
 
+<div class="groups" *ngIf="(entity$ | async)?.groups as groups">
+    <label class="clr-control-label">{{ 'customer.customer-groups' | translate }}</label>
+    <ng-container *ngIf="groups.length; else noGroups">
+        <vdr-chip
+            *ngFor="let group of groups"
+            [colorFrom]="group.id"
+            icon="times"
+            (iconClick)="removeFromGroup(group)"
+            >{{ group.name }}</vdr-chip
+        >
+    </ng-container>
+    <ng-template #noGroups>
+        {{ 'customer.not-a-member-of-any-groups' | translate }}
+    </ng-template>
+    <div>
+        <button class="btn btn-sm btn-secondary" (click)="addToGroup()">
+            <clr-icon shape="plus"></clr-icon>
+            {{ 'customer.add-customer-to-group' | translate }}
+        </button>
+    </div>
+</div>
+
 <div class="clr-row" *ngIf="!(isNew$ | async)">
     <div class="clr-col-md-4">
         <h3>{{ 'customer.addresses' | translate }}</h3>

+ 58 - 6
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts

@@ -2,23 +2,26 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
 import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { BaseDetailComponent } from '@vendure/admin-ui/core';
 import {
+    BaseDetailComponent,
     CreateAddressInput,
     CreateCustomerInput,
     Customer,
     CustomFieldConfig,
+    DataService,
     GetAvailableCountries,
     GetCustomer,
     GetCustomerQuery,
+    ModalService,
+    NotificationService,
+    ServerConfigService,
     UpdateCustomerInput,
 } from '@vendure/admin-ui/core';
-import { NotificationService } from '@vendure/admin-ui/core';
-import { DataService } from '@vendure/admin-ui/core';
-import { ServerConfigService } from '@vendure/admin-ui/core';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
-import { forkJoin, Observable, Subject } from 'rxjs';
-import { filter, map, merge, mergeMap, shareReplay, take } from 'rxjs/operators';
+import { EMPTY, forkJoin, from, Observable, Subject } from 'rxjs';
+import { concatMap, filter, map, merge, mergeMap, shareReplay, switchMap, take } from 'rxjs/operators';
+
+import { SelectCustomerGroupDialogComponent } from '../select-customer-group-dialog/select-customer-group-dialog.component';
 
 type CustomerWithOrders = NonNullable<GetCustomerQuery['customer']>;
 
@@ -49,6 +52,7 @@ export class CustomerDetailComponent extends BaseDetailComponent<CustomerWithOrd
         private changeDetector: ChangeDetectorRef,
         private formBuilder: FormBuilder,
         protected dataService: DataService,
+        private modalService: ModalService,
         private notificationService: NotificationService,
     ) {
         super(route, router, serverConfigService, dataService);
@@ -243,6 +247,53 @@ export class CustomerDetailComponent extends BaseDetailComponent<CustomerWithOrd
             );
     }
 
+    addToGroup() {
+        this.modalService
+            .fromComponent(SelectCustomerGroupDialogComponent, {
+                size: 'md',
+            })
+            .pipe(
+                switchMap((groupIds) => (groupIds ? from(groupIds) : EMPTY)),
+                concatMap((groupId) => this.dataService.customer.addCustomersToGroup(groupId, [this.id])),
+            )
+            .subscribe({
+                next: (res) => {
+                    this.notificationService.success(_(`customer.add-customers-to-group-success`), {
+                        customerCount: 1,
+                        groupName: res.addCustomersToGroup.name,
+                    });
+                },
+                complete: () => {
+                    this.dataService.customer.getCustomer(this.id, { take: 0 }).single$.subscribe();
+                },
+            });
+    }
+
+    removeFromGroup(group: GetCustomer.Groups) {
+        this.modalService
+            .dialog({
+                title: _('customer.confirm-remove-customer-from-group'),
+                buttons: [
+                    { type: 'secondary', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(
+                switchMap((response) =>
+                    response
+                        ? this.dataService.customer.removeCustomersFromGroup(group.id, [this.id])
+                        : EMPTY,
+                ),
+                switchMap(() => this.dataService.customer.getCustomer(this.id, { take: 0 }).single$),
+            )
+            .subscribe((result) => {
+                this.notificationService.success(_(`customer.remove-customers-from-group-success`), {
+                    customerCount: 1,
+                    groupName: group.name,
+                });
+            });
+    }
+
     protected setFormValues(entity: Customer.Fragment): void {
         const customerGroup = this.detailForm.get('customer');
         if (customerGroup) {
@@ -283,6 +334,7 @@ export class CustomerDetailComponent extends BaseDetailComponent<CustomerWithOrd
                 }
             }
         }
+        this.changeDetector.markForCheck();
     }
 
     /**

+ 30 - 0
packages/admin-ui/src/lib/customer/src/components/select-customer-group-dialog/select-customer-group-dialog.component.html

@@ -0,0 +1,30 @@
+<ng-template vdrDialogTitle>
+    {{ 'customer.add-customer-to-group' }}
+</ng-template>
+
+<ng-select
+    [items]="groups$ | async"
+    appendTo="body"
+    [addTag]="false"
+    [multiple]="true"
+    bindValue="id"
+    [(ngModel)]="selectedGroupIds"
+    [clearable]="true"
+    [searchable]="false"
+>
+    <ng-template ng-label-tmp let-item="item" let-clear="clear">
+        <span aria-hidden="true" class="ng-value-icon left" (click)="clear(item)"> × </span>
+        <vdr-chip [colorFrom]="item.id">{{ item.name }}</vdr-chip>
+    </ng-template>
+    <ng-template ng-option-tmp let-item="item">
+        <vdr-chip [colorFrom]="item.id">{{ item.name }}</vdr-chip>
+    </ng-template>
+</ng-select>
+
+
+<ng-template vdrDialogButtons>
+    <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
+    <button type="submit" (click)="add()" [disabled]="!selectedGroupIds.length" class="btn btn-primary">
+        {{ 'customer.add-customer-to-groups-with-count' | translate: {count: selectedGroupIds.length} }}
+    </button>
+</ng-template>

+ 0 - 0
packages/admin-ui/src/lib/customer/src/components/select-customer-group-dialog/select-customer-group-dialog.component.scss


+ 33 - 0
packages/admin-ui/src/lib/customer/src/components/select-customer-group-dialog/select-customer-group-dialog.component.ts

@@ -0,0 +1,33 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { DataService, Dialog, GetCustomerGroups, GetCustomerList } from '@vendure/admin-ui/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
+
+@Component({
+    selector: 'vdr-select-customer-group-dialog',
+    templateUrl: './select-customer-group-dialog.component.html',
+    styleUrls: ['./select-customer-group-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SelectCustomerGroupDialogComponent implements Dialog<string[]>, OnInit {
+    resolveWith: (result?: string[]) => void;
+    groups$: Observable<GetCustomerGroups.Items[]>;
+    selectedGroupIds: string[] = [];
+
+    constructor(private dataService: DataService) {}
+
+    ngOnInit() {
+        this.groups$ = this.dataService.customer
+            .getCustomerGroupList()
+            .mapStream((res) => res.customerGroups.items);
+    }
+
+    cancel() {
+        this.resolveWith();
+    }
+
+    add() {
+        this.resolveWith(this.selectedGroupIds);
+    }
+}

+ 2 - 0
packages/admin-ui/src/lib/customer/src/customer.module.ts

@@ -10,6 +10,7 @@ import { CustomerGroupListComponent } from './components/customer-group-list/cus
 import { CustomerGroupMemberListComponent } from './components/customer-group-member-list/customer-group-member-list.component';
 import { CustomerListComponent } from './components/customer-list/customer-list.component';
 import { CustomerStatusLabelComponent } from './components/customer-status-label/customer-status-label.component';
+import { SelectCustomerGroupDialogComponent } from './components/select-customer-group-dialog/select-customer-group-dialog.component';
 import { customerRoutes } from './customer.routes';
 
 @NgModule({
@@ -23,6 +24,7 @@ import { customerRoutes } from './customer.routes';
         CustomerGroupDetailDialogComponent,
         AddCustomerToGroupDialogComponent,
         CustomerGroupMemberListComponent,
+        SelectCustomerGroupDialogComponent,
     ],
     exports: [AddressCardComponent],
 })

+ 5 - 0
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -196,6 +196,8 @@
     "with-selected": "With selected..."
   },
   "customer": {
+    "add-customer-to-group": "Add customer to group",
+    "add-customer-to-groups-with-count": "Add customer to {count, plural, one {1 group} other {{count} groups}}",
     "add-customers-to-group": "Add customers to group",
     "add-customers-to-group-success": "Added {customerCount, plural, one {1 customer} other {{customerCount} customers}} to \"{ groupName }\"",
     "add-customers-to-group-with-count": "Add {count, plural, one {1 customer} other {{count} customers}}",
@@ -203,11 +205,13 @@
     "addresses": "Addresses",
     "city": "City",
     "confirm-delete-customer-group": "Delete customer group?",
+    "confirm-remove-customer-from-group": "Remove customer from group?",
     "country": "Country",
     "create-customer-group": "Create customer group",
     "create-new-address": "Create new address",
     "create-new-customer": "Create new customer",
     "create-new-customer-group": "Create new customer group",
+    "customer-groups": "Customer groups",
     "customer-type": "Customer type",
     "default-billing-address": "Default billing",
     "default-shipping-address": "Default shipping",
@@ -219,6 +223,7 @@
     "last-name": "Last name",
     "name": "Name",
     "no-orders-placed": "No orders placed",
+    "not-a-member-of-any-groups": "This customer is not a member of any groups",
     "orders": "Orders",
     "password": "Password",
     "phone-number": "Phone number",