Browse Source

feat(admin-ui): Implement UI for updating, deleting notes

Relates to #310
Michael Bromley 5 years ago
parent
commit
ef5eddfbc8
18 changed files with 447 additions and 23 deletions
  1. 76 0
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  2. 19 0
      packages/admin-ui/src/lib/core/src/data/definitions/customer-definitions.ts
  3. 19 0
      packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts
  4. 23 0
      packages/admin-ui/src/lib/core/src/data/providers/customer-data.service.ts
  5. 23 0
      packages/admin-ui/src/lib/core/src/data/providers/order-data.service.ts
  6. 23 0
      packages/admin-ui/src/lib/core/src/shared/components/edit-note-dialog/edit-note-dialog.component.html
  7. 19 0
      packages/admin-ui/src/lib/core/src/shared/components/edit-note-dialog/edit-note-dialog.component.scss
  8. 26 0
      packages/admin-ui/src/lib/core/src/shared/components/edit-note-dialog/edit-note-dialog.component.ts
  9. 2 0
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  10. 7 1
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html
  11. 56 1
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts
  12. 39 5
      packages/admin-ui/src/lib/customer/src/components/customer-history/customer-history.component.html
  13. 9 1
      packages/admin-ui/src/lib/customer/src/components/customer-history/customer-history.component.ts
  14. 2 0
      packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.html
  15. 58 4
      packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts
  16. 38 9
      packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.html
  17. 3 0
      packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.ts
  18. 5 2
      packages/admin-ui/src/lib/static/i18n-messages/en.json

+ 76 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -4553,6 +4553,32 @@ export type AddNoteToCustomerMutation = (
   ) }
 );
 
+export type UpdateCustomerNoteMutationVariables = {
+  input: UpdateCustomerNoteInput;
+};
+
+
+export type UpdateCustomerNoteMutation = (
+  { __typename?: 'Mutation' }
+  & { updateCustomerNote: (
+    { __typename?: 'HistoryEntry' }
+    & Pick<HistoryEntry, 'id' | 'data' | 'isPublic'>
+  ) }
+);
+
+export type DeleteCustomerNoteMutationVariables = {
+  id: Scalars['ID'];
+};
+
+
+export type DeleteCustomerNoteMutation = (
+  { __typename?: 'Mutation' }
+  & { deleteCustomerNote: (
+    { __typename?: 'DeletionResponse' }
+    & Pick<DeletionResponse, 'result' | 'message'>
+  ) }
+);
+
 export type FacetValueFragment = (
   { __typename?: 'FacetValue' }
   & Pick<FacetValue, 'id' | 'createdAt' | 'updatedAt' | 'languageCode' | 'code' | 'name'>
@@ -4910,6 +4936,32 @@ export type AddNoteToOrderMutation = (
   ) }
 );
 
