Forráskód Böngészése

feat(admin-ui): Implement adding notes to Order history

Relates to #118
Michael Bromley 6 éve
szülő
commit
11089145cb

+ 14 - 0
admin-ui/src/app/common/generated-types.ts

@@ -3612,6 +3612,13 @@ export type GetOrderHistoryQueryVariables = {
 
 export type GetOrderHistoryQuery = ({ __typename?: 'Query' } & { order: Maybe<({ __typename?: 'Order' } & Pick<Order, 'id'> & { history: ({ __typename?: 'HistoryEntryList' } & Pick<HistoryEntryList, 'totalItems'> & { items: Array<({ __typename?: 'HistoryEntry' } & Pick<HistoryEntry, 'id' | 'type' | 'createdAt' | 'data'> & { administrator: Maybe<({ __typename?: 'Administrator' } & Pick<Administrator, 'id' | 'firstName' | 'lastName'>)> })> }) })> });
 
+export type AddNoteToOrderMutationVariables = {
+  input: AddNoteToOrderInput
+};
+
+
+export type AddNoteToOrderMutation = ({ __typename?: 'Mutation' } & { addNoteToOrder: ({ __typename?: 'Order' } & Pick<Order, 'id' | 'updatedAt'> & { history: ({ __typename?: 'HistoryEntryList' } & Pick<HistoryEntryList, 'totalItems'>) }) });
+
 export type AssetFragment = ({ __typename?: 'Asset' } & Pick<Asset, 'id' | 'createdAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source'>);
 
 export type ProductVariantFragment = ({ __typename?: 'ProductVariant' } & Pick<ProductVariant, 'id' | 'enabled' | 'languageCode' | 'name' | 'price' | 'currencyCode' | 'priceIncludesTax' | 'priceWithTax' | 'stockOnHand' | 'trackInventory' | 'sku'> & { taxRateApplied: ({ __typename?: 'TaxRate' } & Pick<TaxRate, 'id' | 'name' | 'value'>), taxCategory: ({ __typename?: 'TaxCategory' } & Pick<TaxCategory, 'id' | 'name'>), options: Array<({ __typename?: 'ProductOption' } & Pick<ProductOption, 'id' | 'code' | 'languageCode' | 'name'>)>, facetValues: Array<({ __typename?: 'FacetValue' } & Pick<FacetValue, 'id' | 'code' | 'name'> & { facet: ({ __typename?: 'Facet' } & Pick<Facet, 'id' | 'name'>) })>, featuredAsset: Maybe<({ __typename?: 'Asset' } & AssetFragment)>, assets: Array<({ __typename?: 'Asset' } & AssetFragment)>, translations: Array<({ __typename?: 'ProductVariantTranslation' } & Pick<ProductVariantTranslation, 'id' | 'languageCode' | 'name'>)> });
@@ -4430,6 +4437,13 @@ export namespace GetOrderHistory {
   export type Administrator = (NonNullable<(NonNullable<(NonNullable<GetOrderHistoryQuery['order']>)['history']['items'][0]>)['administrator']>);
 }
 
+export namespace AddNoteToOrder {
+  export type Variables = AddNoteToOrderMutationVariables;
+  export type Mutation = AddNoteToOrderMutation;
+  export type AddNoteToOrder = AddNoteToOrderMutation['addNoteToOrder'];
+  export type History = AddNoteToOrderMutation['addNoteToOrder']['history'];
+}
+
 export namespace Asset {
   export type Fragment = AssetFragment;
 }

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

@@ -243,3 +243,11 @@ export const GET_ORDER_HISTORY = gql`
         }
     }
 `;
+
+export const ADD_NOTE_TO_ORDER = gql`
+    mutation AddNoteToOrder($input: AddNoteToOrderInput!) {
+        addNoteToOrder(input: $input) {
+            id
+        }
+    }
+`;

+ 12 - 0
admin-ui/src/app/data/providers/order-data.service.ts

@@ -1,4 +1,6 @@
 import {
+    AddNoteToOrder,
+    AddNoteToOrderInput,
     CancelOrder,
     CancelOrderInput,
     CreateFulfillment,
@@ -14,6 +16,7 @@ import {
     SettleRefundInput,
 } from '../../common/generated-types';
 import {
+    ADD_NOTE_TO_ORDER,
     CANCEL_ORDER,
     CREATE_FULFILLMENT,
     GET_ORDER,
@@ -84,4 +87,13 @@ export class OrderDataService {
             input,
         });
     }
+
+    addNoteToOrder(input: AddNoteToOrderInput) {
+        return this.baseDataService.mutate<AddNoteToOrder.Mutation, AddNoteToOrder.Variables>(
+            ADD_NOTE_TO_ORDER,
+            {
+                input,
+            },
+        );
+    }
 }

+ 5 - 1
admin-ui/src/app/order/components/order-detail/order-detail.component.html

@@ -104,7 +104,11 @@
                 </tr>
             </table>
 
