Browse Source

feat(admin-ui): Update product option editing screens

Michael Bromley 2 years ago
parent
commit
f01a9ce747
31 changed files with 331 additions and 257 deletions
  1. 4 4
      packages/admin-ui/i18n-coverage.json
  2. 4 4
      packages/admin-ui/src/lib/catalog/src/catalog.module.ts
  3. 25 2
      packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html
  4. 1 1
      packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.scss
  5. 120 109
      packages/admin-ui/src/lib/catalog/src/components/product-options-editor/product-options-editor.component.html
  6. 5 5
      packages/admin-ui/src/lib/catalog/src/components/product-options-editor/product-options-editor.component.ts
  7. 8 4
      packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.component.html
  8. 5 0
      packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.component.scss
  9. 15 34
      packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.component.ts
  10. 2 1
      packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.graphql.ts
  11. 23 9
      packages/admin-ui/src/lib/catalog/src/components/product-variants-editor/product-variants-editor.component.html
  12. 39 2
      packages/admin-ui/src/lib/catalog/src/components/product-variants-editor/product-variants-editor.component.ts
  13. 8 1
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  14. 2 2
      packages/admin-ui/src/lib/core/src/data/definitions/product-definitions.ts
  15. 1 0
      packages/admin-ui/src/lib/core/src/data/providers/product-data.service.ts
  16. 3 0
      packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts
  17. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/cs.json
  18. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  19. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  20. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  21. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/fr.json
  22. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/it.json
  23. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  24. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  25. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json
  26. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/ru.json
  27. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/uk.json
  28. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  29. 4 6
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json
  30. 13 1
      packages/admin-ui/src/lib/static/styles/global/_buttons.scss
  31. 1 0
      packages/admin-ui/src/lib/static/styles/global/_forms.scss

+ 4 - 4
packages/admin-ui/i18n-coverage.json

