Browse Source

feat(admin-ui): Create order detail component & routing

Michael Bromley 7 years ago
parent
commit
3319d2bd62

+ 1 - 1
admin-ui/src/app/core/components/breadcrumb/breadcrumb.component.ts

@@ -136,7 +136,7 @@ export class BreadcrumbComponent implements OnDestroy {
         if (clone[0] === '../') {
             clone[0] = segmentPaths.slice(0, -1).join('/');
         }
-        return clone;
+        return clone.filter(segment => segment !== '');
     }
 }
 

+ 62 - 0
admin-ui/src/app/data/definitions/order-definitions.ts

@@ -1,7 +1,30 @@
 import gql from 'graphql-tag';
 
+export const ADJUSTMENT_FRAGMENT = gql`
+    fragment Adjustment on Adjustment {
+        adjustmentSource
+        amount
+        description
+        type
+    }
+`;
+
 export const ORDER_FRAGMENT = gql`
     fragment Order on Order {
+        id
+        createdAt
+        updatedAt
+        code
+        total
+        customer {
+            firstName
+            lastName
+        }
+    }
+`;
+
+export const ORDER_WITH_LINES_FRAGMENT = gql`
+    fragment OrderWithLines on Order {
         id
         createdAt
         updatedAt
@@ -10,7 +33,37 @@ export const ORDER_FRAGMENT = gql`
             firstName
             lastName
         }
+        lines {
+            id
+            featuredAsset {
+                preview
+            }
+            productVariant {
+                id
+                name
+                sku
+            }
+            unitPrice
+            unitPriceWithTax
+            quantity
+            items {
+                id
+                unitPrice
+                unitPriceIncludesTax
+                unitPriceWithTax
+                taxRate
+            }
+            totalPrice
+        }
+        adjustments {
+            ...Adjustment
+        }
+        subTotal
+        subTotalBeforeTax
+        totalBeforeTax
+        total
     }
+    ${ADJUSTMENT_FRAGMENT}
 `;
 
 export const GET_ORDERS_LIST = gql`
@@ -24,3 +77,12 @@ export const GET_ORDERS_LIST = gql`
     }
     ${ORDER_FRAGMENT}
 `;
+
+export const GET_ORDER = gql`
+    query GetOrder($id: ID!) {
+        order(id: $id) {
+            ...OrderWithLines
+        }
+    }
+    ${ORDER_WITH_LINES_FRAGMENT}
+`;

+ 1 - 0
admin-ui/src/app/data/providers/data.service.mock.ts

@@ -59,6 +59,7 @@ export class MockDataService implements DataServiceMock {
     };
     order = {
         getOrders: spyQueryResult('getOrders'),
+        getOrder: spyQueryResult('getOrder'),
     };
     product = {
         getProducts: spyQueryResult('getProducts'),

+ 6 - 3
admin-ui/src/app/data/providers/order-data.service.ts

@@ -1,7 +1,6 @@
-import { GetOrderList } from 'shared/generated-types';
+import { GetOrder, GetOrderList } from 'shared/generated-types';
 
-import { getDefaultLanguage } from '../../common/utilities/get-default-language';
-import { GET_ORDERS_LIST } from '../definitions/order-definitions';
+import { GET_ORDER, GET_ORDERS_LIST } from '../definitions/order-definitions';
 
 import { BaseDataService } from './base-data.service';
 
@@ -16,4 +15,8 @@ export class OrderDataService {
             },
         });
     }
+
+    getOrder(id: string) {
+        return this.baseDataService.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, { id });
+    }
 }

+ 71 - 0
admin-ui/src/app/order/components/order-detail/order-detail.component.html

