Skip to content

Commit

Permalink
feat(admin-ui): Implement order process state chart view
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Jul 13, 2020
1 parent 0a77438 commit 7283258
Show file tree
Hide file tree
Showing 24 changed files with 503 additions and 20 deletions.
13 changes: 12 additions & 1 deletion packages/admin-ui/src/lib/core/src/common/generated-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2567,6 +2567,12 @@ export type OrderListOptions = {
filter?: Maybe<OrderFilterParameter>;
};

export type OrderProcessState = {
__typename?: 'OrderProcessState';
name: Scalars['String'];
to: Array<Scalars['String']>;
};

export type OrderSortParameter = {
id?: Maybe<SortOrder>;
createdAt?: Maybe<SortOrder>;
Expand Down Expand Up @@ -3378,6 +3384,7 @@ export type SearchResultSortParameter = {

export type ServerConfig = {
__typename?: 'ServerConfig';
orderProcess: Array<OrderProcessState>;
customFieldConfig: CustomFields;
};

Expand Down Expand Up @@ -6309,7 +6316,10 @@ export type GetServerConfigQuery = (
{ __typename?: 'GlobalSettings' }
& { serverConfig: (
{ __typename?: 'ServerConfig' }
& { customFieldConfig: (
& { orderProcess: Array<(
{ __typename?: 'OrderProcessState' }
& Pick<OrderProcessState, 'name' | 'to'>
)>, customFieldConfig: (
{ __typename?: 'CustomFields' }
& { Address: Array<(
{ __typename?: 'StringCustomFieldConfig' }
Expand Down Expand Up @@ -7897,6 +7907,7 @@ export namespace GetServerConfig {
export type Query = GetServerConfigQuery;
export type GlobalSettings = GetServerConfigQuery['globalSettings'];
export type ServerConfig = GetServerConfigQuery['globalSettings']['serverConfig'];
export type OrderProcess = (NonNullable<GetServerConfigQuery['globalSettings']['serverConfig']['orderProcess'][0]>);
export type CustomFieldConfig = GetServerConfigQuery['globalSettings']['serverConfig']['customFieldConfig'];
export type Address = CustomFieldsFragment;
export type Collection = CustomFieldsFragment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ export const GET_SERVER_CONFIG = gql`
query GetServerConfig {
globalSettings {
serverConfig {
orderProcess {
name
to
}
customFieldConfig {
Address {
...CustomFields
Expand Down
11 changes: 8 additions & 3 deletions packages/admin-ui/src/lib/core/src/data/server-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CustomFields,
GetGlobalSettings,
GetServerConfig,
OrderProcessState,
ServerConfig,
} from '../common/generated-types';

Expand Down Expand Up @@ -46,10 +47,10 @@ export class ServerConfigService {
.query<GetServerConfig.Query>(GET_SERVER_CONFIG)
.single$.toPromise()
.then(
result => {
(result) => {
this._serverConfig = result.globalSettings.serverConfig;
},
err => {
(err) => {
// Let the error fall through to be caught by the http interceptor.
},
);
Expand All @@ -58,7 +59,7 @@ export class ServerConfigService {
getAvailableLanguages() {
return this.baseDataService
.query<GetGlobalSettings.Query>(GET_GLOBAL_SETTINGS, {}, 'cache-first')
.mapSingle(res => res.globalSettings.availableLanguages);
.mapSingle((res) => res.globalSettings.availableLanguages);
}

/**
Expand All @@ -76,6 +77,10 @@ export class ServerConfigService {
return this.serverConfig.customFieldConfig[type] || [];
}

getOrderProcessStates(): OrderProcessState[] {
return this.serverConfig.orderProcess;
}

get serverConfig(): ServerConfig {
return this._serverConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<clr-icon shape="success-standard" *ngIf="state === 'Fulfilled'" size="12"></clr-icon>
<clr-icon shape="success-standard" *ngIf="state === 'PartiallyFulfilled'" size="12"></clr-icon>
<clr-icon shape="ban" *ngIf="state === 'Cancelled'" size="12"></clr-icon>
{{ stateToken | translate }}
{{ state | orderStateI18nToken | translate }}
<ng-content></ng-content>
</vdr-chip>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';

@Component({
selector: 'vdr-order-state-label',
Expand All @@ -9,19 +8,6 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
})
export class OrderStateLabelComponent {
@Input() state: string;
private readonly stateI18nTokens = {
AddingItems: _('order.state-adding-items'),
ArrangingPayment: _('order.state-arranging-payment'),
PaymentAuthorized: _('order.state-payment-authorized'),
PaymentSettled: _('order.state-payment-settled'),
PartiallyFulfilled: _('order.state-partially-fulfilled'),
Fulfilled: _('order.state-fulfilled'),
Cancelled: _('order.state-cancelled'),
};

get stateToken(): string {
return this.stateI18nTokens[this.state as any] || this.state;
}

get chipColorType() {
switch (this.state) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { OrderStateI18nTokenPipe } from './order-state-i18n-token.pipe';

describe('orderStateI18nTokenPipe', () => {
const pipe = new OrderStateI18nTokenPipe();

it('works with default states', () => {
const result = pipe.transform('AddingItems');

expect(result).toBe('order.state-adding-items');
});

it('works with unknown states', () => {
const result = pipe.transform('ValidatingCustomer');

expect(result).toBe('order.state-validating-customer');
});

it('works with unknown states with various formatting', () => {
const result1 = pipe.transform('validating-Customer');
expect(result1).toBe('order.state-validating-customer');

const result2 = pipe.transform('validating-Customer');
expect(result2).toBe('order.state-validating-customer');

const result3 = pipe.transform('Validating Customer');
expect(result3).toBe('order.state-validating-customer');
});

it('passes through non-string values', () => {
expect(pipe.transform(null)).toBeNull();
expect(pipe.transform(1)).toBe(1);
expect(pipe.transform({})).toEqual({});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Pipe, PipeTransform } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';

@Pipe({
name: 'orderStateI18nToken',
})
export class OrderStateI18nTokenPipe implements PipeTransform {
private readonly stateI18nTokens = {
AddingItems: _('order.state-adding-items'),
ArrangingPayment: _('order.state-arranging-payment'),
PaymentAuthorized: _('order.state-payment-authorized'),
PaymentSettled: _('order.state-payment-settled'),
PartiallyFulfilled: _('order.state-partially-fulfilled'),
Fulfilled: _('order.state-fulfilled'),
Cancelled: _('order.state-cancelled'),
};
transform<T extends unknown>(value: T): T {
if (typeof value === 'string') {
const defaultStateToken = this.stateI18nTokens[value as any];
if (defaultStateToken) {
return defaultStateToken;
}
return ('order.state-' +
value
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/ +/g, '-')
.toLowerCase()) as any;
}
return value;
}
}
2 changes: 2 additions & 0 deletions packages/admin-ui/src/lib/core/src/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import { CustomFieldLabelPipe } from './pipes/custom-field-label.pipe';
import { DurationPipe } from './pipes/duration.pipe';
import { FileSizePipe } from './pipes/file-size.pipe';
import { HasPermissionPipe } from './pipes/has-permission.pipe';
import { OrderStateI18nTokenPipe } from './pipes/order-state-i18n-token.pipe';
import { SentenceCasePipe } from './pipes/sentence-case.pipe';
import { SortPipe } from './pipes/sort.pipe';
import { StringToColorPipe } from './pipes/string-to-color.pipe';
Expand Down Expand Up @@ -172,6 +173,7 @@ const DECLARATIONS = [
TimelineEntryComponent,
HistoryEntryDetailComponent,
EditNoteDialogComponent,
OrderStateI18nTokenPipe,
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<vdr-ab-left>
<div class="flex clr-align-items-center">
<vdr-entity-info [entity]="entity$ | async"></vdr-entity-info>
<vdr-order-state-label [state]="order.state"></vdr-order-state-label>
<vdr-order-state-label [state]="order.state">
<button class="icon-button" (click)="openStateDiagram()" [title]="'order.order-state-diagram' | translate">
<clr-icon shape="list"></clr-icon>
</button>
</vdr-order-state-label>
</div>
</vdr-ab-left>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { map, startWith, switchMap, take } from 'rxjs/operators';

import { CancelOrderDialogComponent } from '../cancel-order-dialog/cancel-order-dialog.component';
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 { SettleRefundDialogComponent } from '../settle-refund-dialog/settle-refund-dialog.component';

Expand Down Expand Up @@ -114,6 +115,22 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
return ['/marketing', 'promotions', id];
}

openStateDiagram() {
this.entity$
.pipe(
take(1),
switchMap((order) =>
this.modalService.fromComponent(OrderProcessGraphDialogComponent, {
closable: true,
locals: {
activeState: order.state,
},
}),
),
)
.subscribe();
}

transitionToState(state: string) {
this.dataService.order.transitionToState(this.id, state).subscribe((val) => {
this.notificationService.success(_('order.transitioned-to-state-success'), { state });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<ng-template vdrDialogTitle>{{ 'order.order-state-diagram' | translate }}</ng-template>

<vdr-order-process-graph [states]="states" [initialState]="activeState"></vdr-order-process-graph>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import {
CancelOrderInput,
DataService,
Dialog,
OrderProcessState,
ServerConfigService,
} from '@vendure/admin-ui/core';
import { Observable } from 'rxjs';

@Component({
selector: 'vdr-order-process-graph-dialog',
templateUrl: './order-process-graph-dialog.component.html',
styleUrls: ['./order-process-graph-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrderProcessGraphDialogComponent implements OnInit, Dialog<void> {
activeState: string;
states: OrderProcessState[] = [];
constructor(private serverConfigService: ServerConfigService) {}

ngOnInit(): void {
this.states = this.serverConfigService.getOrderProcessStates();
}

resolveWith: (result: void | undefined) => void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NODE_HEIGHT = 72;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div
[attr.data-from]="from.node.name"
[attr.data-to]="to.node.name"
[ngStyle]="getStyle()"
[class.active]="active$ | async"
class="edge">
<clr-icon shape="arrow" flip="vertical" class="arrow"></clr-icon>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import 'variables';

.edge {
position: absolute;
border: 1px solid $color-grey-300;
background-color: $color-grey-300;
opacity: 0.3;
transition: border 0.2s, opacity 0.2s, background-color 0.2s;
&.active {
border-color: $color-primary-500;
background-color: $color-primary-500;
opacity: 1;
.arrow {
color: $color-primary-500;
}
}
.arrow {
position: absolute;
bottom: -4px;
left: -8px;
color: $color-grey-300;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

import { OrderProcessNodeComponent } from './order-process-node.component';

@Component({
selector: 'vdr-order-process-edge',
templateUrl: './order-process-edge.component.html',
styleUrls: ['./order-process-edge.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrderProcessEdgeComponent implements OnInit {
@Input() from: OrderProcessNodeComponent;
@Input() to: OrderProcessNodeComponent;
@Input() index: number;
active$: Observable<boolean>;

ngOnInit() {
this.active$ = this.from.active$
.asObservable()
.pipe(tap((active) => this.to.activeTarget$.next(active)));
}

getStyle() {
const direction = this.from.index < this.to.index ? 'down' : 'up';
const startPos = this.from.getPos(direction === 'down' ? 'bottom' : 'top');
const endPos = this.to.getPos(direction === 'down' ? 'top' : 'bottom');
const dX = Math.abs(startPos.x - endPos.x);
const dY = Math.abs(startPos.y - endPos.y);
const length = Math.sqrt(dX ** 2 + dY ** 2);
return {
'top.px': startPos.y,
'left.px': startPos.x + (direction === 'down' ? 10 : 40) + this.index * 12,
'height.px': length,
'width.px': 1,
...(direction === 'up'
? {
transform: 'rotateZ(180deg)',
'transform-origin': 'top',
}
: {}),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<ng-container *ngFor="let state of nodes; let i = index">
<vdr-order-process-node
[node]="state"
[index]="i"
[active]="(activeState$ | async) === state.name"
(mouseenter)="onMouseOver(state.name)"
(mouseleave)="onMouseOut()"
></vdr-order-process-node>
</ng-container>
<ng-container *ngFor="let edge of edges">
<vdr-order-process-edge [from]="edge.from" [to]="edge.to" [index]="edge.index"></vdr-order-process-edge>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import "variables";

:host {
display: block;
border: 1px hotpink;
margin: 20px;
padding: 12px;
position: relative;
}

.state-row {
display: flex;
}
Loading

0 comments on commit 7283258

Please sign in to comment.