-            <vdr-order-history [order]="order"></vdr-order-history>
+            <vdr-order-history
+                [order]="order"
+                [history]="history$ | async"
+                (addNote)="addNote($event)"
+            ></vdr-order-history>
         </div>
         <div class="clr-col-lg-4 order-cards">
             <div class="card">

+ 30 - 3
admin-ui/src/app/order/components/order-detail/order-detail.component.ts

@@ -1,13 +1,13 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
-import { Observable, of } from 'rxjs';
-import { switchMap, take } from 'rxjs/operators';
+import { Observable, of, Subject } from 'rxjs';
+import { startWith, switchMap, take } from 'rxjs/operators';
 import { omit } from 'shared/omit';
 import { _ } from 'src/app/core/providers/i18n/mark-for-extraction';
 
 import { BaseDetailComponent } from '../../../common/base-detail.component';
-import { Order, OrderDetail } from '../../../common/generated-types';
+import { GetOrderHistory, Order, OrderDetail, SortOrder } from '../../../common/generated-types';
 import { NotificationService } from '../../../core/providers/notification/notification.service';
 import { DataService } from '../../../data/providers/data.service';
 import { ServerConfigService } from '../../../data/server-config';
@@ -26,6 +26,8 @@ import { SettleRefundDialogComponent } from '../settle-refund-dialog/settle-refu
 export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragment>
     implements OnInit, OnDestroy {
     detailForm = new FormGroup({});
+    history$: Observable<GetOrderHistory.Items[] | null>;
+    fetchHistory = new Subject<void>();
     constructor(
         router: Router,
         route: ActivatedRoute,
@@ -40,6 +42,18 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
 
     ngOnInit() {
         this.init();
+        this.history$ = this.fetchHistory.pipe(
+            startWith(null),
+            switchMap(() => {
+                return this.dataService.order
+                    .getOrderHistory(this.id, {
+                        sort: {
+                            createdAt: SortOrder.DESC,
+                        },
+                    })
+                    .mapStream(data => data.order && data.order.history.items);
+            }),
+        );
     }
 
     ngOnDestroy() {
@@ -135,6 +149,18 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
             });
     }
 