@@ -0,0 +1,71 @@
+<vdr-action-bar>
+    <vdr-ab-left>
+    </vdr-ab-left>
+
+    <vdr-ab-right>
+    </vdr-ab-right>
+</vdr-action-bar>
+
+<div *ngIf="entity$ | async as order">
+
+    <div class="order-info">
+        <div class="order-code">{{ 'order.order-code' | translate }}: {{ order.code }}</div>
+        <div class="created">{{ 'common.created' | translate }}: {{ order.createdAt | date:'medium' }}</div>
+        <div class="updated">{{ 'common.updated' | translate }}: {{ order.updatedAt | date:'medium' }}</div>
+    </div>
+
+    <table class="order-lines table">
+        <thead>
+        <tr>
+            <th></th>
+            <th>{{ 'order.product-name' | translate }}</th>
+            <th>{{ 'order.product-sku' | translate }}</th>
+            <th>{{ 'order.unit-price' | translate }}</th>
+            <th>{{ 'order.quantity' | translate }}</th>
+            <th>{{ 'order.total' | translate }}</th>
+        </tr>
+        </thead>
+        <tr *ngFor="let line of order.lines"
+             class="order-line">
+            <td class="thumb">
+                <img [src]="line.featuredAsset.preview + '?preset=tiny'">
+            </td>
+            <td class="name">
+                {{ line.productVariant.name }}
+            </td>
+            <td class="sku">
+                {{ line.productVariant.sku }}
+            </td>
+            <td class="unit-price">
+                {{ line.unitPriceWithTax / 100 | currency }}
+            </td>
+            <td class="quantity">
+                {{ line.quantity }}
+            </td>
+            <td class="total">
+                {{ line.totalPrice / 100 | currency }}
+            </td>
+        </tr>
+        <tr class="sub-total">
+            <td class="left">{{ 'order.sub-total' | translate }}</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>{{ order.subTotal / 100 | currency }}</td>
+        </tr>
+        <tr class="order-ajustment" *ngFor="let adjustment of order.adjustments">
+            <td colspan="5" class="left">{{ adjustment.description  }}</td>
+            <td>{{ adjustment.amount / 100 | currency }}</td>
+        </tr>
+        <tr class="total">
+            <td class="left">{{ 'order.total' | translate }}</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>{{ order.total / 100 | currency }}</td>
+        </tr>
+    </table>
+
+</div>

+ 13 - 0
admin-ui/src/app/order/components/order-detail/order-detail.component.scss

@@ -0,0 +1,13 @@
+@import "variables";
+
+.order-lines {
+
+    .sub-total td {
+        border-top: 1px dashed $color-grey-3;
+    }
+
+    .total td {
+        font-weight: bold;
+        border-top: 1px dashed $color-grey-3;
+    }
+}

+ 31 - 0
admin-ui/src/app/order/components/order-detail/order-detail.component.ts

