Browse Source

feat(admin-ui): Add ability to remove option group from product

Relates to #1134
Michael Bromley 3 years ago
parent
commit
6a62e47e6f

+ 4 - 2
packages/admin-ui/src/lib/catalog/src/components/product-variants-editor/product-variants-editor.component.html

@@ -3,7 +3,7 @@
         <button
             class="btn btn-primary"
             (click)="save()"
-            [disabled]="!formValueChanged || getVariantsToAdd().length === 0"
+            [disabled]="(!formValueChanged && !optionsChanged) || getVariantsToAdd().length === 0"
         >
             {{ 'common.add-new-variants' | translate: { count: getVariantsToAdd().length } }}
         </button>
@@ -27,7 +27,9 @@
         ></vdr-option-value-input>
     </div>
     <div>
-        <button *ngIf="group.isNew" class="btn btn-icon btn-danger-outline mt5" (click)="removeOptionGroup(group)">
+        <button
+            [disabled]="group.locked"
+            class="btn btn-icon btn-danger-outline mt5" (click)="removeOptionGroup(group)">
             <clr-icon shape="trash"></clr-icon>
         </button>
     </div>

+ 58 - 9
packages/admin-ui/src/lib/catalog/src/components/product-variants-editor/product-variants-editor.component.ts

@@ -20,7 +20,7 @@ import { pick } from '@vendure/common/lib/pick';
 import { generateAllCombinations, notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { unique } from '@vendure/common/lib/unique';
 import { EMPTY, forkJoin, Observable, of } from 'rxjs';
-import { filter, map, mergeMap, switchMap } from 'rxjs/operators';
+import { defaultIfEmpty, filter, map, mergeMap, switchMap } from 'rxjs/operators';
 
 import { ProductDetailService } from '../../providers/product-detail/product-detail.service';
 import { ConfirmVariantDeletionDialogComponent } from '../confirm-variant-deletion-dialog/confirm-variant-deletion-dialog.component';
@@ -46,6 +46,7 @@ interface OptionGroupUiModel {
     id?: string;
     isNew: boolean;
     name: string;
+    locked: boolean;
     values: Array<{
         id?: string;
         name: string;
@@ -61,6 +62,7 @@ interface OptionGroupUiModel {
 })
 export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
     formValueChanged = false;
+    optionsChanged = false;
     generatedVariants: GeneratedVariant[] = [];
     optionGroups: OptionGroupUiModel[];
     product: GetProductVariantOptions.Product;
@@ -106,19 +108,59 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
     addOptionGroup() {
         this.optionGroups.push({
             isNew: true,
+            locked: false,
             name: '',
             values: [],
         });
+        this.optionsChanged = true;
     }
 
     removeOptionGroup(optionGroup: OptionGroupUiModel) {
-        this.optionGroups = this.optionGroups.filter(og => og !== optionGroup);
-        this.generateVariants();
+        const id = optionGroup.id;
+        if (optionGroup.isNew) {
+            this.optionGroups = this.optionGroups.filter(og => og !== optionGroup);
+            this.generateVariants();
+            this.optionsChanged = true;
+        } else if (id) {
+            this.modalService
+                .dialog({
+                    title: _('catalog.confirm-delete-product-option-group'),
+                    translationVars: { name: optionGroup.name },
+                    buttons: [
+                        { type: 'secondary', label: _('common.cancel') },
+                        { type: 'danger', label: _('common.delete'), returnValue: true },
+                    ],
+                })
+                .pipe(
+                    switchMap(val => {
+                        if (val) {
+                            return this.dataService.product.removeOptionGroupFromProduct({
+                                optionGroupId: id,
+                                productId: this.product.id,
+                            });
+                        } else {
+                            return EMPTY;
+                        }
+                    }),
+                )
+                .subscribe(({ removeOptionGroupFromProduct }) => {
+                    if (removeOptionGroupFromProduct.__typename === 'Product') {
+                        this.notificationService.success(_('common.notify-delete-success'), {
+                            entity: 'ProductOptionGroup',
+                        });
+                        this.initOptionsAndVariants();
+                        this.optionsChanged = true;
+                    } else if (removeOptionGroupFromProduct.__typename === 'ProductOptionInUseError') {
+                        this.notificationService.error(removeOptionGroupFromProduct.message ?? '');
+                    }
+                });
+        }
     }
 
     addOption(groupId: string, optionName: string) {
         this.optionGroups.find(g => g.id === groupId)?.values.push({ name: optionName, locked: false });
         this.generateVariants();
+        this.optionsChanged = true;
     }
 
     removeOption(groupId: string, { id, name }: { id?: string; name: string }) {
@@ -153,6 +195,7 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
                             });
                             optionGroup.values = optionGroup.values.filter(v => v.id !== id);
                             this.generateVariants();
+                            this.optionsChanged = true;
                         } else {
                             this.notificationService.error(deleteProductOption.message ?? '');
                         }
@@ -276,13 +319,17 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
                         count: variants.length,
                     });
                     this.initOptionsAndVariants();
+                    this.optionsChanged = false;
                 },
             });
     }
 
     private checkUniqueSkus() {
         const withDuplicateSkus = this.generatedVariants.filter((variant, index) => {
-            return this.generatedVariants.find(gv => gv.sku.trim() === variant.sku.trim() && gv !== variant);
+            return (
+                variant.enabled &&
+                this.generatedVariants.find(gv => gv.sku.trim() === variant.sku.trim() && gv !== variant)
+            );
         });
         if (withDuplicateSkus.length) {
             return this.modalService
@@ -385,7 +432,7 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
                     .mapSingle(data => data.productOptionGroup)
                     .pipe(filter(notNullOrUndefined)),
             ),
-        );
+        ).pipe(defaultIfEmpty([] as ProductOptionGroupWithOptionsFragment[]));
     }
 
     private createNewProductVariants(groups: ProductOptionGroupWithOptionsFragment[]) {
@@ -442,18 +489,20 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
             .mapSingle(({ product }) => product!)
             .subscribe(p => {
                 this.product = p;
+                const allUsedOptionIds = p.variants.map(v => v.options.map(option => option.id)).flat();
+                const allUsedOptionGroupIds = p.variants
+                    .map(v => v.options.map(option => option.groupId))
+                    .flat();
                 this.optionGroups = p.optionGroups.map(og => {
                     return {
                         id: og.id,
                         isNew: false,
                         name: og.name,
+                        locked: allUsedOptionGroupIds.includes(og.id),
                         values: og.options.map(o => ({
                             id: o.id,
                             name: o.name,
-                            locked: p.variants
-                                .map(v => v.options.map(option => option.id))
-                                .flat()
-                                .includes(o.id),
+                            locked: allUsedOptionIds.includes(o.id),
                         })),
                     };
                 });

+ 1 - 0
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -78,6 +78,7 @@
     "confirm-delete-facet-value": "Delete facet value?",
     "confirm-delete-product": "Delete product?",
     "confirm-delete-product-option": "Delete product option \"{name}\"?",
+    "confirm-delete-product-option-group": "Delete product option group \"{name}\"?",
     "confirm-delete-product-variant": "Delete product variant \"{name}\"?",
     "confirm-delete-promotion": "Delete promotion?",
     "confirm-delete-shipping-method": "Delete shipping method?",