Skip to content

Commit

Permalink
feat(admin-ui): Allow customer to be reassigned to order
Browse files Browse the repository at this point in the history
Relates to #2505
  • Loading branch information
michaelbromley committed Jan 19, 2024
1 parent 26e77d7 commit a9a596e
Show file tree
Hide file tree
Showing 30 changed files with 284 additions and 45 deletions.
8 changes: 8 additions & 0 deletions packages/admin-ui/src/lib/core/src/common/generated-types.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="entry-list">
<vdr-timeline-entry iconShape="note" displayType="muted" *vdrIfPermissions="'UpdateCustomer'">
<vdr-timeline-entry iconShape="note" displayType="muted" [featured]="true" *vdrIfPermissions="'UpdateCustomer'">
<div class="note-entry">
<textarea [(ngModel)]="note" name="note" class="note"></textarea>
<button class="btn btn-secondary" [disabled]="!note" (click)="addNoteToCustomer()">
Expand Down Expand Up @@ -133,8 +133,8 @@
</div>
<div class="flex-spacer"></div>
<vdr-dropdown>
<button class="icon-button" vdrDropdownTrigger>
<clr-icon shape="ellipsis-vertical"></clr-icon>
<button class="button-small ml-1" vdrDropdownTrigger>
<clr-icon shape="ellipsis-vertical" size="12"></clr-icon>
</button>
<vdr-dropdown-menu vdrPosition="bottom-right">
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,27 @@
<vdr-page-detail-sidebar>
<vdr-card [title]="'order.state' | translate">
<ng-template vdrCardControls>
<button class="button-small" (click)="openStateDiagram()" [title]="'order.order-state-diagram' | translate">
<button
class="button-small"
(click)="openStateDiagram()"
[title]="'order.order-state-diagram' | translate"
>
<clr-icon shape="list"></clr-icon>
</button>
</ng-template>
<vdr-order-state-label [state]="order.state"></vdr-order-state-label>
</vdr-card>
<vdr-card [title]="'order.customer' | translate">
<ng-template vdrCardControls>
<button
*vdrIfPermissions="['UpdateOrder', 'ReadCustomer']"
class="button-small"
(click)="setOrderCustomer()"
[title]="'order.assign-order-to-another-customer' | translate"
>
<clr-icon shape="switch"></clr-icon>
</button>
</ng-template>
<vdr-customer-label [customer]="order.customer"></vdr-customer-label>
<vdr-labeled-data
class="mt-2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
OrderDetailFragment,
OrderDetailQueryDocument,
Refund,
SetOrderCustomerDocument,
SortOrder,
TimelineHistoryEntry,
TypedBaseDetailComponent,
Expand All @@ -29,6 +30,7 @@ import { CancelOrderDialogComponent } from '../cancel-order-dialog/cancel-order-
import { FulfillOrderDialogComponent } from '../fulfill-order-dialog/fulfill-order-dialog.component';
import { OrderProcessGraphDialogComponent } from '../order-process-graph-dialog/order-process-graph-dialog.component';
import { RefundOrderDialogComponent } from '../refund-order-dialog/refund-order-dialog.component';
import { SelectCustomerDialogComponent } from '../select-customer-dialog/select-customer-dialog.component';
import { SettleRefundDialogComponent } from '../settle-refund-dialog/settle-refund-dialog.component';

type Payment = NonNullable<OrderDetailFragment['payments']>[number];
Expand All @@ -42,6 +44,20 @@ export const ORDER_DETAIL_QUERY = gql`
${ORDER_DETAIL_FRAGMENT}
`;

export const SET_ORDER_CUSTOMER_MUTATION = gql`
mutation SetOrderCustomer($input: SetOrderCustomerInput!) {
setOrderCustomer(input: $input) {
id
customer {
id
firstName
lastName
emailAddress
}
}
}
`;

