Browse Source

feat(admin-ui): Add product variant bulk actions (assign/delete channel, delete) (#2238)

* fix(admin-ui): Fix product variant bulk action for deletion

* feat(admin-ui): Add bulk action for removing prod-variants from channel

* feat(admin-ui): Add bulk action for assigning product variants to channels

This also refactors the fetching of variants in the dialog component.
Daniel Biegler 2 years ago
parent
commit
b25ddcd60a

+ 8 - 0
packages/admin-ui/src/lib/catalog/src/catalog.module.ts

@@ -58,6 +58,11 @@ import { ProductListComponent } from './components/product-list/product-list.com
 import { ProductOptionsEditorComponent } from './components/product-options-editor/product-options-editor.component';
 import { ProductVariantDetailComponent } from './components/product-variant-detail/product-variant-detail.component';
 import { ProductVariantListComponent } from './components/product-variant-list/product-variant-list.component';
+import {
+    assignProductVariantsToChannelBulkAction,
+    removeProductVariantsFromChannelBulkAction,
+    deleteProductVariantsBulkAction,
+} from './components/product-variant-list/product-variant-list-bulk-actions';
 import { ProductVariantQuickJumpComponent } from './components/product-variant-quick-jump/product-variant-quick-jump.component';
 import { ProductVariantsEditorComponent } from './components/product-variants-editor/product-variants-editor.component';
 import { ProductVariantsTableComponent } from './components/product-variants-table/product-variants-table.component';
@@ -127,8 +132,11 @@ export class CatalogModule {
     ) {
         bulkActionRegistryService.registerBulkAction(assignFacetValuesToProductsBulkAction);
         bulkActionRegistryService.registerBulkAction(assignProductsToChannelBulkAction);
+        bulkActionRegistryService.registerBulkAction(assignProductVariantsToChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(removeProductsFromChannelBulkAction);
+        bulkActionRegistryService.registerBulkAction(removeProductVariantsFromChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(deleteProductsBulkAction);
+        bulkActionRegistryService.registerBulkAction(deleteProductVariantsBulkAction);
 
         bulkActionRegistryService.registerBulkAction(assignFacetsToChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(removeFacetsFromChannelBulkAction);

+ 15 - 18
packages/admin-ui/src/lib/catalog/src/components/assign-products-to-channel-dialog/assign-products-to-channel-dialog.component.ts

@@ -6,10 +6,10 @@ import {
     Dialog,
     GetChannelsQuery,
     ItemOf,
+    LogicalOperator,
     NotificationService,
-    ProductVariantFragment,
 } from '@vendure/admin-ui/core';
-import { combineLatest, from, Observable } from 'rxjs';
+import { combineLatest, from, lastValueFrom, Observable } from 'rxjs';
 import { map, startWith } from 'rxjs/operators';
 
 type Channel = ItemOf<GetChannelsQuery, 'channels'>;
@@ -115,21 +115,18 @@ export class AssignProductsToChannelDialogComponent implements OnInit, Dialog<an
         this.resolveWith();
     }
 
-    private async getTopVariants(take: number): Promise<ProductVariantFragment[]> {
-        const variants: ProductVariantFragment[] = [];
-
-        for (let i = 0; i < this.productIds.length && variants.length < take; i++) {
-            const productVariants = await this.dataService.product
-                .getProduct(this.productIds[i], { take: this.isProductVariantMode ? undefined : take })
-                .mapSingle(({ product }) => {
-                    const _variants = product ? product.variantList.items : [];
-                    return _variants.filter(v =>
-                        this.isProductVariantMode ? this.productVariantIds?.includes(v.id) : true,
-                    );
-                })
-                .toPromise();
-            variants.push(...(productVariants || []));
-        }
-        return variants.slice(0, take);
+    private async getTopVariants(take: number) {
+        return (
+            await lastValueFrom(
+                this.dataService.product.getProductVariants({
+                    filterOperator: LogicalOperator.OR,
+                    filter: {
+                        productId: { in: this.productIds },
+                        id: { in: this.productVariantIds },
+                    },
+                    take,
+                }).single$,
+            )
+        ).productVariants.items;
     }
 }

+ 0 - 68
packages/admin-ui/src/lib/catalog/src/components/product-variant-list/product-list-bulk-actions.ts

@@ -1,68 +0,0 @@
-import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import {
-    BulkAction,
-    DataService,
-    DeletionResult,
-    ModalService,
-    NotificationService,
-    Permission,
-} from '@vendure/admin-ui/core';
-import { unique } from '@vendure/common/lib/unique';
-import { EMPTY } from 'rxjs';
-import { switchMap } from 'rxjs/operators';
-
-import { ProductVariantListComponent } from './product-variant-list.component';
-
-export const deleteProductVariantsBulkAction: BulkAction<any, ProductVariantListComponent> = {
-    location: 'product-list',
-    label: _('common.delete'),
-    icon: 'trash',
-    iconClass: 'is-danger',
-    requiresPermission: userPermissions =>
-        userPermissions.includes(Permission.DeleteProduct) ||
-        userPermissions.includes(Permission.DeleteCatalog),
-    onClick: ({ injector, selection, hostComponent, clearSelection }) => {
-        const modalService = injector.get(ModalService);
-        const dataService = injector.get(DataService);
-        const notificationService = injector.get(NotificationService);
-        modalService
-            .dialog({
-                title: _('catalog.confirm-bulk-delete-products'),
-                translationVars: {
-                    count: selection.length,
-                },
-                buttons: [
-                    { type: 'secondary', label: _('common.cancel') },
-                    { type: 'danger', label: _('common.delete'), returnValue: true },
-                ],
-            })
-            .pipe(
-                switchMap(response =>
-                    response
-                        ? dataService.product.deleteProducts(unique(selection.map(p => p.productId)))
-                        : EMPTY,
-                ),
-            )
-            .subscribe(result => {
-                let deleted = 0;
-                const errors: string[] = [];
-                for (const item of result.deleteProducts) {
-                    if (item.result === DeletionResult.DELETED) {
-                        deleted++;
-                    } else if (item.message) {
-                        errors.push(item.message);
-                    }
-                }
-                if (0 < deleted) {
-                    notificationService.success(_('catalog.notify-bulk-delete-products-success'), {
-                        count: deleted,
-                    });
-                }
-                if (0 < errors.length) {
-                    notificationService.error(errors.join('\n'));
-                }
-                hostComponent.refresh();
-                clearSelection();
-            });
-    },
-};

+ 120 - 0
packages/admin-ui/src/lib/catalog/src/components/product-variant-list/product-variant-list-bulk-actions.ts

@@ -0,0 +1,120 @@
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import {
+    BulkAction,
+    DataService,
+    DeletionResult,
+    GetProductVariantListQuery,
+    ItemOf,
+    ModalService,
+    NotificationService,
+    Permission,
+    createBulkRemoveFromChannelAction,
+    isMultiChannel,
+} from '@vendure/admin-ui/core';
+import { unique } from '@vendure/common/lib/unique';
+import { EMPTY } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
+
+import { ProductVariant } from 'package/core';
+import { AssignProductsToChannelDialogComponent } from '../assign-products-to-channel-dialog/assign-products-to-channel-dialog.component';
+import { ProductVariantListComponent } from './product-variant-list.component';
+
+export const assignProductVariantsToChannelBulkAction: BulkAction<
+    ItemOf<GetProductVariantListQuery, 'productVariants'>,
+    ProductVariantListComponent
+> = {
+    location: 'product-variant-list',
+    label: _('catalog.assign-to-channel'),
+    icon: 'layers',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateCatalog) ||
+        userPermissions.includes(Permission.UpdateProduct),
+    isVisible: ({ injector }) => isMultiChannel(injector.get(DataService)),
+    onClick: ({ injector, selection, clearSelection }) => {
+        const modalService = injector.get(ModalService);
+        modalService
+            .fromComponent(AssignProductsToChannelDialogComponent, {
+                size: 'lg',
+                locals: {
+                    productVariantIds: unique(selection.map(p => p.id)),
+                    currentChannelIds: [],
+                },
+            })
+            .subscribe(result => {
+                if (result) {
+                    clearSelection();
+                }
+            });
+    },
+};
+
+export const removeProductVariantsFromChannelBulkAction = createBulkRemoveFromChannelAction<
+    ItemOf<GetProductVariantListQuery, 'productVariants'>
+>({
+    location: 'product-variant-list',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateCatalog) ||
+        userPermissions.includes(Permission.UpdateProduct),
+    getItemName: item => item.name,
+    bulkRemoveFromChannel: (dataService, ids, channelId) =>
+        dataService.product
+            .removeVariantsFromChannel({
+                channelId: channelId,
+                productVariantIds: ids,
+            })
+            .pipe(map(res => res.removeProductVariantsFromChannel)),
+});
+
+export const deleteProductVariantsBulkAction: BulkAction<ProductVariant, ProductVariantListComponent> = {
+    location: 'product-variant-list',
+    label: _('common.delete'),
+    icon: 'trash',
+    iconClass: 'is-danger',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.DeleteProduct) ||
+        userPermissions.includes(Permission.DeleteCatalog),
+    onClick: ({ injector, selection, hostComponent, clearSelection }) => {
+        const modalService = injector.get(ModalService);
+        const dataService = injector.get(DataService);
+        const notificationService = injector.get(NotificationService);
+        modalService
+            .dialog({
+                title: _('common.confirm-bulk-delete'),
+                translationVars: {
+                    count: selection.length,
+                },
+                buttons: [
+                    { type: 'secondary', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(
+                switchMap(response =>
+                    response
+                        ? dataService.product.deleteProductVariants(unique(selection.map(p => p.id)))
+                        : EMPTY,
+                ),
+            )
+            .subscribe(result => {
+                let deleted = 0;
+                const errors: string[] = [];
+                for (const item of result.deleteProductVariants) {
+                    if (item.result === DeletionResult.DELETED) {
+                        deleted++;
+                    } else if (item.message) {
+                        errors.push(item.message);
+                    }
+                }
+                if (0 < deleted) {
+                    notificationService.success(_('catalog.notify-bulk-delete-products-success'), {
+                        count: deleted,
+                    });
+                }
+                if (0 < errors.length) {
+                    notificationService.error(errors.join('\n'));
+                }
+                hostComponent.refresh();
+                clearSelection();
+            });
+    },
+};

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/public_api.ts

@@ -35,7 +35,7 @@ export * from './components/product-list/product-list.graphql';
 export * from './components/product-options-editor/product-options-editor.component';
 export * from './components/product-variant-detail/product-variant-detail.component';
 export * from './components/product-variant-detail/product-variant-detail.graphql';
-export * from './components/product-variant-list/product-list-bulk-actions';
+export * from './components/product-variant-list/product-variant-list-bulk-actions';
 export * from './components/product-variant-list/product-variant-list.component';
 export * from './components/product-variant-list/product-variant-list.graphql';
 export * from './components/product-variant-quick-jump/product-variant-quick-jump.component';

File diff suppressed because it is too large
+ 8 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts


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

@@ -616,6 +616,15 @@ export const DELETE_PRODUCT_VARIANT = gql`
     }
 `;
 
+export const DELETE_PRODUCT_VARIANTS = gql`
+    mutation DeleteProductVariants($ids: [ID!]!) {
+        deleteProductVariants(ids: $ids) {
+            result
+            message
+        }
+    }
+`;
+
 export const GET_PRODUCT_VARIANT_OPTIONS = gql`
     query GetProductVariantOptions($id: ID!) {
         product(id: $id) {

+ 10 - 0
packages/admin-ui/src/lib/core/src/data/providers/product-data.service.ts

@@ -16,6 +16,7 @@ import {
     DELETE_PRODUCT,
     DELETE_PRODUCT_OPTION,
     DELETE_PRODUCT_VARIANT,
+    DELETE_PRODUCT_VARIANTS,
     DELETE_PRODUCTS,
     DELETE_TAG,
     GET_ASSET,
@@ -268,6 +269,15 @@ export class ProductDataService {
         });
     }
 
+    deleteProductVariants(ids: string[]) {
+        return this.baseDataService.mutate<
+            Codegen.DeleteProductVariantsMutation,
+            Codegen.DeleteProductVariantsMutationVariables
+        >(DELETE_PRODUCT_VARIANTS, {
+            ids,
+        });
+    }
+
     createProductOptionGroups(productOptionGroup: Codegen.CreateProductOptionGroupInput) {
         const input: Codegen.CreateProductOptionGroupMutationVariables = {
             input: productOptionGroup,

Some files were not shown because too many files changed in this diff