Browse Source

refactor(admin-ui): Extract product selector component

Michael Bromley 5 years ago
parent
commit
3bb4eb8af0

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

@@ -5492,6 +5492,30 @@ export type SearchProductsQuery = (
   ) }
 );
 
+export type ProductSelectorSearchQueryVariables = {
+  term: Scalars['String'];
+  take: Scalars['Int'];
+};
+
+
+export type ProductSelectorSearchQuery = (
+  { __typename?: 'Query' }
+  & { search: (
+    { __typename?: 'SearchResponse' }
+    & { items: Array<(
+      { __typename?: 'SearchResult' }
+      & Pick<SearchResult, 'productVariantId' | 'productVariantName' | 'productPreview' | 'sku'>
+      & { price: { __typename?: 'PriceRange' } | (
+        { __typename?: 'SinglePrice' }
+        & Pick<SinglePrice, 'value'>
+      ), priceWithTax: { __typename?: 'PriceRange' } | (
+        { __typename?: 'SinglePrice' }
+        & Pick<SinglePrice, 'value'>
+      ) }
+    )> }
+  ) }
+);
+
 export type UpdateProductOptionMutationVariables = {
   input: UpdateProductOptionInput;
 };
@@ -6684,30 +6708,6 @@ export type ReindexMutation = (
   ) }
 );
 