@Component({
selector: 'vdr-order-detail',
templateUrl: './order-detail.component.html',
Expand Down Expand Up @@ -134,6 +150,41 @@ export class OrderDetailComponent
.subscribe();
}

setOrderCustomer() {
this.modalService
.fromComponent(SelectCustomerDialogComponent, {
locals: {
canCreateNew: false,
includeNoteInput: true,
title: _('order.assign-order-to-another-customer'),
},
})
.pipe(
switchMap(result => {
function isExisting(input: any): input is { id: string } {
return typeof input === 'object' && !!input.id;
}
if (isExisting(result)) {
return this.dataService.mutate(SetOrderCustomerDocument, {
input: {
customerId: result.id,
orderId: this.id,
note: result.note,
},
});
} else {
return EMPTY;
}
}),
switchMap(result => this.refetchOrder(result)),
)
.subscribe(result => {
if (result) {
this.notificationService.success(_('order.set-customer-success'));
}
});
}

transitionToState(state: string) {
this.dataService.order.transitionToState(this.id, state).subscribe(({ transitionOrderToState }) => {
switch (transitionOrderToState?.__typename) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@
{{ entry.data.reason }}
</vdr-labeled-data>
<vdr-labeled-data [label]="'order.contents' | translate">
<vdr-simple-item-list [items]="getCancelledItems(refund.lines)"></vdr-simple-item-list>
<vdr-simple-item-list
[items]="getCancelledItems(refund.lines)"
></vdr-simple-item-list>
</vdr-labeled-data>
</vdr-history-entry-detail>
</div>
Expand Down Expand Up @@ -239,6 +241,48 @@
><span class="cancelled-coupon-code">{{ entry.data.couponCode }}</span></vdr-chip
>
</ng-container>
<ng-container *ngSwitchCase="type.ORDER_CUSTOMER_UPDATED">
<div class="title">
{{
'order.history-customer-updated'
| translate : { newCustomerName: entry.data.newCustomerName }
}}
</div>
<div class="flex">
<div class="note-text">
{{ entry.data.note }}
</div>
<div class="flex-spacer"></div>
<vdr-history-entry-detail>
<vdr-labeled-data [label]="'order.previous-customer' | translate">
<a
*ngIf="entry.data.previousCustomerId"
class="button-ghost"
[routerLink]="[
'/customer',
'customers',
entry.data.previousCustomerId
]"
>
<clr-icon shape="user" class="is-solid"></clr-icon>
<span>{{ entry.data.previousCustomerName }}</span>
<clr-icon shape="arrow right"></clr-icon>
</a>
</vdr-labeled-data>
<vdr-labeled-data [label]="'order.new-customer' | translate">
<a
*ngIf="entry.data.newCustomerId"
class="button-ghost"
[routerLink]="['/customer', 'customers', entry.data.newCustomerId]"
>
<clr-icon shape="user" class="is-solid"></clr-icon>
<span>{{ entry.data.newCustomerName }}</span>
<clr-icon shape="arrow right"></clr-icon>
</a>
</vdr-labeled-data>
</vdr-history-entry-detail>
</div>
</ng-container>
<ng-container *ngSwitchDefault>
<div class="title">
{{ entry.type | translate }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ textarea.note {
.note-text {
color: var(--color-grey-800);
white-space: pre-wrap;
max-width: 580px;
}

.cancelled-coupon-code {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import {
GetOrderHistoryQuery,
HistoryEntryComponentService,
HistoryEntryType,
OrderDetailFragment,
Expand Down Expand Up @@ -84,6 +83,9 @@ export class OrderHistoryComponent {
if (entry.type === HistoryEntryType.ORDER_MODIFIED) {
return 'pencil';
}
if (entry.type === HistoryEntryType.ORDER_CUSTOMER_UPDATED) {
return 'switch';
}
if (entry.type === HistoryEntryType.ORDER_FULFILLMENT_TRANSITION) {
if (entry.data.to === 'Shipped') {
return 'truck';
Expand Down Expand Up @@ -111,6 +113,7 @@ export class OrderHistoryComponent {
return entry.data.to === 'Delivered' || entry.data.to === 'Shipped';
case HistoryEntryType.ORDER_NOTE:
case HistoryEntryType.ORDER_MODIFIED:
case HistoryEntryType.ORDER_CUSTOMER_UPDATED:
return true;
default:
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,25 @@
<ng-template vdrDialogTitle>{{ 'order.set-customer-for-order' | translate }}</ng-template>
<ng-template vdrDialogTitle>{{ title | translate }}</ng-template>

<clr-tabs>
<ng-container *ngIf="!canCreateNew">
<ng-container *ngTemplateOutlet="customerSelect"></ng-container>
<vdr-form-field [label]="'common.add-note' | translate" *ngIf="includeNoteInput" class="mt-4">
<textarea [(ngModel)]="note"></textarea>
</vdr-form-field>
</ng-container>

<clr-tabs *ngIf="canCreateNew" class="pt-1">
<clr-tab>
<button clrTabLink>{{ 'order.existing-customer' | translate }}</button>

<ng-template [(clrIfActive)]="useExisting">
<clr-tab-content>
<div class="mt-4">
<ng-select
[items]="customers$ | async"
appendTo="body"
bindLabel="name"
[addTag]="false"
[multiple]="true"
[hideSelected]="true"
[trackByFn]="trackByFn"
[minTermLength]="2"
[loading]="isLoading"
[typeahead]="input$"
[(ngModel)]="selectedCustomer"
>
<ng-template ng-label-tmp let-item="item" let-clear="clear">
<span class="item-row">
<clr-icon shape="user" class="is-solid"></clr-icon
><span class="mx-1">{{ item.firstName }} {{ item.lastName }}</span>
<vdr-chip>{{ item.emailAddress }}</vdr-chip>
</span>
</ng-template>
<ng-template ng-option-tmp let-item="item">
<span class="item-row">
<clr-icon shape="user" class="is-solid"></clr-icon
><span class="mx-1">{{ item.firstName }} {{ item.lastName }}</span>
<vdr-chip>{{ item.emailAddress }}</vdr-chip>
</span>
</ng-template>
</ng-select>
<ng-container *ngTemplateOutlet="customerSelect"></ng-container>
</div>
</clr-tab-content>
</ng-template>
</clr-tab>
<clr-tab>
<clr-tab *ngIf="canCreateNew">
<button clrTabLink>{{ 'customer.create-new-customer' | translate }}</button>

<ng-template [(clrIfActive)]="createNew">
Expand All @@ -66,6 +46,37 @@
</clr-tab>
</clr-tabs>

<ng-template #customerSelect>
<ng-select
[items]="customers$ | async"
appendTo="body"
bindLabel="name"
[addTag]="false"
[multiple]="true"
[hideSelected]="true"
[trackByFn]="trackByFn"
[minTermLength]="2"
[loading]="isLoading"
[typeahead]="input$"
[(ngModel)]="selectedCustomer"
>
<ng-template ng-label-tmp let-item="item" let-clear="clear">
<span class="item-row">
<clr-icon shape="user" class="is-solid"></clr-icon
><span class="mx-1">{{ item.firstName }} {{ item.lastName }}</span>
<vdr-chip>{{ item.emailAddress }}</vdr-chip>
</span>
</ng-template>
<ng-template ng-option-tmp let-item="item">
<span class="item-row">
<clr-icon shape="user" class="is-solid"></clr-icon
><span class="mx-1">{{ item.firstName }} {{ item.lastName }}</span>
<vdr-chip>{{ item.emailAddress }}</vdr-chip>
</span>
</ng-template>
</ng-select>
</ng-template>

<ng-template vdrDialogButtons>
<button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
display: flex;
align-items: center;
}

clr-tabs {
display: block;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms
import { CreateCustomerInput, DataService, Dialog, GetCustomerListQuery } from '@vendure/admin-ui/core';
import { concat, Observable, of, Subject } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';

export type Customer = GetCustomerListQuery['customers']['items'][number];
export type SelectCustomerDialogResult = (Customer | CreateCustomerInput) & { note: string };

@Component({
selector: 'vdr-select-customer-dialog',
templateUrl: './select-customer-dialog.component.html',
styleUrls: ['./select-customer-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectCustomerDialogComponent implements OnInit, Dialog<Customer | CreateCustomerInput> {
resolveWith: (result?: Customer | CreateCustomerInput) => void;
export class SelectCustomerDialogComponent implements OnInit, Dialog<SelectCustomerDialogResult> {
resolveWith: (result?: SelectCustomerDialogResult) => void;

// populated by the dialog service
canCreateNew = true;
includeNoteInput = false;
title: string = _('order.set-customer-for-order');

customerForm: UntypedFormGroup;
customers$: Observable<Customer[]>;
isLoading = false;
input$ = new Subject<string>();
selectedCustomer: Customer[] = [];
useExisting = true;
createNew = false;
note = '';

constructor(private dataService: DataService, private formBuilder: UntypedFormBuilder) {
this.customerForm = this.formBuilder.group({
Expand Down Expand Up @@ -62,11 +71,10 @@ export class SelectCustomerDialogComponent implements OnInit, Dialog<Customer |

select() {
if (this.useExisting && this.selectedCustomer.length === 1) {
this.resolveWith(this.selectedCustomer[0]);
}
if (this.createNew && this.customerForm.valid) {
this.resolveWith({ ...this.selectedCustomer[0], note: this.note });
} else if (this.createNew && this.customerForm.valid) {
const formValue = this.customerForm.value;
this.resolveWith(formValue);
this.resolveWith({ ...formValue, note: this.note });
}
}
}
Loading

0 comments on commit a9a596e

Please sign in to comment.