@@ -1,6 +1,6 @@
 {
-  "generatedOn": "2023-05-26T12:46:15.841Z",
-  "lastCommit": "609bc90dfa4226a52242698e9755050d2fd8e705",
+  "generatedOn": "2023-05-29T10:50:35.826Z",
+  "lastCommit": "e6c8f0ca21f187e49a4dbc7cc59b6522dc19865f",
   "translationStatus": {
     "cs": {
       "tokenCount": 713,
@@ -14,8 +14,8 @@
     },
     "en": {
       "tokenCount": 713,
-      "translatedCount": 690,
-      "percentage": 97
+      "translatedCount": 709,
+      "percentage": 99
     },
     "es": {
       "tokenCount": 713,

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

@@ -8,8 +8,8 @@ import {
     detailComponentWithResolver,
     GetFacetDetailDocument,
     GetProductDetailDocument,
+    GetProductVariantDetailDocument,
     PageService,
-    ProductVariantDetailQueryDocument,
     SharedModule,
 } from '@vendure/admin-ui/core';
 
@@ -35,6 +35,8 @@ import { CollectionListComponent } from './components/collection-list/collection
 import { CollectionTreeNodeComponent } from './components/collection-tree/collection-tree-node.component';
 import { CollectionTreeComponent } from './components/collection-tree/collection-tree.component';
 import { ConfirmVariantDeletionDialogComponent } from './components/confirm-variant-deletion-dialog/confirm-variant-deletion-dialog.component';
+import { CreateProductOptionGroupDialogComponent } from './components/create-product-option-group-dialog/create-product-option-group-dialog.component';
+import { CreateProductVariantDialogComponent } from './components/create-product-variant-dialog/create-product-variant-dialog.component';
 import { FacetDetailComponent } from './components/facet-detail/facet-detail.component';
 import {
     assignFacetsToChannelBulkAction,
@@ -60,8 +62,6 @@ import { ProductVariantsEditorComponent } from './components/product-variants-ed
 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 { CreateProductVariantDialogComponent } from './components/create-product-variant-dialog/create-product-variant-dialog.component';
-import { CreateProductOptionGroupDialogComponent } from './components/create-product-option-group-dialog/create-product-option-group-dialog.component';
 
 const CATALOG_COMPONENTS = [
     ProductListComponent,
@@ -166,7 +166,7 @@ export class CatalogModule {
             route: '',
             component: detailComponentWithResolver({
                 component: ProductVariantDetailComponent,
-                query: ProductVariantDetailQueryDocument,
+                query: GetProductVariantDetailDocument,
                 entityKey: 'productVariant',
                 getBreadcrumbs: entity => [
                     {

+ 25 - 2
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html

@@ -37,7 +37,8 @@
 <form class="form" [formGroup]="detailForm">
     <vdr-page-detail-layout>
         <vdr-page-detail-sidebar
-            ><vdr-card>
+        >
+            <vdr-card>
                 <vdr-form-field [label]="'catalog.visibility' | translate" for="visibility">
                     <clr-toggle-wrapper *vdrIfPermissions="updatePermissions">
                         <input
@@ -72,6 +73,27 @@
                     </vdr-form-item>
                 </vdr-card>
             </ng-container>
+            <vdr-card *ngIf="entity?.optionGroups.length" [title]="'catalog.product-options' | translate">
+                <div class="options">
+                    <vdr-chip
+                        *ngFor="let optionGroup of entity?.optionGroups | sort : 'id'"
+                        [colorFrom]="optionGroup.code"
+                        [invert]="true"
+                    >
+                        {{ optionGroup.name }}
+                    </vdr-chip>
+                </div>
+                <div>
+                    <a
+                        [routerLink]="['options']"
+                        class="button"
+                        *vdrIfPermissions="updatePermissions"
+                    >
+                        <clr-icon shape="pencil"></clr-icon>
+                        {{ 'catalog.edit-options' | translate }}
+                    </a>
+                </div>
+            </vdr-card>
             <vdr-card [title]="'catalog.facets' | translate">
                 <div class="facets">
                     <vdr-facet-value-chip
@@ -183,7 +205,8 @@
                     <div class="mx-3" *ngIf="(isNew$ | async) === false">
                         <a class="button" [routerLink]="['manage-variants']">
                             <clr-icon shape="add-text"></clr-icon>
-                            {{ 'catalog.manage-variants' | translate }}</a>
+                            {{ 'catalog.manage-variants' | translate }}</a
+                        >
                     </div>
                 </div>
             </vdr-card>

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.scss

@@ -8,7 +8,7 @@
     }
 }
 
-.facets {
+.facets, .options {
     display: flex;
     flex-wrap: wrap;
     gap: 3px;

+ 120 - 109
packages/admin-ui/src/lib/catalog/src/components/product-options-editor/product-options-editor.component.html

@@ -1,114 +1,125 @@
-<vdr-action-bar>
-    <vdr-ab-left>
-        <vdr-language-selector
-            [availableLanguageCodes]="availableLanguages$ | async"
-            [currentLanguageCode]="languageCode$ | async"
-            (languageCodeChange)="setLanguage($event)"
-        ></vdr-language-selector>
-    </vdr-ab-left>
+<vdr-page-block>
+    <vdr-action-bar>
+        <vdr-ab-left>
+            <vdr-language-selector
+                [availableLanguageCodes]="availableLanguages$ | async"
+                [currentLanguageCode]="languageCode$ | async"
+                (languageCodeChange)="setLanguage($event)"
+            ></vdr-language-selector>
+        </vdr-ab-left>
 
-    <vdr-ab-right>
-        <div class="flex center">
-            <div class="mr2">
-                <clr-checkbox-wrapper>
-                    <input
-                        clrCheckbox
-                        type="checkbox"
-                        id="auto-update"
-                        [(ngModel)]="autoUpdateVariantNames"
-                    />
-                    <label>{{ 'catalog.auto-update-product-variant-name' | translate }}</label>
-                </clr-checkbox-wrapper>
-            </div>
-            <button
-                *vdrIfPermissions="updatePermission"
-                class="btn btn-primary"
-                (click)="save()"
-                [disabled]="detailForm.pristine || detailForm.invalid"
-            >
-                {{ 'common.update' | translate }}
-            </button>
-        </div>
-    </vdr-ab-right>
-</vdr-action-bar>
-<form class="form" [formGroup]="detailForm" *ngIf="optionGroups$ | async as optionGroups">
-    <div formGroupName="optionGroups" class="clr-row">
-        <div class="clr-col-12 clr-col-xl-6" *ngFor="let optionGroup of getOptionGroups(); index as i">
-            <section class="card" [formArrayName]="i">
-                <div class="card-header option-group-header">
-                    <vdr-entity-info [entity]="optionGroup.value"></vdr-entity-info>
-                    <div class="ml2">{{ optionGroup.value.code }}</div>
-                </div>
-                <div class="card-block">
-                    <vdr-form-field [label]="'common.name' | translate" for="name">
-                        <input
-                            [id]="'name-' + i"
-                            type="text"
-                            formControlName="name"
-                            [readonly]="!(updatePermission | hasPermission)"
-                        />
-                    </vdr-form-field>
-                    <vdr-form-field [label]="'common.code' | translate" for="code">
+        <vdr-ab-right>
+            <div class="flex center">
+                <div class="mr-2">
+                    <clr-checkbox-wrapper>
                         <input
-                            [id]="'code-' + i"
-                            type="text"
-                            [readonly]="!(updatePermission | hasPermission)"
-                            formControlName="code"
+                            clrCheckbox
+                            type="checkbox"
+                            id="auto-update"
+                            [(ngModel)]="autoUpdateVariantNames"
                         />
-                    </vdr-form-field>
-                    <section formGroupName="customFields" *ngIf="optionGroupCustomFields.length">
-                        <label>{{ 'common.custom-fields' | translate }}</label>
-                        <vdr-tabbed-custom-fields
-                            entityName="ProductOptionGroup"
-                            [customFields]="optionGroupCustomFields"
-                            [customFieldsFormGroup]="optionGroup.get('customFields')"
-                            [readonly]="!(updatePermission | hasPermission)"
-                        ></vdr-tabbed-custom-fields>
-                    </section>
+                        <label>{{ 'catalog.auto-update-product-variant-name' | translate }}</label>
+                    </clr-checkbox-wrapper>
                 </div>
-                <section class="card-block">
-                    <table class="facet-values-list table mt-2 mb-4" formGroupName="options">
-                        <thead>
-                            <tr>
-                                <th></th>
-                                <th>{{ 'common.name' | translate }}</th>
-                                <th>{{ 'common.code' | translate }}</th>
-                                <ng-container *ngIf="optionCustomFields.length">
-                                    <th>{{ 'common.custom-fields' | translate }}</th>
-                                </ng-container>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            <tr
-                                class="facet-value"
-                                *ngFor="let option of getOptions(optionGroup); let i = index"
-                                [formGroupName]="i"
-                            >
-                                <td class="align-middle">
-                                    <vdr-entity-info [entity]="option.value"></vdr-entity-info>
-                                </td>
-                                <td class="align-middle">
-                                    <input
-                                        type="text"
-                                        formControlName="name"
-                                        [readonly]="!(updatePermission | hasPermission)"
-                                    />
-                                </td>
-                                <td class="align-middle"><input type="text" formControlName="code" /></td>
-                                <td class="" *ngIf="optionCustomFields.length">
-                                    <vdr-tabbed-custom-fields
-                                        entityName="ProductOption"
-                                        [customFields]="optionCustomFields"
-                                        [compact]="true"
-                                        [customFieldsFormGroup]="option.get('customFields')"
-                                        [readonly]="!(updatePermission | hasPermission)"
-                                    ></vdr-tabbed-custom-fields>
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
-                </section>
-            </section>
+                <button
+                    *vdrIfPermissions="updatePermission"
+                    class="btn btn-primary"
+                    (click)="save()"
+                    [disabled]="detailForm.pristine || detailForm.invalid"
+                >
+                    {{ 'common.update' | translate }}
+                </button>
+            </div>
+        </vdr-ab-right>
+    </vdr-action-bar>
+</vdr-page-block>
+<vdr-page-block>
+    <form class="form" [formGroup]="detailForm" *ngIf="optionGroups$ | async as optionGroups">
+        <div formGroupName="optionGroups">
+            <vdr-card
+                *ngFor="let optionGroup of getOptionGroups(); index as i"
+                [formArrayName]="i"
+                [title]="optionGroup.value.code"
+            >
+                <vdr-page-entity-info class="card-span" [entity]="optionGroup.value"></vdr-page-entity-info>
+                <vdr-form-field [label]="'common.name' | translate" for="name">
+                    <input
+                        [id]="'name-' + i"
+                        type="text"
+                        formControlName="name"
+                        [readonly]="!(updatePermission | hasPermission)"
+                    />
+                </vdr-form-field>
+                <vdr-form-field [label]="'common.code' | translate" for="code">
+                    <input
+                        [id]="'code-' + i"
+                        type="text"
+                        [readonly]="!(updatePermission | hasPermission)"
+                        formControlName="code"
+                    />
+                </vdr-form-field>
+                <ng-container formGroupName="customFields" *ngIf="optionGroupCustomFields.length">
+                    <vdr-tabbed-custom-fields
+                        entityName="ProductOptionGroup"
+                        [customFields]="optionGroupCustomFields"
+                        [customFieldsFormGroup]="optionGroup.get('customFields')"
+                        [readonly]="!(updatePermission | hasPermission)"
+                    ></vdr-tabbed-custom-fields>
+                </ng-container>
+
+                <vdr-data-table-2
+                    class="card-span"
+                    id="edit-options-list"
+                    *ngIf="getOptions(optionGroup) as options"
+                    [items]="options"
+                    [itemsPerPage]="10"
+                    [totalItems]="options.length"
+                >
+                    <vdr-dt2-column [heading]="'common.id' | translate" [hiddenByDefault]="true">
+                        <ng-template let-optionControl="item">
+                            {{ optionControl.value.id }}
+                        </ng-template>
+                    </vdr-dt2-column>
+                    <vdr-dt2-column [heading]="'common.created-at' | translate" [hiddenByDefault]="true">
+                        <ng-template let-optionControl="item">
+                            {{ optionControl.value.createdAt | localeDate : 'short' }}
+                        </ng-template>
+                    </vdr-dt2-column>
+                    <vdr-dt2-column [heading]="'common.updated-at' | translate" [hiddenByDefault]="true">
+                        <ng-template let-optionControl="item">
+                            {{ optionControl.value.updatedAt | localeDate : 'short' }}
+                        </ng-template>
+                    </vdr-dt2-column>
+                    <vdr-dt2-column [heading]="'common.name' | translate" [optional]="false">
+                        <ng-template let-optionControl="item">
+                            <input
+                                type="text"
+                                [formControl]="optionControl.get('name')"
+                                [readonly]="!(updatePermission | hasPermission)"
+                            />
+                        </ng-template>
+                    </vdr-dt2-column>
+                    <vdr-dt2-column [heading]="'common.code' | translate" [optional]="false">
+                        <ng-template let-optionControl="item">
+                            <input type="text" [formControl]="optionControl.get('code')" />
+                        </ng-template>
+                    </vdr-dt2-column>
+                    <vdr-dt2-column
+                        [heading]="'common.custom-fields' | translate"
+                        [hiddenByDefault]="optionCustomFields.length === 0"
+                    >
+                        <ng-template let-optionControl="item">
+                            <vdr-tabbed-custom-fields
+                                entityName="ProductOption"
+                                [customFields]="optionCustomFields"
+                                [compact]="true"
+                                [customFieldsFormGroup]="optionControl.get('customFields')"
+                                [readonly]="!(updatePermission | hasPermission)"
+                            ></vdr-tabbed-custom-fields>
+                        </ng-template>
+                    </vdr-dt2-column>
+                </vdr-data-table-2>
+            </vdr-card>
         </div>
-    </div>
-</form>
+    </form>
+</vdr-page-block>

+ 5 - 5
packages/admin-ui/src/lib/catalog/src/components/product-options-editor/product-options-editor.component.ts

@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
-import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
+import { FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import {
@@ -244,8 +244,8 @@ export class ProductOptionsEditorComponent extends BaseDetailComponent<ProductWi
             groupForm.get('id')?.setValue(group.id);
             groupForm.get('code')?.setValue(group.code);
             groupForm.get('name')?.setValue(group.name);
-            groupForm.get('createdAt')?.setValue(group.name);
-            groupForm.get('updatedAt')?.setValue(group.name);
+            groupForm.get('createdAt')?.setValue(group.createdAt);
+            groupForm.get('updatedAt')?.setValue(group.updatedAt);
         } else {
             groupForm = this.formBuilder.group(group);
             groupsForm.push(groupForm);
@@ -272,8 +272,8 @@ export class ProductOptionsEditorComponent extends BaseDetailComponent<ProductWi
             optionForm.get('id')?.setValue(group.id);
             optionForm.get('code')?.setValue(group.code);
             optionForm.get('name')?.setValue(group.name);
-            optionForm.get('createdAt')?.setValue(group.name);
-            optionForm.get('updatedAt')?.setValue(group.name);
+            optionForm.get('createdAt')?.setValue(group.createdAt);
+            optionForm.get('updatedAt')?.setValue(group.updatedAt);
         } else {
             optionForm = this.formBuilder.group(group);
             optionsForm.push(optionForm);

+ 8 - 4
packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.component.html

@@ -48,15 +48,19 @@
                 <div class="options">
                     <vdr-chip
                         *ngFor="let option of variant.options | sort : 'groupId'"
-                        [colorFrom]="optionGroupName(option.groupId)"
+                        [colorFrom]="optionGroupCode(option.groupId)"
                         [invert]="true"
-                        (iconClick)="editOption(option)"
-                        [icon]="(updatePermissions | hasPermission) && 'pencil'"
                     >
-                        <span>{{ optionGroupName(option.groupId) }}:</span>
+                        <span>{{ optionGroupCode(option.groupId) }}:</span>
                         {{ optionName(option) }}
                     </vdr-chip>
                 </div>
+                <div>
+                    <a [routerLink]="['../../', 'options']" class="button" *vdrIfPermissions="updatePermissions">
+                        <clr-icon shape="pencil"></clr-icon>
+                        {{ 'catalog.edit-options' | translate }}
+                    </a>
+                </div>
             </vdr-card>
             <vdr-card [title]="'catalog.facets' | translate">
                 <div class="facets">

+ 5 - 0
packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.component.scss

@@ -0,0 +1,5 @@
+.facets, .options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 3px;
+}

+ 15 - 34
packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.component.ts

@@ -8,6 +8,8 @@ import {
     createUpdatedTranslatable,
     DataService,
     findTranslation,
+    GetProductVariantDetailDocument,
+    GetProductVariantDetailQuery,
     GlobalFlag,
     ItemOf,
     LanguageCode,
@@ -15,8 +17,6 @@ import {
     NotificationService,
     Permission,
     ProductOptionFragment,
-    ProductVariantDetailQueryDocument,
-    ProductVariantDetailQueryQuery,
     ProductVariantFragment,
     ProductVariantUpdateMutationDocument,
     ServerConfigService,
@@ -25,7 +25,7 @@ import {
 } from '@vendure/admin-ui/core';
 import { pick } from '@vendure/common/lib/pick';
 import { unique } from '@vendure/common/lib/unique';
-import { combineLatest, concat, Observable } from 'rxjs';
+import { combineLatest, concat, EMPTY, Observable } from 'rxjs';
 import {
     distinctUntilChanged,
     map,
@@ -39,6 +39,7 @@ import {
 } from 'rxjs/operators';
 import { ProductDetailService } from '../../providers/product-detail/product-detail.service';
 import { ApplyFacetDialogComponent } from '../apply-facet-dialog/apply-facet-dialog.component';
+import { UpdateProductOptionDialogComponent } from '../update-product-option-dialog/update-product-option-dialog.component';
 
 interface SelectedAssets {
     assets?: Asset[];
@@ -60,7 +61,7 @@ interface VariantFormValue {
     facetValueIds: string[][];
     customFields?: any;
 }
-type T = NonNullable<ProductVariantDetailQueryQuery['productVariant']>;
+type T = NonNullable<GetProductVariantDetailQuery['productVariant']>;
 type T1 = T['stockLevels'];
 @Component({
     selector: 'vdr-product-variant-detail',
@@ -69,12 +70,13 @@ type T1 = T['stockLevels'];
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class ProductVariantDetailComponent
-    extends TypedBaseDetailComponent<typeof ProductVariantDetailQueryDocument, 'productVariant'>
+    extends TypedBaseDetailComponent<typeof GetProductVariantDetailDocument, 'productVariant'>
     implements OnInit, OnDestroy
 {
     public readonly updatePermissions = [Permission.UpdateCatalog, Permission.UpdateProduct];
     readonly customFields = this.getCustomFieldConfig('ProductVariant');
-    stockLevels$: Observable<NonNullable<ProductVariantDetailQueryQuery['productVariant']>['stockLevels']>;
+    readonly customOptionFields = this.getCustomFieldConfig('ProductOption');
+    stockLevels$: Observable<NonNullable<GetProductVariantDetailQuery['productVariant']>['stockLevels']>;
     detailForm = this.formBuilder.group<VariantFormValue>({
         id: '',
         enabled: false,
@@ -101,13 +103,13 @@ export class ProductVariantDetailComponent
         }>
     >([]);
     assetChanges: SelectedAssets = {};
-    taxCategories$: Observable<Array<ItemOf<ProductVariantDetailQueryQuery, 'taxCategories'>>>;
-    stockLocations$: Observable<ItemOf<ProductVariantDetailQueryQuery, 'stockLocations'>>;
+    taxCategories$: Observable<Array<ItemOf<GetProductVariantDetailQuery, 'taxCategories'>>>;
+    stockLocations$: Observable<ItemOf<GetProductVariantDetailQuery, 'stockLocations'>>;
     channelPriceIncludesTax$: Observable<boolean>;
     readonly GlobalFlag = GlobalFlag;
     globalTrackInventory: boolean;
     globalOutOfStockThreshold: number;
-    facetValues$: Observable<NonNullable<ProductVariantDetailQueryQuery['productVariant']>['facetValues']>;
+    facetValues$: Observable<NonNullable<GetProductVariantDetailQuery['productVariant']>['facetValues']>;
 
     constructor(
         route: ActivatedRoute,
@@ -234,13 +236,9 @@ export class ProductVariantDetailComponent
         );
     }
 
-    optionGroupName(optionGroupId: string): string | undefined {
+    optionGroupCode(optionGroupId: string): string | undefined {
         const group = this.entity?.product.optionGroups.find(g => g.id === optionGroupId);
-        if (group) {
-            const translation =
-                group?.translations.find(t => t.languageCode === this.languageCode) ?? group.translations[0];
-            return translation.name;
-        }
+        return group?.code;
     }
 
     optionName(option: ProductOptionFragment) {
@@ -249,23 +247,6 @@ export class ProductVariantDetailComponent
         return translation.name;
     }
 
-    editOption(option: ProductVariantFragment['options'][number]) {
-        /*     this.modalService
-                .fromComponent(UpdateProductOptionDialogComponent, {
-                    size: 'md',
-                    locals: {
-                        productOption: option,
-                        activeLanguage: this.languageCode,
-                        customFields: this.customOptionFields,
-                    },
-                })
-                .subscribe(result => {
-                    if (result) {
-                        this.updateProductOption.emit(result);
-                    }
-                }); */
-    }
-
     removeFacetValue(facetValueId: string) {
         const productGroup = this.detailForm;
         const currentFacetValueIds = productGroup.value.facetValueIds ?? [];
@@ -297,7 +278,7 @@ export class ProductVariantDetailComponent
     }
 
     protected setFormValues(
-        variant: NonNullable<ProductVariantDetailQueryQuery['productVariant']>,
+        variant: NonNullable<GetProductVariantDetailQuery['productVariant']>,
         languageCode: LanguageCode,
     ): void {
         const variantTranslation = findTranslation(variant, languageCode);
@@ -342,7 +323,7 @@ export class ProductVariantDetailComponent
      * can then be persisted to the API.
      */
     private getUpdatedVariant(
-        variant: NonNullable<ProductVariantDetailQueryQuery['productVariant']>,
+        variant: NonNullable<GetProductVariantDetailQuery['productVariant']>,
         variantFormGroup: typeof this.detailForm,
         languageCode: LanguageCode,
     ): UpdateProductVariantInput | CreateProductVariantInput {

+ 2 - 1
packages/admin-ui/src/lib/catalog/src/components/product-variant-detail/product-variant-detail.graphql.ts

@@ -74,6 +74,7 @@ export const PRODUCT_VARIANT_DETAIL_QUERY_PRODUCT_VARIANT_FRAGMENT = gql`
             optionGroups {
                 id
                 name
+                code
                 translations {
                     id
                     languageCode
@@ -85,7 +86,7 @@ export const PRODUCT_VARIANT_DETAIL_QUERY_PRODUCT_VARIANT_FRAGMENT = gql`
 `;
 
 export const PRODUCT_VARIANT_DETAIL_QUERY = gql`
-    query ProductVariantDetailQuery($id: ID!) {
+    query GetProductVariantDetail($id: ID!) {
         productVariant(id: $id) {
             ...ProductVariantDetailQueryProductVariantFragment
         }

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

@@ -16,7 +16,7 @@
             </vdr-form-field>
             <div>
                 <button
-                    [disabled]="group.locked"
+
                     class="button-small mt-4"
                     (click)="removeOptionGroup(group)"
                 >
@@ -40,7 +40,7 @@
         </div>
         <vdr-data-table-2
             class="card-span"
-            id="product-variant-list"
+            id="manage-product-variant-list"
             [items]="variants$ | async"
             [itemsPerPage]="itemsPerPage"
             [totalItems]="totalItems$ | async"
@@ -48,11 +48,6 @@
             (pageChange)="setPageNumber($event)"
             (itemsPerPageChange)="setItemsPerPage($event)"
         >
-            <vdr-bulk-action-menu
-                locationId="manage-variants-list"
-                [hostComponent]="this"
-                [selectionManager]="selectionManager"
-            />
             <vdr-dt2-search
                 [searchTermControl]="searchTermControl"
                 [searchTermPlaceholder]="'catalog.filter-by-name' | translate"
@@ -85,10 +80,29 @@
             <vdr-dt2-column *ngFor="let optionGroup of optionGroups$ | async" [heading]="optionGroup.name">
                 <ng-template let-variant="item">
                     <vdr-chip
-                        *ngIf="getOption(variant, optionGroup.id) as option"
-                        [colorFrom]="optionGroup.name"
+                        *ngIf="getOption(variant, optionGroup.id) as option; else selectOption"
+                        [colorFrom]="optionGroup.code"
                         >{{ option.name }}</vdr-chip
                     >
+                    <ng-template #selectOption>
+                        <div class="flex center">
+                            <ng-select
+                                [items]="optionGroup.options"
+                                bindLabel="name"
+                                bindValue="id"
+                                appendTo="body"
+                                (change)="setOptionToAddToVariant(variant.id, optionGroup.id, $event?.id)"
+                            ></ng-select>
+                            <button
+                                class="button-small ml-1"
+                                [class.primary]="!!optionsToAddToVariant[variant.id]?.[optionGroup.id]"
+                                (click)="addOptionToVariant(variant)"
+                                [disabled]="!optionsToAddToVariant[variant.id]?.[optionGroup.id]"
+                            >
+                                <clr-icon shape="floppy"></clr-icon>
+                            </button>
+                        </div>
+                    </ng-template>
                 </ng-template>
             </vdr-dt2-column>
             <vdr-dt2-column [heading]="'common.price' | translate" [hiddenByDefault]="true">

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

@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
 import { FormControl } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@@ -74,6 +74,9 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
         itemsAreEqual: (a, b) => a.id === b.id,
         additiveMode: true,
     });
+    optionsToAddToVariant: {
+        [variantId: string]: { [groupId: string]: string };
+    } = {};
     private refresh$ = new Subject<void>();
     private languageCode: LanguageCode;
 
@@ -83,6 +86,7 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
         private productDetailService: ProductDetailService,
         private notificationService: NotificationService,
         private modalService: ModalService,
+        private changeDetector: ChangeDetectorRef,
     ) {}
 
     ngOnInit() {
@@ -181,6 +185,7 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
                     entity: 'ProductOptionGroup',
                 });
                 this.refresh$.next();
+                this.changeDetector.markForCheck();
             });
     }
 
@@ -188,10 +193,14 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
         optionGroup: NonNullable<GetProductVariantOptionsQuery['product']>['optionGroups'][number],
     ) {
         const id = optionGroup.id;
+        const usedByVariantsCount = this.product.variants.filter(v =>
+            v.options.map(o => o.groupId).includes(id),
+        ).length;
         this.modalService
             .dialog({
                 title: _('catalog.confirm-delete-product-option-group'),
-                translationVars: { name: optionGroup.name },
+                body: usedByVariantsCount ? _('catalog.confirm-delete-product-option-group-body') : '',
+                translationVars: { name: optionGroup.name, count: usedByVariantsCount },
                 buttons: [
                     { type: 'secondary', label: _('common.cancel') },
                     { type: 'danger', label: _('common.delete'), returnValue: true },
@@ -203,6 +212,7 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
                         return this.dataService.product.removeOptionGroupFromProduct({
                             optionGroupId: id,
                             productId: this.product.id,
+                            force: true,
                         });
                     } else {
                         return EMPTY;
@@ -274,6 +284,33 @@ export class ProductVariantsEditorComponent implements OnInit, DeactivateAware {
         }
     }
 
+    setOptionToAddToVariant(variantId: string, optionGroupId: string, optionId?: string) {
+        if (!this.optionsToAddToVariant[variantId]) {
+            this.optionsToAddToVariant[variantId] = {};
+        }
+        if (optionId) {
+            this.optionsToAddToVariant[variantId][optionGroupId] = optionId;
+        } else {
+            delete this.optionsToAddToVariant[variantId][optionGroupId];
+        }
+    }
+
+    addOptionToVariant(variant: NonNullable<GetProductVariantOptionsQuery['product']>['variants'][number]) {
+        this.dataService.product
+            .updateProductVariants([
+                {
+                    id: variant.id,
+                    optionIds: [
+                        ...variant.options.map(o => o.id),
+                        ...Object.values(this.optionsToAddToVariant[variant.id]),
+                    ],
+                },
+            ])
+            .subscribe(({ updateProductVariants }) => {
+                this.refresh$.next();
+            });
+    }
+
     deleteVariant(variant: NonNullable<GetProductVariantOptionsQuery['product']>['variants'][number]) {
         this.modalService
             .dialog({

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


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

@@ -310,8 +310,8 @@ export const ADD_OPTION_GROUP_TO_PRODUCT = gql`
 `;
 
 export const REMOVE_OPTION_GROUP_FROM_PRODUCT = gql`
-    mutation RemoveOptionGroupFromProduct($productId: ID!, $optionGroupId: ID!) {
-        removeOptionGroupFromProduct(productId: $productId, optionGroupId: $optionGroupId) {
+    mutation RemoveOptionGroupFromProduct($productId: ID!, $optionGroupId: ID!, $force: Boolean) {
+        removeOptionGroupFromProduct(productId: $productId, optionGroupId: $optionGroupId, force: $force) {
             ... on Product {
                 id
                 createdAt

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

@@ -244,6 +244,7 @@ export class ProductDataService {
                     'facetValueIds',
                     'featuredAssetId',
                     'assetIds',
+                    'optionIds',
                     'trackInventory',
                     'outOfStockThreshold',
                     'useGlobalOutOfStockThreshold',

+ 3 - 0
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts

@@ -228,6 +228,9 @@ export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnIn
             this.selectionManager.setCurrentItems(this.items);
         }
         this.showSearchFilterRow = dataTableConfig?.[this.id]?.showSearchFilterRow ?? false;
+        this.columns.changes.subscribe(() => {
+            this.changeDetectorRef.markForCheck();
+        });
     }
 
     onColumnReorder(event: { column: DataTable2ColumnComponent<any>; newIndex: number }) {

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/cs.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Automaticky aktualizovat jména variant pomocí této",
     "auto-update-product-variant-name": "Automaticky aktualizovat jména variant",
     "channel-price-preview": "Náhled ceny v kanálu",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Obsah kolekce",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Smazat produkt?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Smazat variantu produktu?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "Vytvořit kolekci",
     "create-new-facet": "Vytvořit nový atribut",
     "create-new-product": "Nový produkt",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Přetáhněte soubory k nahrávání",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Hodnoty atributů",
@@ -571,9 +571,11 @@
     "note-is-private": "Interní poznámka",
     "note-only-visible-to-administrators": "Pouze pro adminy",
     "note-visible-to-customer": "Pro adminy i zákazníka",
+    "order": "",
     "order-history": "Historie objednávky",
     "order-state-diagram": "Přehled stavu objednávky",
     "order-type": "",
+    "orders": "",
     "payment": "Platba",
     "payment-amount": "Částka platby",
     "payment-metadata": "Data platby",
@@ -646,10 +648,6 @@
     "unfulfilled": "Nevyřízeno",
     "unit-price": "Cena za kus"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Přidat země do { zoneName }",
     "add-countries-to-zone-success": "Přidáno: { countryCount } {countryCount, plural, one {země} other {země}} do zóny \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Automatisch Namen der Optionsvariante aktualisieren",
     "auto-update-product-variant-name": "Automatisch Namen der Produktvariante aktualisieren",
     "channel-price-preview": "Kanal-Preisvorschau",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Inhalt der Sammlung",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Produkt löschen?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Produktvariante löschen?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "Neue Kollektion anlegen",
     "create-new-facet": "Neue Facette erstellen",
     "create-new-product": "Neues Produkt",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Dateien zum Hochladen ablegen",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Facettenwerte",
@@ -571,9 +571,11 @@
     "note-is-private": "Notiz ist privat",
     "note-only-visible-to-administrators": "Nur für Administratoren sichtbar",
     "note-visible-to-customer": "Sichtbar für Administratoren und Kunden",
+    "order": "",
     "order-history": "Bestellhistorie",
     "order-state-diagram": "",
     "order-type": "",
+    "orders": "",
     "payment": "Zahlung",
     "payment-amount": "Zahlungsbetrag",
     "payment-metadata": "Metadaten zur Bezahlung",
@@ -646,10 +648,6 @@
     "unfulfilled": "Nicht ausgeführt",
     "unit-price": "Einzelpreis"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Länder hinzufügen zu { zoneName }",
     "add-countries-to-zone-success": "{ countryCount } {countryCount, plural, one {Land} other {Länder}} hinzugefügt zu \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Automatically update the names of ProductVariants using this option",
     "auto-update-product-variant-name": "Automatically update the names of ProductVariants",
     "channel-price-preview": "Channel price preview",
-    "code-pattern-error": "",
     "collection": "Collection",
     "collection-contents": "Collection contents",
     "collections": "Collections",
@@ -86,6 +85,7 @@
     "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-option-group-body": "This option group is used by {count} {count, plural, one {variant} other {variants}}. Are you sure you want to delete it?",
     "confirm-delete-product-variant": "Delete product variant \"{name}\"?",
     "confirm-deletion-of-unused-variants-body": "The following product variants have been made obsolete due to the addition of new options. They will be deleted during the creation of the new product variants.",
     "confirm-deletion-of-unused-variants-title": "Delete obsolete product variants?",
@@ -93,12 +93,12 @@
     "create-new-collection": "Create new collection",
     "create-new-facet": "Create new facet",
     "create-new-product": "New product",
-    "create-new-variant": "Create new variant",
     "create-product-option-group": "Create product option group",
     "create-product-variant": "Create product variant",
     "do-not-inherit-filters": "Do not inherit filters",
     "drop-files-to-upload": "Drop files to upload",
     "edit-facet-values": "Edit facet values",
+    "edit-options": "Edit options",
     "facet": "Facet",
     "facet-value-not-available": "Facet value \"{ id }\" not available",
     "facet-values": "Facet values",
@@ -571,9 +571,11 @@
     "note-is-private": "Note is private",
     "note-only-visible-to-administrators": "Visible to admins only",
     "note-visible-to-customer": "Visible to admins and customer",
+    "order": "",
     "order-history": "Order history",
     "order-state-diagram": "Order state diagram",
     "order-type": "Order type",
+    "orders": "",
     "payment": "Payment",
     "payment-amount": "Payment amount",
     "payment-metadata": "Payment metadata",
@@ -646,10 +648,6 @@
     "unfulfilled": "Unfulfilled",
     "unit-price": "Unit price"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Add countries to { zoneName }",
     "add-countries-to-zone-success": "Added { countryCount } {countryCount, plural, one {country} other {countries}} to zone \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Actualiza los nombres de las variantes de producto automáticamente usando esta opción",
     "auto-update-product-variant-name": "Actualiza los nombres de las variantes de producto automáticamente",
     "channel-price-preview": "Vista previa de precio para el canal de ventas",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Contenidos de la colección",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "¿Eliminar producto?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "¿Eliminar variante?",
     "confirm-deletion-of-unused-variants-body": "Las siguientes variantes de producto han quedado obsoletas debido a la incorporación de nuevas opciones. Se eliminarán durante la creación de las nuevas variantes de producto.",
     "confirm-deletion-of-unused-variants-title": "¿Eliminar variantes de producto obsoletas?",
@@ -93,12 +93,12 @@
     "create-new-collection": "Crear nueva colección",
     "create-new-facet": "Crear nueva faceta",
     "create-new-product": "Crear nuevo producto",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Arrastra recursos para subirlos",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Valores de faceta",
@@ -571,9 +571,11 @@
     "note-is-private": "La nota es privada",
     "note-only-visible-to-administrators": "Visible sólo para administradores",
     "note-visible-to-customer": "Visible para administradores y clientes",
+    "order": "",
     "order-history": "Historial de pedidos",
     "order-state-diagram": "Diagrama de estado de los pedidos",
     "order-type": "",
+    "orders": "",
     "payment": "Pago",
     "payment-amount": "Importe del pago",
     "payment-metadata": "Metadatos de pago",
@@ -646,10 +648,6 @@
     "unfulfilled": "Fulfillment no completado",
     "unit-price": "Precio unitario"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Añadir países a zona...",
     "add-countries-to-zone-success": "Añadido { countryCount } {countryCount, plural, one {país} other {países}} a la zona \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/fr.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Mettre à jour automatiquement les noms de variations du produit en utilisant cette option",
     "auto-update-product-variant-name": "Mettre à jour automatiquement les noms de variations du produit ",
     "channel-price-preview": "Prévisualisation du prix du canal",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Contenu de la Collection",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Supprimer produit ?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Supprimer variation du produit ?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "Créer nouvelle collection",
     "create-new-facet": "Créer nouveau composant",
     "create-new-product": "Nouveau produit",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Déposer des fichiers pour téléverser",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Valeurs de composant",
@@ -571,9 +571,11 @@
     "note-is-private": "La note est privée",
     "note-only-visible-to-administrators": "Visible par les admins uniquement",
     "note-visible-to-customer": "Visible par les admins et le client",
+    "order": "",
     "order-history": "Historique de la commande",
     "order-state-diagram": "Diagramme des états de la commande",
     "order-type": "",
+    "orders": "",
     "payment": "paiement",
     "payment-amount": "Montant à payer",
     "payment-metadata": "Métadonnées de paiement",
@@ -646,10 +648,6 @@
     "unfulfilled": "Non préparé",
     "unit-price": "Prix à l'unité"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Ajouter des pays à { zoneName }",
     "add-countries-to-zone-success": "{ countryCount } {countryCount, plural, one {pays ajouté} other {pays ajoutés}} à la zone \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/it.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Aggiorna automaticamente i nomi delle Varianti utilizzando questa opzione",
     "auto-update-product-variant-name": "Aggiorna automaticamente i nomi delle Varianti",
     "channel-price-preview": "Anteprima prezzo canale",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Contenuti della Collezione",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Eliminare il prodotto?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Eliminare la variante?",
     "confirm-deletion-of-unused-variants-body": "Le seguenti varianti sono diventate obsolete a seguito dell'aggiunta di nuove opzioni. Queste verranno cancellate durante la creazione delle nuove varianti.",
     "confirm-deletion-of-unused-variants-title": "Cancellare le varianti obsolete?",
@@ -93,12 +93,12 @@
     "create-new-collection": "Crea nuova Collezione",
     "create-new-facet": "Crea nuovo attributo",
     "create-new-product": "Crea nuovo prodotto",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Trascina file da caricare",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Valori attributo",
@@ -571,9 +571,11 @@
     "note-is-private": "Le note sono private",
     "note-only-visible-to-administrators": "Visibile solo agli amministratori",
     "note-visible-to-customer": "Visibile agli amministratori e al cliente",
+    "order": "",
     "order-history": "Storico ordine",
     "order-state-diagram": "Diagramma di stato dell'ordine",
     "order-type": "",
+    "orders": "",
     "payment": "Pagamento",
     "payment-amount": "Importo pagamento",
     "payment-metadata": "Metadati pagamento",
@@ -646,10 +648,6 @@
     "unfulfilled": "Non consegnato",
     "unit-price": "Prezzo unitario"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Aggiungi nazioni a { zoneName }",
     "add-countries-to-zone-success": "Ho aggiunto { countryCount } {countryCount, plural, one {nazione} other {nazioni}} alla zona \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/pl.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "",
     "auto-update-product-variant-name": "",
     "channel-price-preview": "Podgląd cen kanału",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Zawartość kolekcji",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Usunąć produkt?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Usunąć wariant produktu?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "Utwórz nową kolekcje",
     "create-new-facet": "Utwórz faset",
     "create-new-product": "Nowy produkt",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Upuść pliki do uploadu",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Wartości faseta",
@@ -571,9 +571,11 @@
     "note-is-private": "Notatka jest prywatna",
     "note-only-visible-to-administrators": "Widoczne tylko dla administratora",
     "note-visible-to-customer": "Widoczne dla administratora i klienta",
+    "order": "",
     "order-history": "Historia zamówienia",
     "order-state-diagram": "",
     "order-type": "",
+    "orders": "",
     "payment": "Płatność",
     "payment-amount": "Wartość płatności",
     "payment-metadata": "Metadane płatności",
@@ -646,10 +648,6 @@
     "unfulfilled": "Unfulfilled",
     "unit-price": "Cena jednostkowa"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Dodaj kraje do strefy...",
     "add-countries-to-zone-success": "Dodano { countryCount } {countryCount, plural, one {kraj} other {kraje}} do strefy \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Atualizar automaticamente os nomes das variações do produto usando esta opção",
     "auto-update-product-variant-name": "Atualizar automaticamente os nomes das variações do produto",
     "channel-price-preview": "Visualizar preço do canal",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Conteúdo da categoria",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Excluir produto?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Excluir variação de produto?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "Criar nova categoria",
     "create-new-facet": "Criar nova etiqueta",
     "create-new-product": "Novo produto",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Soltar arquivos para envio",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Valor da Etiqueta",
@@ -571,9 +571,11 @@
     "note-is-private": "Nota é privada",
     "note-only-visible-to-administrators": "Visível somente para administradores",
     "note-visible-to-customer": "Visível para administradores e clientes",
+    "order": "",
     "order-history": "Histórico de pedidos",
     "order-state-diagram": "Diagrama do estado do pedido",
     "order-type": "",
+    "orders": "",
     "payment": "Pagamento",
     "payment-amount": "Valor do pagamento",
     "payment-metadata": "Dados do pagamento",
@@ -646,10 +648,6 @@
     "unfulfilled": "Não realizado",
     "unit-price": "Preço unitário"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Adicionar paises para { zoneName }",
     "add-countries-to-zone-success": "Adicionado { countryCount } {countryCount, plural, one {country} other {countries}} para zona \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Utilizar esta opção para actualizar automaticamente os nomes das variantes",
     "auto-update-product-variant-name": "Actualizar automaticamente os nomes das variantes do produto",
     "channel-price-preview": "Visualizar preço do canal",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Conteúdo da categoria",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Eliminar produto?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Eliminar variante do produto?",
     "confirm-deletion-of-unused-variants-body": "As variantes listadas abaixo estão obsoletas e serão eliminadas devido à adição de novas opções.",
     "confirm-deletion-of-unused-variants-title": "Eliminar as variantes obsoletas?",
@@ -93,12 +93,12 @@
     "create-new-collection": "Criar nova categoria",
     "create-new-facet": "Criar nova etiqueta",
     "create-new-product": "Novo produto",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Colocar ficheiros para enviar",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Valor da Etiqueta",
@@ -571,9 +571,11 @@
     "note-is-private": "Marcar como privada",
     "note-only-visible-to-administrators": "Visível somente para administradores",
     "note-visible-to-customer": "Visível para administradores e clientes",
+    "order": "",
     "order-history": "Histórico de encomendas",
     "order-state-diagram": "Diagrama do estado da encomenda",
     "order-type": "",
+    "orders": "",
     "payment": "Pagamento",
     "payment-amount": "Valor do pagamento",
     "payment-metadata": "Dados do pagamento",
@@ -646,10 +648,6 @@
     "unfulfilled": "Por entreguar",
     "unit-price": "Preço unitário"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Adicionar paises para { zoneName }",
     "add-countries-to-zone-success": "A adicionar { countryCount } {countryCount, plural, one {país} other {países}} à região \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/ru.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Автоматически обновлять названия вариантов товара с помощью этой опции",
     "auto-update-product-variant-name": "Автоматически обновлять названия вариантов товара",
     "channel-price-preview": "Предварительный просмотр цен канала",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Содержание коллекции",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Удалить товар?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Удалить вариант товара?",
     "confirm-deletion-of-unused-variants-body": "Следующие варианты товаров устарели из за добавления новых опций. Они будут удалены во время создания новых вариантов товара.",
     "confirm-deletion-of-unused-variants-title": "Удалить устаревшие варианты товара?",
@@ -93,12 +93,12 @@
     "create-new-collection": "Создать новую коллекцию",
     "create-new-facet": "Создать новый тег",
     "create-new-product": "Создать новый товар",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Перетащите файлы для загрузки",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Значения тега",
@@ -571,9 +571,11 @@
     "note-is-private": "Заметка является служебной",
     "note-only-visible-to-administrators": "Доступно только администраторам",
     "note-visible-to-customer": "Доступно администраторам и клиентам",
+    "order": "",
     "order-history": "История заказов",
     "order-state-diagram": "Диаграмма состояния заказа",
     "order-type": "",
+    "orders": "",
     "payment": "Оплата",
     "payment-amount": "Сумма к оплате",
     "payment-metadata": "Метаданные платежа",
@@ -646,10 +648,6 @@
     "unfulfilled": "Невыполненный",
     "unit-price": "Цена за единицу"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Добавить страны в { zoneName }",
     "add-countries-to-zone-success": "Добавлено { countryCount } {countryCount, plural, one {страна} other {стран}} в зону \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/uk.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "Автоматично оновлювати назви варіантів товару, використовуючи цю опцію",
     "auto-update-product-variant-name": "Автоматично оновлювати назви варіантів товару",
     "channel-price-preview": "Попередній перегляд цін каналу",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "Зміст колекції",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "Видалити товар?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "Видалити варіант товару?",
     "confirm-deletion-of-unused-variants-body": "Наступні варіанти товару застаріли через додавання нових опцій. Вони будуть видалені під час створення нових варіантів товару.",
     "confirm-deletion-of-unused-variants-title": "Видалити застарілі варіанти товару?",
@@ -93,12 +93,12 @@
     "create-new-collection": "Створити нову колекцію",
     "create-new-facet": "Створити новий тег",
     "create-new-product": "Створити новий товар",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "Перетягніть файли для завантаження",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "Значення тегу",
@@ -571,9 +571,11 @@
     "note-is-private": "Примітка є службовою",
     "note-only-visible-to-administrators": "Доступно тільки адміністраторам",
     "note-visible-to-customer": "Доступно адміністраторам і клієнтам",
+    "order": "",
     "order-history": "Історія замовлень",
     "order-state-diagram": "Діаграма стану замовлення",
     "order-type": "",
+    "orders": "",
     "payment": "Оплата",
     "payment-amount": "Сума до оплати",
     "payment-metadata": "Метадані платежу",
@@ -646,10 +648,6 @@
     "unfulfilled": "Невиконано",
     "unit-price": "Ціна за одиницю"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "Додати країни в { zoneName }",
     "add-countries-to-zone-success": "Додано { countryCount } {countryCount, plural, one {країна} other {країн}} в зону \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "此选项自动更新不同商品变体名称",
     "auto-update-product-variant-name": "自动更新不同商品变体名称",
     "channel-price-preview": "渠道价格预览",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "系列产品",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "确认删除商品?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "确认删除商品规格?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "添加系列",
     "create-new-facet": "添加特征",
     "create-new-product": "添加商品",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "拖拽文件上传",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "特征值列表",
@@ -571,9 +571,11 @@
     "note-is-private": "隐藏备注",
     "note-only-visible-to-administrators": "仅管理员可见",
     "note-visible-to-customer": "管理员及客户可见",
+    "order": "",
     "order-history": "历史订单",
     "order-state-diagram": "",
     "order-type": "",
+    "orders": "",
     "payment": "付款信息",
     "payment-amount": "付款金额",
     "payment-metadata": "付款元数据",
@@ -646,10 +648,6 @@
     "unfulfilled": "未配货",
     "unit-price": "单价"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "添加国家到销售区域...",
     "add-countries-to-zone-success": "{ countryCount }个国家已到销售区域 \"{ zoneName }\"",

+ 4 - 6
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -71,7 +71,6 @@
     "auto-update-option-variant-name": "",
     "auto-update-product-variant-name": "",
     "channel-price-preview": "渠道價格覽",
-    "code-pattern-error": "",
     "collection": "",
     "collection-contents": "系列產品",
     "collections": "",
@@ -86,6 +85,7 @@
     "confirm-delete-product": "確認移除商品?",
     "confirm-delete-product-option": "",
     "confirm-delete-product-option-group": "",
+    "confirm-delete-product-option-group-body": "",
     "confirm-delete-product-variant": "確認移除商品規格?",
     "confirm-deletion-of-unused-variants-body": "",
     "confirm-deletion-of-unused-variants-title": "",
@@ -93,12 +93,12 @@
     "create-new-collection": "新增系列",
     "create-new-facet": "新增特徵",
     "create-new-product": "新增商品",
-    "create-new-variant": "",
     "create-product-option-group": "",
     "create-product-variant": "",
     "do-not-inherit-filters": "",
     "drop-files-to-upload": "拖拽文件上傳",
     "edit-facet-values": "",
+    "edit-options": "",
     "facet": "",
     "facet-value-not-available": "",
     "facet-values": "特徵值列表",
@@ -571,9 +571,11 @@
     "note-is-private": "隱藏備注",
     "note-only-visible-to-administrators": "僅管理員可瀏覽",
     "note-visible-to-customer": "管理員及客户可瀏覽",
+    "order": "",
     "order-history": "訂單",
     "order-state-diagram": "",
     "order-type": "",
+    "orders": "",
     "payment": "付款信息",
     "payment-amount": "付款金額",
     "payment-metadata": "付款元數據",
@@ -646,10 +648,6 @@
     "unfulfilled": "未配貨",
     "unit-price": "單價"
   },
-  "orders": {
-    "order": "",
-    "orders": ""
-  },
   "settings": {
     "add-countries-to-zone": "新增國家到銷售區域...",
     "add-countries-to-zone-success": "{ countryCount }個國家已到銷售區域 \"{ zoneName }\"",

+ 13 - 1
packages/admin-ui/src/lib/static/styles/global/_buttons.scss

@@ -15,6 +15,9 @@
         0px 2px 6px rgba(0, 0, 0, 0.03), 0px 2px 11px rgba(0, 0, 0, 0.04);
     background-color: var(--color-button-bg);
     color: var(--color-weight-700);
+    &:link, &:visited {
+        color: var(--color-weight-700);
+    }
     &:disabled {
         background-color: var(--color-weight-100);
         color: var(--color-weight-500);
@@ -82,9 +85,18 @@ a.button-ghost:hover {
     border: none;
     border-radius: var(--border-radius-lg);
     cursor: pointer;
+    &.primary {
+        &:not(:disabled) {
+            background-color: var(--color-primary-700);
+            color: white;
+            &:hover {
+                background-color: var(--color-primary-800);
+                color: white;
+            }
+        }
+    }
 }
 a.button-small:link,
 a.button-small:visited {
     color: var(--color-button-small-text);
 }
-

+ 1 - 0
packages/admin-ui/src/lib/static/styles/global/_forms.scss

@@ -120,6 +120,7 @@ select {
 
 .ng-select .ng-select-container {
     background: var(--color-form-input-bg) !important;
+    color: var(--color-text-100);
     line-height: initial;
     position: initial !important;
 }

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