Browse Source

feat(admin-ui): Implement editing of ProductOptions

Michael Bromley 6 years ago
parent
commit
420793d0b5

+ 8 - 1
admin-ui/src/app/catalog/catalog.module.ts

@@ -26,6 +26,7 @@ import { ProductListComponent } from './components/product-list/product-list.com
 import { ProductSearchInputComponent } from './components/product-search-input/product-search-input.component';
 import { ProductVariantsListComponent } from './components/product-variants-list/product-variants-list.component';
 import { ProductVariantsTableComponent } from './components/product-variants-table/product-variants-table.component';
+import { UpdateProductOptionDialogComponent } from './components/update-product-option-dialog/update-product-option-dialog.component';
 import { VariantPriceDetailComponent } from './components/variant-price-detail/variant-price-detail.component';
 import { ProductDetailService } from './providers/product-detail.service';
 import { CollectionResolver } from './providers/routing/collection-resolver';
@@ -58,8 +59,14 @@ import { ProductResolver } from './providers/routing/product-resolver';
         AssetPreviewComponent,
         ProductSearchInputComponent,
         OptionValueInputComponent,
+        UpdateProductOptionDialogComponent,
+    ],
+    entryComponents: [
+        AssetPickerDialogComponent,
+        ApplyFacetDialogComponent,
+        AssetPreviewComponent,
+        UpdateProductOptionDialogComponent,
     ],
-    entryComponents: [AssetPickerDialogComponent, ApplyFacetDialogComponent, AssetPreviewComponent],
     providers: [ProductResolver, FacetResolver, CollectionResolver, ProductDetailService],
 })
 export class CatalogModule {}

+ 2 - 0
admin-ui/src/app/catalog/components/product-detail/product-detail.component.html

@@ -148,9 +148,11 @@
                         *ngIf="variantDisplayMode === 'card'"
                         [variants]="variants$ | async"
                         [facets]="facets$ | async"
+                        [optionGroups]="product.optionGroups"
                         [productVariantsFormArray]="detailForm.get('variants')"
                         [taxCategories]="taxCategories$ | async"
                         (assetChange)="variantAssetChange($event)"
+                        (updateProductOption)="updateProductOption($event)"
                         (selectionChange)="selectedVariantIds = $event"
                         (selectFacetValueClick)="selectVariantFacetValue($event)"
                     ></vdr-product-variants-list>

+ 17 - 1
admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts

@@ -19,6 +19,7 @@ import {
     TaxCategory,
     UpdateProductInput,
     UpdateProductMutation,
+    UpdateProductOptionInput,
     UpdateProductVariantInput,
     UpdateProductVariantsMutation,
 } from '../../../common/generated-types';
@@ -194,6 +195,21 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
         });
     }
 
+    updateProductOption(input: UpdateProductOptionInput) {
+        this.productDetailService.updateProductOption(input).subscribe(
+            () => {
+                this.notificationService.success(_('common.notify-update-success'), {
+                    entity: 'ProductOption',
+                });
+            },
+            err => {
+                this.notificationService.error(_('common.notify-update-error'), {
+                    entity: 'ProductOption',
+                });
+            },
+        );
+    }
+
     removeProductFacetValue(facetValueId: string) {
         const productGroup = this.getProductFormGroup();
         const currentFacetValueIds = productGroup.value.facetValueIds;
@@ -315,7 +331,7 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
                         );
                     }
 