@@ -0,0 +1,31 @@
+import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Order, OrderWithLines } from 'shared/generated-types';
+
+import { BaseDetailComponent } from '../../../common/base-detail.component';
+import { ServerConfigService } from '../../../data/server-config';
+
+@Component({
+    selector: 'vdr-order-detail',
+    templateUrl: './order-detail.component.html',
+    styleUrls: ['./order-detail.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class OrderDetailComponent extends BaseDetailComponent<OrderWithLines.Fragment>
+    implements OnInit, OnDestroy {
+    constructor(router: Router, route: ActivatedRoute, serverConfigService: ServerConfigService) {
+        super(route, router, serverConfigService);
+    }
+
+    ngOnInit() {
+        this.init();
+    }
+
+    ngOnDestroy() {
+        this.destroy();
+    }
+
+    protected setFormValues(entity: Order.Fragment): void {
+        // empty
+    }
+}

+ 4 - 2
admin-ui/src/app/order/components/order-list/order-list.component.html

@@ -15,14 +15,16 @@
                 (itemsPerPageChange)="setItemsPerPage($event)">
     <vdr-dt-column>{{ 'common.ID' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.code' | translate }}</vdr-dt-column>
+    <vdr-dt-column>{{ 'order.total' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.created-at' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.updated-at' | translate }}</vdr-dt-column>
     <vdr-dt-column></vdr-dt-column>
     <ng-template let-order="item">
         <td class="left">{{ order.id }}</td>
         <td class="left">{{ order.code }}</td>
-        <td class="left">{{ order.createdAt }}</td>
-        <td class="left">{{ order.updatedAt }}</td>
+        <td class="left">{{ order.total / 100 | currency }}</td>
+        <td class="left">{{ order.createdAt | date:'medium' }}</td>
+        <td class="left">{{ order.updatedAt | date:'medium' }}</td>
         <td class="right">
             <vdr-table-row-action iconShape="edit"
                                   [label]="'common.edit' | translate"

+ 1 - 0
admin-ui/src/app/order/components/order-list/order-list.component.scss

@@ -0,0 +1 @@
+@import "variables";

+ 4 - 2
admin-ui/src/app/order/order.module.ts

@@ -3,12 +3,14 @@ import { RouterModule } from '@angular/router';
 
 import { SharedModule } from '../shared/shared.module';
 
+import { OrderDetailComponent } from './components/order-detail/order-detail.component';
 import { OrderListComponent } from './components/order-list/order-list.component';
 import { orderRoutes } from './order.routes';
+import { OrderResolver } from './providers/routing/order-resolver';
 
 @NgModule({
     imports: [SharedModule, RouterModule.forChild(orderRoutes)],
-    declarations: [OrderListComponent],
-    providers: [],
+    declarations: [OrderListComponent, OrderDetailComponent],
+    providers: [OrderResolver],
 })
 export class OrderModule {}

+ 23 - 0
admin-ui/src/app/order/order.routes.ts

@@ -1,8 +1,13 @@
 import { Route } from '@angular/router';
+import { OrderWithLines } from 'shared/generated-types';
 
+import { createResolveData } from '../common/base-entity-resolver';
+import { detailBreadcrumb } from '../common/detail-breadcrumb';
 import { _ } from '../core/providers/i18n/mark-for-extraction';
 
+import { OrderDetailComponent } from './components/order-detail/order-detail.component';
 import { OrderListComponent } from './components/order-list/order-list.component';
+import { OrderResolver } from './providers/routing/order-resolver';
 
 export const orderRoutes: Route[] = [
     {
@@ -12,4 +17,22 @@ export const orderRoutes: Route[] = [
             breadcrumb: _('breadcrumb.orders'),
         },
     },
+    {
+        path: ':id',
+        component: OrderDetailComponent,
+        resolve: createResolveData(OrderResolver),
+        data: {
+            breadcrumb: orderBreadcrumb,
+        },
+    },
 ];
+
+export function orderBreadcrumb(data: any, params: any) {
+    return detailBreadcrumb<OrderWithLines.Fragment>({
+        entity: data.entity,
+        id: params.id,
+        breadcrumbKey: 'breadcrumb.orders',
+        getName: order => order.code,
+        route: '',
+    });
+}

+ 25 - 0
admin-ui/src/app/order/providers/routing/order-resolver.ts

@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { OrderWithLines } from 'shared/generated-types';
+
+import { BaseEntityResolver } from '../../../common/base-entity-resolver';
+import { DataService } from '../../../data/providers/data.service';
+
+/**
+ * Resolves the id from the path into a Customer entity.
+ */
+@Injectable()
+export class OrderResolver extends BaseEntityResolver<OrderWithLines.Fragment> {
+    constructor(private dataService: DataService) {
+        super(
+            {
+                __typename: 'Order',
+                id: '',
+                code: '',
+                createdAt: '',
+                updatedAt: '',
+                total: 0,
+            } as any,
+            id => this.dataService.order.getOrder(id).mapStream(data => data.order),
+        );
+    }
+}

+ 10 - 1
admin-ui/src/i18n-messages/en.json

@@ -75,6 +75,7 @@
     "code": "Code",
     "confirm": "Confirm",
     "create": "Create",
+    "created": "Created",
     "created-at": "Created at",
     "edit": "Edit",
     "edit-field": "Edit field",
@@ -94,6 +95,7 @@
     "remember-me": "Remember me",
     "remove": "Remove",
     "update": "Update",
+    "updated": "Updated",
     "updated-at": "Updated at",
     "username": "Username"
   },
@@ -131,7 +133,14 @@
     "tax-rates": "Tax Rates"
   },
   "order": {
-    "create-new-order": "Create new order"
+    "create-new-order": "Create new order",
+    "order-code": "Order code",
+    "product-name": "Product name",
+    "product-sku": "SKU",
+    "quantity": "Quantity",
+    "sub-total": "Sub total",
+    "total": "Total",
+    "unit-price": "Unit price"
   },
   "settings": {
     "add-countries-to-zone": "Add countries to zone...",

+ 3 - 1
server/src/entity/order-line/order-line.entity.ts

@@ -48,7 +48,9 @@ export class OrderLine extends VendureEntity {
 
     @Calculated()
     get totalPrice(): number {
-        return this.items.reduce((total, item) => total + item.unitPriceWithPromotionsAndTax, 0);
+        return this.items
+            ? this.items.reduce((total, item) => total + item.unitPriceWithPromotionsAndTax, 0)
+            : 0;
     }
 
     @Calculated()

+ 81 - 0
shared/generated-types.ts

@@ -4103,6 +4103,19 @@ export namespace GetOrderList {
     export type Items = Order.Fragment;
 }
 
+export namespace GetOrder {
+    export type Variables = {
+        id: string;
+    };
+
+    export type Query = {
+        __typename?: 'Query';
+        order?: Order | null;
+    };
+
+    export type Order = OrderWithLines.Fragment;
+}
+
 export namespace UpdateProduct {
     export type Variables = {
         input: UpdateProductInput;
@@ -4824,6 +4837,16 @@ export namespace FacetWithValues {
     export type Values = FacetValue.Fragment;
 }
 
+export namespace Adjustment {
+    export type Fragment = {
+        __typename?: 'Adjustment';
+        adjustmentSource: string;
+        amount: number;
+        description: string;
+        type: AdjustmentType;
+    };
+}
+
 export namespace Order {
     export type Fragment = {
         __typename?: 'Order';
@@ -4831,6 +4854,7 @@ export namespace Order {
         createdAt: DateTime;
         updatedAt: DateTime;
         code: string;
+        total: number;
         customer?: Customer | null;
     };
 
@@ -4841,6 +4865,63 @@ export namespace Order {
     };
 }
 
+export namespace OrderWithLines {
+    export type Fragment = {
+        __typename?: 'Order';
+        id: string;
+        createdAt: DateTime;
+        updatedAt: DateTime;
+        code: string;
+        customer?: Customer | null;
+        lines: Lines[];
+        adjustments: Adjustments[];
+        subTotal: number;
+        subTotalBeforeTax: number;
+        totalBeforeTax: number;
+        total: number;
+    };
+
+    export type Customer = {
+        __typename?: 'Customer';
+        firstName: string;
+        lastName: string;
+    };
+
+    export type Lines = {
+        __typename?: 'OrderLine';
+        id: string;
+        featuredAsset?: FeaturedAsset | null;
+        productVariant: ProductVariant;
+        unitPrice: number;
+        unitPriceWithTax: number;
+        quantity: number;
+        items: Items[];
+        totalPrice: number;
+    };
+
+    export type FeaturedAsset = {
+        __typename?: 'Asset';
+        preview: string;
+    };
+
+    export type ProductVariant = {
+        __typename?: 'ProductVariant';
+        id: string;
+        name: string;
+    };
+
+    export type Items = {
+        __typename?: 'OrderItem';
+        id: string;
+        unitPrice: number;
+        unitPriceIncludesTax: boolean;
+        unitPriceWithTax: number;
+        taxRate: number;
+    };
+
+    export type Adjustments = Adjustment.Fragment;
+}
+
 export namespace Asset {
     export type Fragment = {
         __typename?: 'Asset';