+    addNote(note: string) {
+        this.dataService.order
+            .addNoteToOrder({
+                id: this.id,
+                note,
+            })
+            .pipe(switchMap(result => this.refetchOrder(result)))
+            .subscribe(result => {
+                this.notificationService.success(_('order.add-note-success'));
+            });
+    }
+
     private cancelOrder(order: OrderDetail.Fragment) {
         this.modalService
             .fromComponent(CancelOrderDialogComponent, {
@@ -197,6 +223,7 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
     }
 
     private refetchOrder(result: object | undefined) {
+        this.fetchHistory.next();
         if (result) {
             return this.dataService.order.getOrder(this.id).single$;
         } else {

+ 25 - 10
admin-ui/src/app/order/components/order-history/order-history.component.html

@@ -1,8 +1,20 @@
 <h4>{{ 'order.order-history' | translate }}</h4>
 <div class="entry-list">
-    <div class="entry"></div>
+    <div class="entry has-custom-icon">
+        <div class="timeline">
+            <div class="custom-icon">
+                <clr-icon shape="note" size="24" class="compose-note"></clr-icon>
+            </div>
+        </div>
+        <div class="entry-body note-entry">
+            <textarea [(ngModel)]="note" name="note" class="note"></textarea>
+            <button class="btn btn-secondary" [disabled]="!note" (click)="addNoteToOrder()">
+                {{ 'order.add-note' | translate }}
+            </button>
+        </div>
+    </div>
     <div
-        *ngFor="let entry of history$ | async"
+        *ngFor="let entry of history"
         [ngClass]="getClassForEntry(entry)"
         [class.has-custom-icon]="!!getTimelineIcon(entry)"
         class="entry"
@@ -27,6 +39,7 @@
                     size="24"
                     class="cancelled"
                 ></clr-icon>
+                <clr-icon *ngIf="getTimelineIcon(entry) === 'note'" shape="note" size="24"></clr-icon>
             </div>
         </div>
         <div class="entry-body" [ngSwitch]="entry.type">
@@ -62,7 +75,7 @@
                     *ngIf="entry.data.to === 'Settled'; else regularPaymentTransition"
                 >
                     <div class="title">
-                        {{ 'order.history-payment-settled' | translate }}
+                        {{ 'order.history-payment-settled' | translate: { id: entry.data.paymentId } }}
                     </div>
                     {{ 'order.transaction-id' | translate }}: {{ getPayment(entry)?.transactionId }}
                     <vdr-history-entry-detail *ngIf="getPayment(entry) as payment">
@@ -75,14 +88,15 @@
                 <ng-template #regularPaymentTransition>
                     {{
                         'order.history-payment-transition'
-                            | translate: { from: entry.data.from, to: entry.data.to }
+                            | translate
+                                : { from: entry.data.from, to: entry.data.to, id: entry.data.paymentId }
                     }}
                 </ng-template>
             </ng-container>
             <ng-container *ngSwitchCase="type.ORDER_REFUND_TRANSITION">
                 {{
                     'order.history-refund-transition'
-                        | translate: { from: entry.data.from, to: entry.data.to }
+                        | translate: { from: entry.data.from, to: entry.data.to, id: entry.data.refundId }
                 }}
             </ng-container>
             <ng-container *ngSwitchCase="type.ORDER_CANCELLATION">
@@ -92,11 +106,7 @@
                         {{ entry.data.reason }}
                     </vdr-labeled-data>
                     <vdr-labeled-data [label]="'order.contents' | translate">
-                        <div class="items-list">
-                            <ul>
-                                <li *ngFor="let item of items">{{ item.name }} x {{ item.quantity }}</li>
-                            </ul>
-                        </div>
+                        <vdr-simple-item-list [items]="items"></vdr-simple-item-list>
                     </vdr-labeled-data>
                 </vdr-history-entry-detail>
             </ng-container>
@@ -114,6 +124,11 @@
                     </vdr-history-entry-detail>
                 </div>
             </ng-container>
+            <ng-container *ngSwitchCase="type.ORDER_NOTE">
+                <div class="featured-entry">
+                    <div class="note-text">{{ entry.data.note }}</div>
+                </div>
+            </ng-container>
         </div>
     </div>
 </div>

+ 21 - 5
admin-ui/src/app/order/components/order-history/order-history.component.scss

@@ -59,6 +59,9 @@
     .cancelled {
         color: $color-error-400;
     }
+    .compose-note {
+        color: $color-grey-400;
+    }
 }
 .entry.has-custom-icon {
     .timeline:after {
@@ -76,17 +79,23 @@
 
 .entry-body {
     flex: 1;
-    padding-bottom: 18px;
+    padding-bottom: 24px;
     padding-left: 12px;
     line-height: 16px;
     margin-left: 12px;
+    color: $color-grey-500;
 }
 
 .featured-entry {
     .title {
+        color: $color-grey-800;
         font-size: 16px;
         line-height: 26px;
     }
+    .note-text {
+        color: $color-grey-800;
+        white-space: pre-wrap;
+    }
 }
 
 .detail {
@@ -120,10 +129,17 @@
         border: 1px solid $color-warning-400;
     }
 }
-.items-list {
-    font-size: 12px;
 
-    ul {
-        list-style-type: none;
+.note-entry {
+    display: flex;
+
+    button {
+        margin: 0;
     }
 }
+textarea.note {
+    flex: 1;
+    height: 36px;
+    border-radius: 3px;
+    margin-right: 6px;
+}

+ 10 - 29
admin-ui/src/app/order/components/order-history/order-history.component.ts

@@ -1,15 +1,11 @@
-import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
-import { Observable, Subject } from 'rxjs';
-import { startWith, switchMap } from 'rxjs/operators';
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
 
 import {
     GetOrderHistory,
     HistoryEntryType,
     OrderDetail,
     OrderDetailFragment,
-    SortOrder,
 } from '../../../common/generated-types';
-import { DataService } from '../../../data/providers/data.service';
 
 @Component({
     selector: 'vdr-order-history',
@@ -17,32 +13,12 @@ import { DataService } from '../../../data/providers/data.service';
     styleUrls: ['./order-history.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class OrderHistoryComponent implements OnInit, OnChanges {
+export class OrderHistoryComponent {
     @Input() order: OrderDetailFragment;
-    history$: Observable<GetOrderHistory.Items[] | null>;
+    @Input() history: GetOrderHistory.Items[];
+    @Output() addNote = new EventEmitter<string>();
+    note = '';
     readonly type = HistoryEntryType;
-    private orderChange = new Subject<void>();
-
-    constructor(private dataService: DataService) {}
-
-    ngOnInit() {
-        this.history$ = this.orderChange.pipe(
-            startWith(null),
-            switchMap(() => {
-                return this.dataService.order
-                    .getOrderHistory(this.order.id, {
-                        sort: {
-                            createdAt: SortOrder.DESC,
-                        },
-                    })
-                    .mapStream(data => data.order && data.order.history.items);
-            }),
-        );
-    }
-
-    ngOnChanges(changes: SimpleChanges): void {
-        this.orderChange.next();
-    }
 
     getClassForEntry(entry: GetOrderHistory.Items): 'success' | 'error' | 'warning' | undefined {
         if (entry.type === HistoryEntryType.ORDER_PAYMENT_TRANSITION) {
@@ -124,4 +100,9 @@ export class OrderHistoryComponent implements OnInit, OnChanges {
         }
         return '';
     }
+
+    addNoteToOrder() {
+        this.addNote.emit(this.note);
+        this.note = '';
+    }
 }

+ 2 - 0
admin-ui/src/i18n-messages/en.json

@@ -405,6 +405,8 @@
     "tax-rates": "Tax Rates"
   },
   "order": {
+    "add-note": "Add note",
+    "add-note-success": "Note successfully added",
     "amount": "Amount",
     "cancel": "Cancel",
     "cancel-order": "Cancel Order",