-                    return this.productDetailService.updateProduct(languageCode, productInput, variantsInput);
+                    return this.productDetailService.updateProduct(productInput, variantsInput);
                 }),
             )
             .subscribe(

+ 10 - 2
admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.html

@@ -105,8 +105,16 @@
                 <div class="options-facets">
                     <div *ngIf="variant.options.length">
                         <div class="options">
-                            {{ 'catalog.options' | translate }}:
-                            <vdr-chip *ngFor="let option of variant.options">{{ option.name }}</vdr-chip>
+                            <vdr-chip
+                                *ngFor="let option of variant.options | sort: 'groupId'"
+                                [colorFrom]="optionGroupName(option.groupId)"
+                                [invert]="true"
+                                (iconClick)="editOption(option)"
+                                icon="pencil"
+                            >
+                                <span class="option-group-name">{{ optionGroupName(option.groupId) }}</span>
+                                {{ option.name }}
+                            </vdr-chip>
                         </div>
                     </div>
                     <div class="flex-spacer"></div>

+ 8 - 0
admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.scss

@@ -93,6 +93,14 @@
         }
     }
 
+    .option-group-name {
+        color: $color-grey-400;
+        text-transform: uppercase;
+        font-size: 10px;
+        margin-right: 3px;
+        height: 11px;
+    }
+
     .options-facets {
         display: flex;
         color: $color-grey-400;

+ 27 - 1
admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.ts

@@ -17,12 +17,16 @@ import { notNullOrUndefined } from 'shared/shared-utils';
 import {
     FacetValue,
     FacetWithValues,
+    ProductVariant,
     ProductWithVariants,
     TaxCategory,
+    UpdateProductOptionInput,
 } from '../../../common/generated-types';
 import { flattenFacetValues } from '../../../common/utilities/flatten-facet-values';
+import { ModalService } from '../../../shared/providers/modal/modal.service';
 import { AssetChange } from '../product-assets/product-assets.component';
 import { VariantFormValue } from '../product-detail/product-detail.component';
+import { UpdateProductOptionDialogComponent } from '../update-product-option-dialog/update-product-option-dialog.component';
 
 export interface VariantAssetChange extends AssetChange {
     variantId: string;
@@ -39,14 +43,16 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
     @Input() variants: ProductWithVariants.Variants[];
     @Input() taxCategories: TaxCategory[];
     @Input() facets: FacetWithValues.Fragment[];
+    @Input() optionGroups: ProductWithVariants.OptionGroups[];
     @Output() assetChange = new EventEmitter<VariantAssetChange>();
     @Output() selectionChange = new EventEmitter<string[]>();
     @Output() selectFacetValueClick = new EventEmitter<string[]>();
+    @Output() updateProductOption = new EventEmitter<UpdateProductOptionInput>();
     selectedVariantIds: string[] = [];
     private facetValues: FacetValue.Fragment[];
     private formSubscription: Subscription;
 
-    constructor(private changeDetector: ChangeDetectorRef) {}
+    constructor(private changeDetector: ChangeDetectorRef, private modalService: ModalService) {}
 
     ngOnInit() {
         this.formSubscription = this.formArray.valueChanges.subscribe(() =>
@@ -98,6 +104,11 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
         this.selectionChange.emit(this.selectedVariantIds);
     }
 
+    optionGroupName(optionGroupId: string): string | undefined {
+        const group = this.optionGroups.find(g => g.id === optionGroupId);
+        return group && group.name;
+    }
+
     pendingFacetValues(index: number) {
         if (this.facets) {
             const formFacetValueIds = this.getFacetValueIds(index);
@@ -137,6 +148,21 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
         return -1 < this.selectedVariantIds.indexOf(variantId);
     }
 
+    editOption(option: ProductVariant.Options) {
+        this.modalService
+            .fromComponent(UpdateProductOptionDialogComponent, {
+                size: 'md',
+                locals: {
+                    productOption: option,
+                },
+            })
+            .subscribe(result => {
+                if (result) {
+                    this.updateProductOption.emit(result);
+                }
+            });
+    }
+
     private getFacetValueIds(index: number): string[] {
         const formValue: VariantFormValue = this.formArray.at(index).value;
         return formValue.facetValueIds;

+ 7 - 3
admin-ui/src/app/catalog/components/product-variants-table/product-variants-table.component.html

@@ -2,7 +2,7 @@
     <vdr-dt-column></vdr-dt-column>
     <vdr-dt-column>{{ 'common.name' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'catalog.sku' | translate }}</vdr-dt-column>
-    <ng-container *ngFor="let optionGroup of optionGroups">
+    <ng-container *ngFor="let optionGroup of optionGroups | sort: 'id'">
         <vdr-dt-column>{{ optionGroup.name }}</vdr-dt-column>
     </ng-container>
     <vdr-dt-column>{{ 'catalog.price' | translate }}</vdr-dt-column>
@@ -43,8 +43,12 @@
                     />
                 </clr-input-container>
             </td>
-            <ng-container *ngFor="let option of variant.options">
-                <td class="left align-middle" [class.disabled]="!formArray.get([i, 'enabled']).value">
+            <ng-container *ngFor="let option of variant.options | sort: 'groupId'">
+                <td
+                    class="left align-middle"
+                    [class.disabled]="!formArray.get([i, 'enabled']).value"
+                    [style.color]="optionGroupName(option.groupId) | stringToColor"
+                >
                     {{ option.name }}
                 </td>
             </ng-container>

+ 5 - 0
admin-ui/src/app/catalog/components/product-variants-table/product-variants-table.component.ts

@@ -13,4 +13,9 @@ export class ProductVariantsTableComponent {
     @Input('productVariantsFormArray') formArray: FormArray;
     @Input() variants: ProductWithVariants.Variants[];
     @Input() optionGroups: ProductWithVariants.OptionGroups[];
+
+    optionGroupName(optionGroupId: string): string | undefined {
+        const group = this.optionGroups.find(g => g.id === optionGroupId);
+        return group && group.name;
+    }
 }

+ 27 - 0
admin-ui/src/app/catalog/components/update-product-option-dialog/update-product-option-dialog.component.html

@@ -0,0 +1,27 @@
+<ng-template vdrDialogTitle>{{ 'catalog.update-product-option' | translate }}</ng-template>
+
+<vdr-form-field [label]="'catalog.option-name' | translate" for="name">
+    <input
+        id="name"
+        type="text"
+        #nameInput="ngModel"
+        [(ngModel)]="name"
+        required
+        (input)="updateCode($event.target.value)"
+    />
+</vdr-form-field>
+<vdr-form-field [label]="'common.code' | translate" for="code">
+    <input id="code" type="text" #codeInput="ngModel" required [(ngModel)]="code" pattern="[a-z0-9_-]+" />
+</vdr-form-field>
+
+<ng-template vdrDialogButtons>
+    <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
+    <button
+        type="submit"
+        (click)="update()"
+        [disabled]="nameInput.invalid || codeInput.invalid || (nameInput.pristine && codeInput.pristine)"
+        class="btn btn-primary"
+    >
+        {{ 'catalog.update-product-option' | translate }}
+    </button>
+</ng-template>

+ 0 - 0
admin-ui/src/app/catalog/components/update-product-option-dialog/update-product-option-dialog.component.scss


+ 48 - 0
admin-ui/src/app/catalog/components/update-product-option-dialog/update-product-option-dialog.component.ts

@@ -0,0 +1,48 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { normalizeString } from 'shared/normalize-string';
+
+import { ProductVariant, UpdateProductOptionInput } from '../../../common/generated-types';
+import { createUpdatedTranslatable } from '../../../common/utilities/create-updated-translatable';
+import { Dialog } from '../../../shared/providers/modal/modal.service';
+
+@Component({
+    selector: 'vdr-update-product-option-dialog',
+    templateUrl: './update-product-option-dialog.component.html',
+    styleUrls: ['./update-product-option-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class UpdateProductOptionDialogComponent implements Dialog<UpdateProductOptionInput>, OnInit {
+    resolveWith: (result?: UpdateProductOptionInput) => void;
+    // Provided by caller
+    productOption: ProductVariant.Options;
+    name: string;
+    code: string;
+    codeInputTouched = false;
+
+    ngOnInit(): void {
+        this.name = this.productOption.name;
+        this.code = this.productOption.code;
+    }
+
+    update() {
+        const result = createUpdatedTranslatable({
+            translatable: this.productOption,
+            languageCode: this.productOption.languageCode,
+            updatedFields: {
+                code: this.code,
+                name: this.name,
+            },
+        });
+        this.resolveWith(result);
+    }
+
+    cancel() {
+        this.resolveWith();
+    }
+
+    updateCode(nameValue: string) {
+        if (!this.codeInputTouched) {
+            this.code = normalizeString(nameValue, '-');
+        }
+    }
+}

+ 10 - 6
admin-ui/src/app/catalog/providers/product-detail.service.ts

@@ -10,6 +10,7 @@ import {
     LanguageCode,
     UpdateProductInput,
     UpdateProductMutation,
+    UpdateProductOptionInput,
     UpdateProductVariantInput,
     UpdateProductVariantsMutation,
 } from '../../common/generated-types';
@@ -36,7 +37,10 @@ export class ProductDetailService {
             skipValue = 1;
         }
 
-        return this.facetsSubject.pipe(skip(skipValue));
+        return this.facetsSubject.pipe(
+            skip(skipValue),
+            shareReplay(1),
+        );
     }
 
     getTaxCategories() {
@@ -127,11 +131,7 @@ export class ProductDetailService {
         );
     }
 
-    updateProduct(
-        languageCode: LanguageCode,
-        productInput?: UpdateProductInput,
-        variantInput?: UpdateProductVariantInput[],
-    ) {
+    updateProduct(productInput?: UpdateProductInput, variantInput?: UpdateProductVariantInput[]) {
         const updateOperations: Array<Observable<UpdateProductMutation | UpdateProductVariantsMutation>> = [];
         if (productInput) {
             updateOperations.push(this.dataService.product.updateProduct(productInput));
@@ -141,4 +141,8 @@ export class ProductDetailService {
         }
         return forkJoin(updateOperations);
     }
+
+    updateProductOption(input: UpdateProductOptionInput) {
+        return this.dataService.product.updateProductOption(input);
+    }
 }

+ 24 - 10
admin-ui/src/app/common/generated-types.ts

@@ -1603,8 +1603,8 @@ export type Mutation = {
   updateFacetValues: Array<FacetValue>,
   /** Delete one or more FacetValues */
   deleteFacetValues: Array<DeletionResponse>,
-  updateGlobalSettings: GlobalSettings,
   importProducts?: Maybe<ImportInfo>,
+  updateGlobalSettings: GlobalSettings,
   settlePayment: Payment,
   fulfillOrder: Fulfillment,
   cancelOrder: Order,
@@ -1829,13 +1829,13 @@ export type MutationDeleteFacetValuesArgs = {
 };
 
 
-export type MutationUpdateGlobalSettingsArgs = {
-  input: UpdateGlobalSettingsInput
+export type MutationImportProductsArgs = {
+  csvFile: Scalars['Upload']
 };
 
 
-export type MutationImportProductsArgs = {
-  csvFile: Scalars['Upload']
+export type MutationUpdateGlobalSettingsArgs = {
+  input: UpdateGlobalSettingsInput
 };
 
 
@@ -2319,9 +2319,9 @@ export type ProductOption = Node & {
   id: Scalars['ID'],
   createdAt: Scalars['DateTime'],
   updatedAt: Scalars['DateTime'],
-  languageCode?: Maybe<LanguageCode>,
-  code?: Maybe<Scalars['String']>,
-  name?: Maybe<Scalars['String']>,
+  languageCode: LanguageCode,
+  code: Scalars['String'],
+  name: Scalars['String'],
   groupId: Scalars['ID'],
   translations: Array<ProductOptionTranslation>,
   customFields?: Maybe<Scalars['JSON']>,
@@ -3660,7 +3660,7 @@ export type AddNoteToOrderMutation = ({ __typename?: 'Mutation' } & { addNoteToO
 
 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'>)> });
+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' | 'groupId'> & { translations: Array<({ __typename?: 'ProductOptionTranslation' } & Pick<ProductOptionTranslation, 'id' | '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'>)> });
 
 export type ProductWithVariantsFragment = ({ __typename?: 'Product' } & Pick<Product, 'id' | 'enabled' | 'languageCode' | 'name' | 'slug' | 'description'> & { featuredAsset: Maybe<({ __typename?: 'Asset' } & AssetFragment)>, assets: Array<({ __typename?: 'Asset' } & AssetFragment)>, translations: Array<({ __typename?: 'ProductTranslation' } & Pick<ProductTranslation, 'languageCode' | 'name' | 'slug' | 'description'>)>, optionGroups: Array<({ __typename?: 'ProductOptionGroup' } & Pick<ProductOptionGroup, 'id' | 'languageCode' | 'code' | 'name'>)>, variants: Array<({ __typename?: 'ProductVariant' } & ProductVariantFragment)>, facetValues: Array<({ __typename?: 'FacetValue' } & Pick<FacetValue, 'id' | 'code' | 'name'> & { facet: ({ __typename?: 'Facet' } & Pick<Facet, 'id' | 'name'>) })> });
 
@@ -3769,6 +3769,13 @@ export type SearchProductsQueryVariables = {
 
 export type SearchProductsQuery = ({ __typename?: 'Query' } & { search: ({ __typename?: 'SearchResponse' } & Pick<SearchResponse, 'totalItems'> & { items: Array<({ __typename?: 'SearchResult' } & Pick<SearchResult, 'enabled' | 'productId' | 'productName' | 'productPreview' | 'productVariantId' | 'productVariantName' | 'productVariantPreview' | 'sku'>)>, facetValues: Array<({ __typename?: 'FacetValueResult' } & Pick<FacetValueResult, 'count'> & { facetValue: ({ __typename?: 'FacetValue' } & Pick<FacetValue, 'id' | 'name'> & { facet: ({ __typename?: 'Facet' } & Pick<Facet, 'id' | 'name'>) }) })> }) });
 
+export type UpdateProductOptionMutationVariables = {
+  input: UpdateProductOptionInput
+};
+
+
+export type UpdateProductOptionMutation = ({ __typename?: 'Mutation' } & { updateProductOption: ({ __typename?: 'ProductOption' } & Pick<ProductOption, 'id' | 'code' | 'name'>) });
+
 export type ConfigurableOperationFragment = ({ __typename?: 'ConfigurableOperation' } & Pick<ConfigurableOperation, 'code' | 'description'> & { args: Array<({ __typename?: 'ConfigArg' } & Pick<ConfigArg, 'name' | 'type' | 'value'>)> });
 
 export type PromotionFragment = ({ __typename?: 'Promotion' } & Pick<Promotion, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'enabled'> & { conditions: Array<({ __typename?: 'ConfigurableOperation' } & ConfigurableOperationFragment)>, actions: Array<({ __typename?: 'ConfigurableOperation' } & ConfigurableOperationFragment)> });
@@ -4488,11 +4495,12 @@ export namespace ProductVariant {
   export type TaxRateApplied = ProductVariantFragment['taxRateApplied'];
   export type TaxCategory = ProductVariantFragment['taxCategory'];
   export type Options = (NonNullable<ProductVariantFragment['options'][0]>);
+  export type Translations = (NonNullable<(NonNullable<ProductVariantFragment['options'][0]>)['translations'][0]>);
   export type FacetValues = (NonNullable<ProductVariantFragment['facetValues'][0]>);
   export type Facet = (NonNullable<ProductVariantFragment['facetValues'][0]>)['facet'];
   export type FeaturedAsset = AssetFragment;
   export type Assets = AssetFragment;
-  export type Translations = (NonNullable<ProductVariantFragment['translations'][0]>);
+  export type _Translations = (NonNullable<ProductVariantFragment['translations'][0]>);
 }
 
 export namespace ProductWithVariants {
@@ -4609,6 +4617,12 @@ export namespace SearchProducts {
   export type Facet = (NonNullable<SearchProductsQuery['search']['facetValues'][0]>)['facetValue']['facet'];
 }
 
+export namespace UpdateProductOption {
+  export type Variables = UpdateProductOptionMutationVariables;
+  export type Mutation = UpdateProductOptionMutation;
+  export type UpdateProductOption = UpdateProductOptionMutation['updateProductOption'];
+}
+
 export namespace ConfigurableOperation {
   export type Fragment = ConfigurableOperationFragment;
   export type Args = (NonNullable<ConfigurableOperationFragment['args'][0]>);

+ 16 - 0
admin-ui/src/app/data/definitions/product-definitions.ts

@@ -40,6 +40,12 @@ export const PRODUCT_VARIANT_FRAGMENT = gql`
             code
             languageCode
             name
+            groupId
+            translations {
+                id
+                languageCode
+                name
+            }
         }
         facetValues {
             id
@@ -309,3 +315,13 @@ export const SEARCH_PRODUCTS = gql`
         }
     }
 `;
+
+export const UPDATE_PRODUCT_OPTION = gql`
+    mutation UpdateProductOption($input: UpdateProductOptionInput!) {
+        updateProductOption(input: $input) {
+            id
+            code
+            name
+        }
+    }
+`;

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

@@ -20,6 +20,8 @@ import {
     SortOrder,
     UpdateProduct,
     UpdateProductInput,
+    UpdateProductOption,
+    UpdateProductOptionInput,
     UpdateProductVariantInput,
     UpdateProductVariants,
 } from '../../common/generated-types';
@@ -38,6 +40,7 @@ import {
     REMOVE_OPTION_GROUP_FROM_PRODUCT,
     SEARCH_PRODUCTS,
     UPDATE_PRODUCT,
+    UPDATE_PRODUCT_OPTION,
     UPDATE_PRODUCT_VARIANTS,
 } from '../definitions/product-definitions';
 import { REINDEX } from '../definitions/settings-definitions';
@@ -179,6 +182,15 @@ export class ProductDataService {
         >(REMOVE_OPTION_GROUP_FROM_PRODUCT, variables);
     }
 
+    updateProductOption(input: UpdateProductOptionInput) {
+        return this.baseDataService.mutate<UpdateProductOption.Mutation, UpdateProductOption.Variables>(
+            UPDATE_PRODUCT_OPTION,
+            {
+                input: pick(input, ['id', 'code', 'translations']),
+            },
+        );
+    }
+
     getProductOptionGroups(filterTerm?: string) {
         return this.baseDataService.query<GetProductOptionGroups.Query, GetProductOptionGroups.Variables>(
             GET_PRODUCT_OPTION_GROUPS,

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

@@ -52,8 +52,8 @@
     "notify-create-assets-success": "Created {count, plural, one {new Asset} other {{count} new Assets}}",
     "open-asset-source": "Open asset source",
     "option": "Option",
+    "option-name": "Option name",
     "option-values": "Option values",
-    "options": "Options",
     "original-asset-size": "Source size",
     "preview": "Preview",
     "preview-size": "Preview size",
@@ -83,6 +83,7 @@
     "tax-category": "Tax category",
     "taxes": "Taxes",
     "track-inventory": "Track inventory",
+    "update-product-option": "Update product option",
     "upload-assets": "Upload assets",
     "values": "Values",
     "variant": "Variant",
@@ -513,4 +514,4 @@
     "update": "Update",
     "zone": "Zone"
   }
-}
+}