+export type UpdateOrderNoteMutationVariables = {
+  input: UpdateOrderNoteInput;
+};
+
+
+export type UpdateOrderNoteMutation = (
+  { __typename?: 'Mutation' }
+  & { updateOrderNote: (
+    { __typename?: 'HistoryEntry' }
+    & Pick<HistoryEntry, 'id' | 'data' | 'isPublic'>
+  ) }
+);
+
+export type DeleteOrderNoteMutationVariables = {
+  id: Scalars['ID'];
+};
+
+
+export type DeleteOrderNoteMutation = (
+  { __typename?: 'Mutation' }
+  & { deleteOrderNote: (
+    { __typename?: 'DeletionResponse' }
+    & Pick<DeletionResponse, 'result' | 'message'>
+  ) }
+);
+
 export type AssetFragment = (
   { __typename?: 'Asset' }
   & Pick<Asset, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source' | 'width' | 'height'>
@@ -6985,6 +7037,18 @@ export namespace AddNoteToCustomer {
   export type AddNoteToCustomer = AddNoteToCustomerMutation['addNoteToCustomer'];
 }
 
+export namespace UpdateCustomerNote {
+  export type Variables = UpdateCustomerNoteMutationVariables;
+  export type Mutation = UpdateCustomerNoteMutation;
+  export type UpdateCustomerNote = UpdateCustomerNoteMutation['updateCustomerNote'];
+}
+
+export namespace DeleteCustomerNote {
+  export type Variables = DeleteCustomerNoteMutationVariables;
+  export type Mutation = DeleteCustomerNoteMutation;
+  export type DeleteCustomerNote = DeleteCustomerNoteMutation['deleteCustomerNote'];
+}
+
 export namespace FacetValue {
   export type Fragment = FacetValueFragment;
   export type Translations = (NonNullable<FacetValueFragment['translations'][0]>);
@@ -7148,6 +7212,18 @@ export namespace AddNoteToOrder {
   export type AddNoteToOrder = AddNoteToOrderMutation['addNoteToOrder'];
 }
 
+export namespace UpdateOrderNote {
+  export type Variables = UpdateOrderNoteMutationVariables;
+  export type Mutation = UpdateOrderNoteMutation;
+  export type UpdateOrderNote = UpdateOrderNoteMutation['updateOrderNote'];
+}
+
+export namespace DeleteOrderNote {
+  export type Variables = DeleteOrderNoteMutationVariables;
+  export type Mutation = DeleteOrderNoteMutation;
+  export type DeleteOrderNote = DeleteOrderNoteMutation['deleteOrderNote'];
+}
+
 export namespace Asset {
   export type Fragment = AssetFragment;
   export type FocalPoint = (NonNullable<AssetFragment['focalPoint']>);

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

@@ -246,3 +246,22 @@ export const ADD_NOTE_TO_CUSTOMER = gql`
         }
     }
 `;
+
+export const UPDATE_CUSTOMER_NOTE = gql`
+    mutation UpdateCustomerNote($input: UpdateCustomerNoteInput!) {
+        updateCustomerNote(input: $input) {
+            id
+            data
+            isPublic
+        }
+    }
+`;
+
+export const DELETE_CUSTOMER_NOTE = gql`
+    mutation DeleteCustomerNote($id: ID!) {
+        deleteCustomerNote(id: $id) {
+            result
+            message
+        }
+    }
+`;

+ 19 - 0
packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts

@@ -268,3 +268,22 @@ export const ADD_NOTE_TO_ORDER = gql`
         }
     }
 `;
+
+export const UPDATE_ORDER_NOTE = gql`
+    mutation UpdateOrderNote($input: UpdateOrderNoteInput!) {
+        updateOrderNote(input: $input) {
+            id
+            data
+            isPublic
+        }
+    }
+`;
+
+export const DELETE_ORDER_NOTE = gql`
+    mutation DeleteOrderNote($id: ID!) {
+        deleteOrderNote(id: $id) {
+            result
+            message
+        }
+    }
+`;

+ 23 - 0
packages/admin-ui/src/lib/core/src/data/providers/customer-data.service.ts

@@ -10,6 +10,7 @@ import {
     CustomerGroupListOptions,
     CustomerListOptions,
     DeleteCustomerGroup,
+    DeleteCustomerNote,
     GetCustomer,
     GetCustomerGroups,
     GetCustomerGroupWithCustomers,
@@ -24,6 +25,8 @@ import {
     UpdateCustomerGroup,
     UpdateCustomerGroupInput,
     UpdateCustomerInput,
+    UpdateCustomerNote,
+    UpdateCustomerNoteInput,
 } from '../../common/generated-types';
 import {
     ADD_CUSTOMERS_TO_GROUP,
@@ -32,6 +35,7 @@ import {
     CREATE_CUSTOMER_ADDRESS,
     CREATE_CUSTOMER_GROUP,
     DELETE_CUSTOMER_GROUP,
+    DELETE_CUSTOMER_NOTE,
     GET_CUSTOMER,
     GET_CUSTOMER_GROUP_WITH_CUSTOMERS,
     GET_CUSTOMER_GROUPS,
@@ -41,6 +45,7 @@ import {
     UPDATE_CUSTOMER,
     UPDATE_CUSTOMER_ADDRESS,
     UPDATE_CUSTOMER_GROUP,
+    UPDATE_CUSTOMER_NOTE,
 } from '../definitions/customer-definitions';
 
 import { BaseDataService } from './base-data.service';
@@ -201,4 +206,22 @@ export class CustomerDataService {
             },
         );
     }
+
+    updateCustomerNote(input: UpdateCustomerNoteInput) {
+        return this.baseDataService.mutate<UpdateCustomerNote.Mutation, UpdateCustomerNote.Variables>(
+            UPDATE_CUSTOMER_NOTE,
+            {
+                input,
+            },
+        );
+    }
+
+    deleteCustomerNote(id: string) {
+        return this.baseDataService.mutate<DeleteCustomerNote.Mutation, DeleteCustomerNote.Variables>(
+            DELETE_CUSTOMER_NOTE,
+            {
+                id,
+            },
+        );
+    }
 }

+ 23 - 0
packages/admin-ui/src/lib/core/src/data/providers/order-data.service.ts

@@ -4,6 +4,7 @@ import {
     CancelOrder,
     CancelOrderInput,
     CreateFulfillment,
+    DeleteOrderNote,
     FulfillOrderInput,
     GetOrder,
     GetOrderHistory,
@@ -14,17 +15,21 @@ import {
     SettlePayment,
     SettleRefund,
     SettleRefundInput,
+    UpdateOrderNote,
+    UpdateOrderNoteInput,
 } from '../../common/generated-types';
 import {
     ADD_NOTE_TO_ORDER,
     CANCEL_ORDER,
     CREATE_FULFILLMENT,
+    DELETE_ORDER_NOTE,
     GET_ORDER,
     GET_ORDER_HISTORY,
     GET_ORDERS_LIST,
     REFUND_ORDER,
     SETTLE_PAYMENT,
     SETTLE_REFUND,
+    UPDATE_ORDER_NOTE,
 } from '../definitions/order-definitions';
 
 import { BaseDataService } from './base-data.service';
@@ -96,4 +101,22 @@ export class OrderDataService {
             },
         );
     }
+
+    updateOrderNote(input: UpdateOrderNoteInput) {
+        return this.baseDataService.mutate<UpdateOrderNote.Mutation, UpdateOrderNote.Variables>(
+            UPDATE_ORDER_NOTE,
+            {
+                input,
+            },
+        );
+    }
+
+    deleteOrderNote(id: string) {
+        return this.baseDataService.mutate<DeleteOrderNote.Mutation, DeleteOrderNote.Variables>(
+            DELETE_ORDER_NOTE,
+            {
+                id,
+            },
+        );
+    }
 }

+ 23 - 0
packages/admin-ui/src/lib/core/src/shared/components/edit-note-dialog/edit-note-dialog.component.html

@@ -0,0 +1,23 @@
+<ng-template vdrDialogTitle>
+    {{ 'common.edit-note' | translate }}
+</ng-template>
+
+<textarea [(ngModel)]="note" name="note" class="note"></textarea>
+<div class="visibility-select" *ngIf="displayPrivacyControls">
+    <clr-checkbox-wrapper>
+        <input type="checkbox" clrCheckbox [(ngModel)]="noteIsPrivate" />
+        <label>{{ 'order.note-is-private' | translate }}</label>
+    </clr-checkbox-wrapper>
+    <span *ngIf="noteIsPrivate" class="private">
+        {{ 'order.note-only-visible-to-administrators' | translate }}
+    </span>
+    <span *ngIf="!noteIsPrivate" class="public">
+        {{ 'order.note-visible-to-customer' | translate }}
+    </span>
+</div>
+<ng-template vdrDialogButtons>
+    <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
+    <button type="submit" (click)="confirm()" class="btn btn-primary" [disabled]="note.length === 0">
+        {{ 'common.confirm' | translate }}
+    </button>
+</ng-template>

+ 19 - 0
packages/admin-ui/src/lib/core/src/shared/components/edit-note-dialog/edit-note-dialog.component.scss

@@ -0,0 +1,19 @@
+@import "variables";
+
+.visibility-select {
+    display: flex;
+    justify-content: space-between;
+    align-items: baseline;
+    .public {
+        color: $color-warning-500;
+    }
+    .private {
+        color: $color-success-500;
+    }
+}
+textarea.note {
+    width: 100%;
+    height: 72px;
+    border-radius: 3px;
+    margin-right: 6px;
+}

+ 26 - 0
packages/admin-ui/src/lib/core/src/shared/components/edit-note-dialog/edit-note-dialog.component.ts

@@ -0,0 +1,26 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { Dialog } from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'vdr-edit-note-dialog',
+    templateUrl: './edit-note-dialog.component.html',
+    styleUrls: ['./edit-note-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class EditNoteDialogComponent implements Dialog<{ note: string; isPrivate?: boolean }> {
+    displayPrivacyControls = true;
+    noteIsPrivate = true;
+    note = '';
+    resolveWith: (result?: { note: string; isPrivate?: boolean }) => void;
+
+    confirm() {
+        this.resolveWith({
+            note: this.note,
+            isPrivate: this.noteIsPrivate,
+        });
+    }
+
+    cancel() {
+        this.resolveWith();
+    }
+}

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

@@ -41,6 +41,7 @@ import { DropdownItemDirective } from './components/dropdown/dropdown-item.direc
 import { DropdownMenuComponent } from './components/dropdown/dropdown-menu.component';
 import { DropdownTriggerDirective } from './components/dropdown/dropdown-trigger.directive';
 import { DropdownComponent } from './components/dropdown/dropdown.component';
+import { EditNoteDialogComponent } from './components/edit-note-dialog/edit-note-dialog.component';
 import { EmptyPlaceholderComponent } from './components/empty-placeholder/empty-placeholder.component';
 import { EntityInfoComponent } from './components/entity-info/entity-info.component';
 import { ExtensionHostComponent } from './components/extension-host/extension-host.component';
@@ -170,6 +171,7 @@ const DECLARATIONS = [
     EmptyPlaceholderComponent,
     TimelineEntryComponent,
     HistoryEntryDetailComponent,
+    EditNoteDialogComponent,
 ];
 
 @NgModule({

+ 7 - 1
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html

@@ -143,6 +143,12 @@
 </div>
 <div class="clr-row" *ngIf="!(isNew$ | async)">
     <div class="clr-col-md-6">
-        <vdr-customer-history [customer]="entity$ | async" [history]="history$ | async" (addNote)="addNoteToCustomer($event)"></vdr-customer-history>
+        <vdr-customer-history
+            [customer]="entity$ | async"
+            [history]="history$ | async"
+            (addNote)="addNoteToCustomer($event)"
+            (updateNote)="updateNote($event)"
+            (deleteNote)="deleteNote($event)"
+        ></vdr-customer-history>
     </div>
 </div>

+ 56 - 1
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts

@@ -13,6 +13,7 @@ import {
     GetCustomer,
     GetCustomerHistory,
     GetCustomerQuery,
+    HistoryEntry,
     ModalService,
     NotificationService,
     ServerConfigService,
@@ -33,6 +34,7 @@ import {
     take,
 } from 'rxjs/operators';
 
+import { EditNoteDialogComponent } from '../../../../core/src/shared/components/edit-note-dialog/edit-note-dialog.component';
 import { SelectCustomerGroupDialogComponent } from '../select-customer-group-dialog/select-customer-group-dialog.component';
 
 type CustomerWithOrders = NonNullable<GetCustomerQuery['customer']>;
@@ -324,7 +326,60 @@ export class CustomerDetailComponent extends BaseDetailComponent<CustomerWithOrd
     }
 
     addNoteToCustomer({ note }: { note: string }) {
-        this.dataService.customer.addNoteToCustomer(this.id, note).subscribe(() => this.fetchHistory.next());
+        this.dataService.customer.addNoteToCustomer(this.id, note).subscribe(() => {
+            this.fetchHistory.next();
+            this.notificationService.success(_('common.notify-create-success'), {
+                entity: 'Note',
+            });
+        });
+    }
+
+    updateNote(entry: HistoryEntry) {
+        this.modalService
+            .fromComponent(EditNoteDialogComponent, {
+                closable: true,
+                locals: {
+                    displayPrivacyControls: false,
+                    note: entry.data.note,
+                },
+            })
+            .pipe(
+                switchMap((result) => {
+                    if (result) {
+                        return this.dataService.customer.updateCustomerNote({
+                            noteId: entry.id,
+                            note: result.note,
+                        });
+                    } else {
+                        return EMPTY;
+                    }
+                }),
+            )
+            .subscribe((result) => {
+                this.fetchHistory.next();
+                this.notificationService.success(_('common.notify-update-success'), {
+                    entity: 'Note',
+                });
+            });
+    }
+
+    deleteNote(entry: HistoryEntry) {
+        return this.modalService
+            .dialog({
+                title: _('common.confirm-delete-note'),
+                body: entry.data.note,
+                buttons: [
+                    { type: 'secondary', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(switchMap((res) => (res ? this.dataService.customer.deleteCustomerNote(entry.id) : EMPTY)))
+            .subscribe(() => {
+                this.fetchHistory.next();
+                this.notificationService.success(_('common.notify-delete-success'), {
+                    entity: 'Note',
+                });
+            });
     }
 
     protected setFormValues(entity: Customer.Fragment): void {

+ 39 - 5
packages/admin-ui/src/lib/customer/src/components/customer-history/customer-history.component.html

@@ -25,7 +25,6 @@
                 </div>
             </ng-container>
             <ng-container *ngSwitchCase="type.CUSTOMER_DETAIL_UPDATED">
-
                 <div class="flex">
                     {{ 'customer.history-customer-detail-updated' | translate }}
                     <vdr-history-entry-detail>
@@ -34,10 +33,16 @@
                 </div>
             </ng-container>
             <ng-container *ngSwitchCase="type.CUSTOMER_ADDED_TO_GROUP">
-                {{ 'customer.history-customer-added-to-group' | translate: { groupName: entry.data.groupName } }}
+                {{
+                    'customer.history-customer-added-to-group'
+                        | translate: { groupName: entry.data.groupName }
+                }}
             </ng-container>
             <ng-container *ngSwitchCase="type.CUSTOMER_REMOVED_FROM_GROUP">
-                {{ 'customer.history-customer-removed-from-group' | translate: { groupName: entry.data.groupName } }}
+                {{
+                    'customer.history-customer-removed-from-group'
+                        | translate: { groupName: entry.data.groupName }
+                }}
             </ng-container>
             <ng-container *ngSwitchCase="type.CUSTOMER_ADDRESS_CREATED">
                 {{ 'customer.history-customer-address-created' | translate }}
@@ -94,8 +99,37 @@
                 </div>
             </ng-container>
             <ng-container *ngSwitchCase="type.CUSTOMER_NOTE">
-                <div class="note-text">
-                    {{ entry.data.note }}
+                <div class="flex">
+                    <div class="note-text">
+                        {{ entry.data.note }}
+                    </div>
+                    <div class="flex-spacer"></div>
+                    <vdr-dropdown>
+                        <button class="icon-button" vdrDropdownTrigger>
+                            <clr-icon shape="ellipsis-vertical"></clr-icon>
+                        </button>
+                        <vdr-dropdown-menu vdrPosition="bottom-right">
+                            <button
+                                class="button"
+                                vdrDropdownItem
+                                (click)="updateNote.emit(entry)"
+                                [disabled]="!('UpdateOrder' | hasPermission)"
+                            >
+                                <clr-icon shape="edit"></clr-icon>
+                                {{ 'common.edit' | translate }}
+                            </button>
+                            <div class="dropdown-divider"></div>
+                            <button
+                                class="button"
+                                vdrDropdownItem
+                                (click)="deleteNote.emit(entry)"
+                                [disabled]="!('UpdateOrder' | hasPermission)"
+                            >
+                                <clr-icon shape="trash" class="is-danger"></clr-icon>
+                                {{ 'common.delete' | translate }}
+                            </button>
+                        </vdr-dropdown-menu>
+                    </vdr-dropdown>
                 </div>
             </ng-container>
         </ng-container>

+ 9 - 1
packages/admin-ui/src/lib/customer/src/components/customer-history/customer-history.component.ts

@@ -1,5 +1,11 @@
 import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
-import { Customer, GetCustomerHistory, HistoryEntryType, TimelineDisplayType } from '@vendure/admin-ui/core';
+import {
+    Customer,
+    GetCustomerHistory,
+    HistoryEntry,
+    HistoryEntryType,
+    TimelineDisplayType,
+} from '@vendure/admin-ui/core';
 
 @Component({
     selector: 'vdr-customer-history',
@@ -11,6 +17,8 @@ export class CustomerHistoryComponent {
     @Input() customer: Customer.Fragment;
     @Input() history: GetCustomerHistory.Items[];
     @Output() addNote = new EventEmitter<{ note: string }>();
+    @Output() updateNote = new EventEmitter<HistoryEntry>();
+    @Output() deleteNote = new EventEmitter<HistoryEntry>();
     note = '';
     readonly type = HistoryEntryType;
 

+ 2 - 0
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.html

@@ -218,6 +218,8 @@
                 [order]="order"
                 [history]="history$ | async"
                 (addNote)="addNote($event)"
+                (updateNote)="updateNote($event)"
+                (deleteNote)="deleteNote($event)"
             ></vdr-order-history>
         </div>
         <div class="clr-col-lg-4 order-cards">

+ 58 - 4
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
 import { FormGroup } 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, HistoryEntry } from '@vendure/admin-ui/core';
 import {
     AdjustmentType,
     CustomFieldConfig,
@@ -16,9 +16,10 @@ import { DataService } from '@vendure/admin-ui/core';
 import { ServerConfigService } from '@vendure/admin-ui/core';
 import { ModalService } from '@vendure/admin-ui/core';
 import { omit } from '@vendure/common/lib/omit';
-import { Observable, of, Subject } from 'rxjs';
-import { startWith, switchMap, take } from 'rxjs/operators';
+import { EMPTY, Observable, of, Subject } from 'rxjs';
+import { map, startWith, switchMap, take } from 'rxjs/operators';
 
+import { EditNoteDialogComponent } from '../../../../core/src/shared/components/edit-note-dialog/edit-note-dialog.component';
 import { CancelOrderDialogComponent } from '../cancel-order-dialog/cancel-order-dialog.component';
 import { FulfillOrderDialogComponent } from '../fulfill-order-dialog/fulfill-order-dialog.component';
 import { RefundOrderDialogComponent } from '../refund-order-dialog/refund-order-dialog.component';
@@ -123,6 +124,7 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
                     this.notificationService.error(_('order.settle-payment-error'));
                 }
                 this.dataService.order.getOrder(this.id).single$.subscribe();
+                this.fetchHistory.next();
             }
         });
     }
@@ -204,7 +206,59 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
             })
             .pipe(switchMap((result) => this.refetchOrder(result)))
             .subscribe((result) => {
-                this.notificationService.success(_('order.add-note-success'));
+                this.notificationService.success(_('common.notify-create-success'), {
+                    entity: 'Note',
+                });
+            });
+    }
+
+    updateNote(entry: HistoryEntry) {
+        this.modalService
+            .fromComponent(EditNoteDialogComponent, {
+                closable: true,
+                locals: {
+                    displayPrivacyControls: true,
+                    note: entry.data.note,
+                    noteIsPrivate: !entry.isPublic,
+                },
+            })
+            .pipe(
+                switchMap((result) => {
+                    if (result) {
+                        return this.dataService.order.updateOrderNote({
+                            noteId: entry.id,
+                            isPublic: !result.isPrivate,
+                            note: result.note,
+                        });
+                    } else {
+                        return EMPTY;
+                    }
+                }),
+            )
+            .subscribe((result) => {
+                this.fetchHistory.next();
+                this.notificationService.success(_('common.notify-update-success'), {
+                    entity: 'Note',
+                });
+            });
+    }
+
+    deleteNote(entry: HistoryEntry) {
+        return this.modalService
+            .dialog({
+                title: _('common.confirm-delete-note'),
+                body: entry.data.note,
+                buttons: [
+                    { type: 'secondary', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(switchMap((res) => (res ? this.dataService.order.deleteOrderNote(entry.id) : EMPTY)))
+            .subscribe(() => {
+                this.fetchHistory.next();
+                this.notificationService.success(_('common.notify-delete-success'), {
+                    entity: 'Note',
+                });
             });
     }
 

+ 38 - 9
packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.html

@@ -4,7 +4,7 @@
         <div class="note-entry">
             <textarea [(ngModel)]="note" name="note" class="note"></textarea>
             <button class="btn btn-secondary" [disabled]="!note" (click)="addNoteToOrder()">
-                {{ 'order.add-note' | translate }}
+                {{ 'common.add-note' | translate }}
             </button>
         </div>
         <div class="visibility-select">
@@ -94,14 +94,43 @@
                 </vdr-history-entry-detail>
             </ng-container>
             <ng-container *ngSwitchCase="type.ORDER_NOTE">
-                <div class="note-text">
-                    <span *ngIf="entry.isPublic" class="note-visibility public">{{
-                        'common.public' | translate
-                    }}</span>
-                    <span *ngIf="!entry.isPublic" class="note-visibility private">{{
-                        'common.private' | translate
-                    }}</span>
-                    {{ entry.data.note }}
+                <div class="flex">
+                    <div class="note-text">
+                        <span *ngIf="entry.isPublic" class="note-visibility public">{{
+                            'common.public' | translate
+                        }}</span>
+                        <span *ngIf="!entry.isPublic" class="note-visibility private">{{
+                            'common.private' | translate
+                        }}</span>
+                        {{ entry.data.note }}
+                    </div>
+                    <div class="flex-spacer"></div>
+                    <vdr-dropdown>
+                        <button class="icon-button" vdrDropdownTrigger>
+                            <clr-icon shape="ellipsis-vertical"></clr-icon>
+                        </button>
+                        <vdr-dropdown-menu vdrPosition="bottom-right">
+                            <button
+                                class="button"
+                                vdrDropdownItem
+                                (click)="updateNote.emit(entry)"
+                                [disabled]="!('UpdateOrder' | hasPermission)"
+                            >
+                                <clr-icon shape="edit"></clr-icon>
+                                {{ 'common.edit' | translate }}
+                            </button>
+                            <div class="dropdown-divider"></div>
+                            <button
+                                class="button"
+                                vdrDropdownItem
+                                (click)="deleteNote.emit(entry)"
+                                [disabled]="!('UpdateOrder' | hasPermission)"
+                            >
+                                <clr-icon shape="trash" class="is-danger"></clr-icon>
+                                {{ 'common.delete' | translate }}
+                            </button>
+                        </vdr-dropdown-menu>
+                    </vdr-dropdown>
                 </div>
             </ng-container>
             <ng-container *ngSwitchCase="type.ORDER_COUPON_APPLIED">

+ 3 - 0
packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.ts

@@ -1,6 +1,7 @@
 import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
 import {
     GetOrderHistory,
+    HistoryEntry,
     HistoryEntryType,
     OrderDetail,
     OrderDetailFragment,
@@ -17,6 +18,8 @@ export class OrderHistoryComponent {
     @Input() order: OrderDetailFragment;
     @Input() history: GetOrderHistory.Items[];
     @Output() addNote = new EventEmitter<{ note: string; isPublic: boolean }>();
+    @Output() updateNote = new EventEmitter<HistoryEntry>();
+    @Output() deleteNote = new EventEmitter<HistoryEntry>();
     note = '';
     noteIsPrivate = true;
     readonly type = HistoryEntryType;

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

@@ -133,12 +133,15 @@
     "ID": "ID",
     "actions": "Actions",
     "add-new-variants": "Add {count, plural, one {1 variant} other {{count} variants}}",
+    "add-note": "Add note",
     "available-languages": "Available languages",
     "cancel": "Cancel",
     "cancel-navigation": "Cancel navigation",
     "channel": "Channel",
     "channels": "Channels",
     "code": "Code",
+    "confirm": "Confirm",
+    "confirm-delete-note": "Delete note?",
     "confirm-navigation": "Confirm navigation",
     "create": "Create",
     "created-at": "Created at",
@@ -154,6 +157,7 @@
     "done": "Done",
     "edit": "Edit",
     "edit-field": "Edit field",
+    "edit-note": "Edit note",
     "enabled": "Enabled",
     "extension-running-in-separate-window": "Extension is running in a separate window",
     "guest": "Guest",
@@ -533,7 +537,6 @@
   },
   "order": {
     "add-note": "Add note",
-    "add-note-success": "Note successfully added",
     "amount": "Amount",
     "cancel": "Cancel",
     "cancel-order": "Cancel Order",
@@ -702,4 +705,4 @@
     "job-result": "Job result",
     "job-state": "Job state"
   }
-}
+}