-export type SearchForTestOrderQueryVariables = {
-  term: Scalars['String'];
-  take: Scalars['Int'];
-};
-
-
-export type SearchForTestOrderQuery = (
-  { __typename?: 'Query' }
-  & { search: (
-    { __typename?: 'SearchResponse' }
-    & { items: Array<(
-      { __typename?: 'SearchResult' }
-      & Pick<SearchResult, 'productVariantId' | 'productVariantName' | 'productPreview' | 'sku'>
-      & { price: { __typename?: 'PriceRange' } | (
-        { __typename?: 'SinglePrice' }
-        & Pick<SinglePrice, 'value'>
-      ), priceWithTax: { __typename?: 'PriceRange' } | (
-        { __typename?: 'SinglePrice' }
-        & Pick<SinglePrice, 'value'>
-      ) }
-    )> }
-  ) }
-);
-
 export type ConfigurableOperationFragment = (
   { __typename?: 'ConfigurableOperation' }
   & Pick<ConfigurableOperation, 'code'>
@@ -7576,6 +7576,17 @@ export namespace SearchProducts {
   export type Facet = (NonNullable<SearchProductsQuery['search']['facetValues'][0]>)['facetValue']['facet'];
 }
 
+export namespace ProductSelectorSearch {
+  export type Variables = ProductSelectorSearchQueryVariables;
+  export type Query = ProductSelectorSearchQuery;
+  export type Search = ProductSelectorSearchQuery['search'];
+  export type Items = (NonNullable<ProductSelectorSearchQuery['search']['items'][0]>);
+  export type Price = (NonNullable<ProductSelectorSearchQuery['search']['items'][0]>)['price'];
+  export type SinglePriceInlineFragment = (DiscriminateUnion<RequireField<(NonNullable<ProductSelectorSearchQuery['search']['items'][0]>)['price'], '__typename'>, { __typename: 'SinglePrice' }>);
+  export type PriceWithTax = (NonNullable<ProductSelectorSearchQuery['search']['items'][0]>)['priceWithTax'];
+  export type _SinglePriceInlineFragment = (DiscriminateUnion<RequireField<(NonNullable<ProductSelectorSearchQuery['search']['items'][0]>)['priceWithTax'], '__typename'>, { __typename: 'SinglePrice' }>);
+}
+
 export namespace UpdateProductOption {
   export type Variables = UpdateProductOptionMutationVariables;
   export type Mutation = UpdateProductOptionMutation;
@@ -8002,17 +8013,6 @@ export namespace Reindex {
   export type Reindex = JobInfoFragment;
 }
 
-export namespace SearchForTestOrder {
-  export type Variables = SearchForTestOrderQueryVariables;
-  export type Query = SearchForTestOrderQuery;
-  export type Search = SearchForTestOrderQuery['search'];
-  export type Items = (NonNullable<SearchForTestOrderQuery['search']['items'][0]>);
-  export type Price = (NonNullable<SearchForTestOrderQuery['search']['items'][0]>)['price'];
-  export type SinglePriceInlineFragment = (DiscriminateUnion<RequireField<(NonNullable<SearchForTestOrderQuery['search']['items'][0]>)['price'], '__typename'>, { __typename: 'SinglePrice' }>);
-  export type PriceWithTax = (NonNullable<SearchForTestOrderQuery['search']['items'][0]>)['priceWithTax'];
-  export type _SinglePriceInlineFragment = (DiscriminateUnion<RequireField<(NonNullable<SearchForTestOrderQuery['search']['items'][0]>)['priceWithTax'], '__typename'>, { __typename: 'SinglePrice' }>);
-}
-
 export namespace ConfigurableOperation {
   export type Fragment = ConfigurableOperationFragment;
   export type Args = (NonNullable<ConfigurableOperationFragment['args'][0]>);

+ 23 - 0
packages/admin-ui/src/lib/core/src/data/definitions/product-definitions.ts

@@ -442,6 +442,29 @@ export const SEARCH_PRODUCTS = gql`
     }
 `;
 
+export const PRODUCT_SELECTOR_SEARCH = gql`
+    query ProductSelectorSearch($term: String!, $take: Int!) {
+        search(input: { groupByProduct: false, term: $term, take: $take }) {
+            items {
+                productVariantId
+                productVariantName
+                productPreview
+                price {
+                    ... on SinglePrice {
+                        value
+                    }
+                }
+                priceWithTax {
+                    ... on SinglePrice {
+                        value
+                    }
+                }
+                sku
+            }
+        }
+    }
+`;
+
 export const UPDATE_PRODUCT_OPTION = gql`
     mutation UpdateProductOption($input: UpdateProductOptionInput!) {
         updateProductOption(input: $input) {

+ 0 - 23
packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts

@@ -661,26 +661,3 @@ export const REINDEX = gql`
     }
     ${JOB_INFO_FRAGMENT}
 `;
-
-export const SEARCH_FOR_TEST_ORDER = gql`
-    query SearchForTestOrder($term: String!, $take: Int!) {
-        search(input: { groupByProduct: false, term: $term, take: $take }) {
-            items {
-                productVariantId
-                productVariantName
-                productPreview
-                price {
-                    ... on SinglePrice {
-                        value
-                    }
-                }
-                priceWithTax {
-                    ... on SinglePrice {
-                        value
-                    }
-                }
-                sku
-            }
-        }
-    }
-`;

+ 13 - 1
packages/admin-ui/src/lib/core/src/data/providers/product-data.service.ts

@@ -23,6 +23,7 @@ import {
     GetProductOptionGroups,
     GetProductVariantOptions,
     GetProductWithVariants,
+    ProductSelectorSearch,
     Reindex,
     RemoveOptionGroupFromProduct,
     RemoveProductsFromChannel,
@@ -56,6 +57,7 @@ import {
     GET_PRODUCT_OPTION_GROUPS,
     GET_PRODUCT_VARIANT_OPTIONS,
     GET_PRODUCT_WITH_VARIANTS,
+    PRODUCT_SELECTOR_SEARCH,
     REMOVE_OPTION_GROUP_FROM_PRODUCT,
     REMOVE_PRODUCTS_FROM_CHANNEL,
     SEARCH_PRODUCTS,
@@ -82,6 +84,16 @@ export class ProductDataService {
         });
     }
 
+    productSelectorSearch(term: string, take: number) {
+        return this.baseDataService.query<ProductSelectorSearch.Query, ProductSelectorSearch.Variables>(
+            PRODUCT_SELECTOR_SEARCH,
+            {
+                take,
+                term,
+            },
+        );
+    }
+
     reindex() {
         return this.baseDataService.mutate<Reindex.Mutation>(REINDEX);
     }
@@ -274,7 +286,7 @@ export class ProductDataService {
 
     createAssets(files: File[]) {
         return this.baseDataService.mutate<CreateAssets.Mutation, CreateAssets.Variables>(CREATE_ASSETS, {
-            input: files.map((file) => ({ file })),
+            input: files.map(file => ({ file })),
         });
     }
 

+ 0 - 12
packages/admin-ui/src/lib/core/src/data/providers/settings-data.service.ts

@@ -40,7 +40,6 @@ import {
     JobListOptions,
     JobState,
     RemoveMembersFromZone,
-    SearchForTestOrder,
     UpdateChannel,
     UpdateChannelInput,
     UpdateCountry,
@@ -87,7 +86,6 @@ import {
     GET_TAX_RATE_LIST,
     GET_ZONES,
     REMOVE_MEMBERS_FROM_ZONE,
-    SEARCH_FOR_TEST_ORDER,
     UPDATE_CHANNEL,
     UPDATE_COUNTRY,
     UPDATE_GLOBAL_SETTINGS,
@@ -370,14 +368,4 @@ export class SettingsDataService {
             },
         });
     }
-
-    searchForTestOrder(term: string, take: number) {
-        return this.baseDataService.query<SearchForTestOrder.Query, SearchForTestOrder.Variables>(
-            SEARCH_FOR_TEST_ORDER,
-            {
-                take,
-                term,
-            },
-        );
-    }
 }

+ 12 - 0
packages/admin-ui/src/lib/core/src/shared/components/product-selector/product-selector.component.html

@@ -0,0 +1,12 @@
+<ng-select
+    #autoComplete
+    [items]="searchResults$ | async"
+    bindLabel="productVariantName"
+    [addTag]="false"
+    [multiple]="false"
+    [hideSelected]="true"
+    [loading]="searchLoading"
+    [typeahead]="searchInput$"
+    [placeholder]="'settings.search-by-product-name-or-sku' | translate"
+    (change)="selectResult($event)"
+></ng-select>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/components/product-selector/product-selector.component.scss


+ 55 - 0
packages/admin-ui/src/lib/core/src/shared/components/product-selector/product-selector.component.ts

@@ -0,0 +1,55 @@
+import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
+import { NgSelectComponent } from '@ng-select/ng-select';
+import { concat, merge, Observable, of, Subject } from 'rxjs';
+import { debounceTime, distinctUntilChanged, mapTo, switchMap, tap, throttleTime } from 'rxjs/operators';
+
+import { ProductSelectorSearch } from '../../../common/generated-types';
+import { DataService } from '../../../data/providers/data.service';
+
+@Component({
+    selector: 'vdr-product-selector',
+    templateUrl: './product-selector.component.html',
+    styleUrls: ['./product-selector.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ProductSelectorComponent implements OnInit {
+    searchInput$ = new Subject<string>();
+    searchLoading = false;
+    searchResults$: Observable<ProductSelectorSearch.Items[]>;
+    @Output() productSelected = new EventEmitter<ProductSelectorSearch.Items>();
+
+    @ViewChild('autoComplete', { static: true })
+    private ngSelect: NgSelectComponent;
+    constructor(private dataService: DataService) {}
+
+    ngOnInit(): void {
+        this.initSearchResults();
+    }
+
+    private initSearchResults() {
+        const searchItems$ = this.searchInput$.pipe(
+            debounceTime(200),
+            distinctUntilChanged(),
+            tap(() => (this.searchLoading = true)),
+            switchMap(term => {
+                if (!term) {
+                    return of([]);
+                }
+                return this.dataService.product
+                    .productSelectorSearch(term, 10)
+                    .mapSingle(result => result.search.items);
+            }),
+            tap(() => (this.searchLoading = false)),
+        );
+
+        const clear$ = this.productSelected.pipe(mapTo([]));
+        this.searchResults$ = concat(of([]), merge(searchItems$, clear$));
+    }
+
+    selectResult(product?: ProductSelectorSearch.Items) {
+        if (product) {
+            this.productSelected.emit(product);
+            this.ngSelect.clearModel();
+        }
+    }
+}

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

@@ -63,6 +63,7 @@ import { ModalDialogComponent } from './components/modal-dialog/modal-dialog.com
 import { ObjectTreeComponent } from './components/object-tree/object-tree.component';
 import { OrderStateLabelComponent } from './components/order-state-label/order-state-label.component';
 import { PaginationControlsComponent } from './components/pagination-controls/pagination-controls.component';
+import { ProductSelectorComponent } from './components/product-selector/product-selector.component';
 import { ExternalImageDialogComponent } from './components/rich-text-editor/external-image-dialog/external-image-dialog.component';
 import { LinkDialogComponent } from './components/rich-text-editor/link-dialog/link-dialog.component';
 import { RichTextEditorComponent } from './components/rich-text-editor/rich-text-editor.component';
@@ -182,6 +183,7 @@ const DECLARATIONS = [
     HistoryEntryDetailComponent,
     EditNoteDialogComponent,
     OrderStateI18nTokenPipe,
+    ProductSelectorComponent,
 ];
 
 const DYNAMIC_FORM_INPUTS = [

+ 1 - 12
packages/admin-ui/src/lib/settings/src/components/test-order-builder/test-order-builder.component.html

@@ -55,17 +55,6 @@
         </div>
     </ng-template>
     <div class="card-block">
-        <ng-select
-            #autoComplete
-            [items]="searchResults$ | async"
-            bindLabel="productVariantName"
-            [addTag]="false"
-            [multiple]="false"
-            [hideSelected]="true"
-            [loading]="searchLoading"
-            [typeahead]="searchInput$"
-            [placeholder]="'settings.search-by-product-name-or-sku' | translate"
-            (change)="selectResult($event)"
-        ></ng-select>
+        <vdr-product-selector (productSelected)="selectResult($event)"> </vdr-product-selector>
     </div>
 </div>

+ 13 - 41
packages/admin-ui/src/lib/settings/src/components/test-order-builder/test-order-builder.component.ts

@@ -1,8 +1,10 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
-import { NgSelectComponent } from '@ng-select/ng-select';
-import { CurrencyCode, DataService, LocalStorageService, SearchForTestOrder } from '@vendure/admin-ui/core';
-import { concat, merge, Observable, of, Subject } from 'rxjs';
-import { debounceTime, distinctUntilChanged, mapTo, switchMap, tap } from 'rxjs/operators';
+import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output } from '@angular/core';
+import {
+    CurrencyCode,
+    DataService,
+    LocalStorageService,
+    ProductSelectorSearch,
+} from '@vendure/admin-ui/core';
 
 export interface TestOrderLine {
     id: string;
@@ -21,19 +23,12 @@ export interface TestOrderLine {
 })
 export class TestOrderBuilderComponent implements OnInit {
     @Output() orderLinesChange = new EventEmitter<TestOrderLine[]>();
-
     lines: TestOrderLine[] = [];
     currencyCode: CurrencyCode;
-    searchInput$ = new Subject<string>();
-    resultSelected$ = new Subject<void>();
-    searchLoading = false;
-    searchResults$: Observable<SearchForTestOrder.Items[]>;
-    @ViewChild('autoComplete', { static: true })
-    private ngSelect: NgSelectComponent;
-
     get subTotal(): number {
         return this.lines.reduce((sum, l) => sum + l.unitPriceWithTax * l.quantity, 0);
     }
+
     constructor(private dataService: DataService, private localStorageService: LocalStorageService) {}
 
     ngOnInit() {
@@ -41,22 +36,19 @@ export class TestOrderBuilderComponent implements OnInit {
         if (this.lines) {
             this.orderLinesChange.emit(this.lines);
         }
-        this.initSearchResults();
-        this.dataService.settings.getActiveChannel('cache-first').single$.subscribe((result) => {
+        this.dataService.settings.getActiveChannel('cache-first').single$.subscribe(result => {
             this.currencyCode = result.activeChannel.currencyCode;
         });
     }
 
-    selectResult(result: SearchForTestOrder.Items) {
+    selectResult(result: ProductSelectorSearch.Items) {
         if (result) {
-            this.resultSelected$.next();
-            this.ngSelect.clearModel();
             this.addToLines(result);
         }
     }
 
-    private addToLines(result: SearchForTestOrder.Items) {
-        if (!this.lines.find((l) => l.id === result.productVariantId)) {
+    private addToLines(result: ProductSelectorSearch.Items) {
+        if (!this.lines.find(l => l.id === result.productVariantId)) {
             this.lines.push({
                 id: result.productVariantId,
                 name: result.productVariantName,
@@ -77,31 +69,11 @@ export class TestOrderBuilderComponent implements OnInit {
     }
 
     removeLine(line: TestOrderLine) {
-        this.lines = this.lines.filter((l) => l.id !== line.id);
+        this.lines = this.lines.filter(l => l.id !== line.id);
         this.persistToLocalStorage();
         this.orderLinesChange.emit(this.lines);
     }
 
-    private initSearchResults() {
-        const searchItems$ = this.searchInput$.pipe(
-            debounceTime(200),
-            distinctUntilChanged(),
-            tap(() => (this.searchLoading = true)),
-            switchMap((term) => {
-                if (!term) {
-                    return of([]);
-                }
-                return this.dataService.settings
-                    .searchForTestOrder(term, 10)
-                    .mapSingle((result) => result.search.items);
-            }),
-            tap(() => (this.searchLoading = false)),
-        );
-
-        const clear$ = this.resultSelected$.pipe(mapTo([]));
-        this.searchResults$ = concat(of([]), merge(searchItems$, clear$));
-    }
-
     private persistToLocalStorage() {
         this.localStorageService.setForCurrentLocation('shippingTestOrder', this.lines);
     }