Browse Source

feat(admin-ui): Add support for tabbed custom fields

Relates to #724
Michael Bromley 4 years ago
parent
commit
b6cb16f023
57 changed files with 294 additions and 274 deletions
  1. 17 17
      packages/admin-ui/i18n-coverage.json
  2. 6 8
      packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.html
  3. 0 4
      packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts
  4. 28 21
      packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.html
  5. 0 4
      packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.ts
  6. 6 9
      packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html
  7. 0 4
      packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.ts
  8. 18 14
      packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.html
  9. 6 9
      packages/admin-ui/src/lib/catalog/src/components/update-product-option-dialog/update-product-option-dialog.component.html
  10. 12 14
      packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.html
  11. 9 10
      packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html
  12. 0 4
      packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.ts
  13. 4 0
      packages/admin-ui/src/lib/core/src/shared/components/rich-text-editor/rich-text-editor.component.scss
  14. 41 0
      packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.html
  15. 0 0
      packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.scss
  16. 45 0
      packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.ts
  17. 2 2
      packages/admin-ui/src/lib/core/src/shared/components/title-input/title-input.component.scss
  18. 3 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.scss
  19. 2 0
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  20. 5 8
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html
  21. 0 4
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts
  22. 5 8
      packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.html
  23. 6 8
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.html
  24. 0 4
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts
  25. 6 8
      packages/admin-ui/src/lib/settings/src/components/admin-detail/admin-detail.component.html
  26. 0 4
      packages/admin-ui/src/lib/settings/src/components/admin-detail/admin-detail.component.ts
  27. 9 11
      packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.html
  28. 0 4
      packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.ts
  29. 6 8
      packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.html
  30. 0 4
      packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.ts
  31. 6 8
      packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.html
  32. 0 4
      packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.ts
  33. 6 8
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html
  34. 0 4
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts
  35. 5 8
      packages/admin-ui/src/lib/settings/src/components/profile/profile.component.html
  36. 0 4
      packages/admin-ui/src/lib/settings/src/components/profile/profile.component.ts
  37. 6 8
      packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.html
  38. 0 4
      packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts
  39. 6 8
      packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.html
  40. 0 4
      packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.ts
  41. 6 8
      packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.html
  42. 0 4
      packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.ts
  43. 6 8
      packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.html
  44. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/cs.json
  45. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  46. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  47. 2 1
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  48. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/fr.json
  49. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/it.json
  50. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  51. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  52. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json
  53. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/ru.json
  54. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/uk.json
  55. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  56. 1 0
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json
  57. 3 2
      packages/common/src/shared-types.ts

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

@@ -1,69 +1,69 @@
 {
-  "generatedOn": "2021-10-07T12:23:24.068Z",
-  "lastCommit": "0eadedef0bfaacd2838befc67f75c14a5e7a6606",
+  "generatedOn": "2021-11-26T11:42:42.094Z",
+  "lastCommit": "cee98091eeb0fedc5b5269bdee8a8b237f0c7c8e",
   "translationStatus": {
     "cs": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 591,
       "percentage": 94
     },
     "de": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 570,
       "percentage": 91
     },
     "en": {
-      "tokenCount": 627,
-      "translatedCount": 626,
+      "tokenCount": 628,
+      "translatedCount": 627,
       "percentage": 100
     },
     "es": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 623,
       "percentage": 99
     },
     "fr": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 613,
       "percentage": 98
     },
     "it": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 621,
       "percentage": 99
     },
     "pl": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 405,
-      "percentage": 65
+      "percentage": 64
     },
     "pt_BR": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 588,
       "percentage": 94
     },
     "pt_PT": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 622,
       "percentage": 99
     },
     "ru": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 621,
       "percentage": 99
     },
     "uk": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 621,
       "percentage": 99
     },
     "zh_Hans": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 558,
       "percentage": 89
     },
     "zh_Hant": {
-      "tokenCount": 627,
+      "tokenCount": 628,
       "translatedCount": 385,
       "percentage": 61
     }

+ 6 - 8
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.html

@@ -79,14 +79,12 @@
 
             <section formGroupName="customFields" *ngIf="customFields.length">
                 <label>{{ 'common.custom-fields' | translate }}</label>
-                <ng-container *ngFor="let customField of customFields">
-                    <vdr-custom-field-control
-                        *ngIf="customFieldIsSet(customField.name)"
-                        entityName="Collection"
-                        [customFieldsFormGroup]="detailForm.get(['customFields'])"
-                        [customField]="customField"
-                    ></vdr-custom-field-control>
-                </ng-container>
+                <vdr-tabbed-custom-fields
+                    entityName="Collection"
+                    [customFields]="customFields"
+                    [customFieldsFormGroup]="detailForm.get(['customFields'])"
+                    [readonly]="!(updatePermission | hasPermission)"
+                ></vdr-tabbed-custom-fields>
             </section>
         </div>
         <div class="clr-col-md-auto">

+ 0 - 4
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts

@@ -94,10 +94,6 @@ export class CollectionDetailComponent
         return this.allFilters.find(f => f.code === filter.code);
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     assetsChanged(): boolean {
         return !!Object.values(this.assetChanges).length;
     }

+ 28 - 21
packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.html

@@ -73,14 +73,12 @@
 
         <section formGroupName="customFields" *ngIf="customFields.length">
             <label>{{ 'common.custom-fields' | translate }}</label>
-            <ng-container *ngFor="let customField of customFields">
-                <vdr-custom-field-control
-                    *ngIf="customFieldIsSet(customField.name)"
-                    entityName="Facet"
-                    [customFieldsFormGroup]="detailForm.get(['facet', 'customFields'])"
-                    [customField]="customField"
-                ></vdr-custom-field-control>
-            </ng-container>
+            <vdr-tabbed-custom-fields
+                entityName="Facet"
+                [customFields]="customFields"
+                [customFieldsFormGroup]="detailForm.get(['facet', 'customFields'])"
+                [readonly]="!(updatePermission | hasPermission)"
+            ></vdr-tabbed-custom-fields>
         </section>
     </section>
 
@@ -93,8 +91,8 @@
                     <th></th>
                     <th>{{ 'common.name' | translate }}</th>
                     <th>{{ 'common.code' | translate }}</th>
-                    <ng-container *ngFor="let customField of customValueFields">
-                        <th>{{ customField.name }}</th>
+                    <ng-container *ngIf="customValueFields.length">
+                        <th>{{ 'common.custom-fields' | translate }}</th>
                     </ng-container>
                     <th></th>
                 </tr>
@@ -117,17 +115,26 @@
                         />
                     </td>
                     <td class="align-middle"><input type="text" formControlName="code" readonly /></td>
-                    <ng-container *ngFor="let customField of customValueFields">
-                        <td class="align-middle">
-                            <vdr-custom-field-control
-                                *ngIf="customValueFieldIsSet(i, customField.name)"
-                                entityName="FacetValue"
-                                [showLabel]="false"
-                                [customFieldsFormGroup]="detailForm.get(['values', i, 'customFields'])"
-                                [customField]="customField"
-                            ></vdr-custom-field-control>
-                        </td>
-                    </ng-container>
+                    <td class="" *ngIf="customValueFields.length">
+<!--                    <ng-container *ngFor="let customField of customValueFields">-->
+<!--                       -->
+<!--                            <vdr-custom-field-control-->
+<!--                                *ngIf="customValueFieldIsSet(i, customField.name)"-->
+<!--                                entityName="FacetValue"-->
+<!--                                [showLabel]="false"-->
+<!--                                [customFieldsFormGroup]="detailForm.get(['values', i, 'customFields'])"-->
+<!--                                [customField]="customField"-->
+<!--                            ></vdr-custom-field-control>-->
+<!--                      -->
+<!--                    </ng-container>-->
+                        <vdr-tabbed-custom-fields
+                            entityName="FacetValue"
+                            [customFields]="customFields"
+                            [compact]="true"
+                            [customFieldsFormGroup]="detailForm.get(['values', i, 'customFields'])"
+                            [readonly]="!(updatePermission | hasPermission)"
+                        ></vdr-tabbed-custom-fields>
+                    </td>
                     <td class="align-middle">
                         <vdr-dropdown>
                             <button type="button" class="btn btn-link btn-sm" vdrDropdownTrigger>

+ 0 - 4
packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.ts

@@ -93,10 +93,6 @@ export class FacetDetailComponent
         }
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['facet', 'customFields', name]);
-    }
-
     customValueFieldIsSet(index: number, name: string): boolean {
         return !!this.detailForm.get(['values', index, 'customFields', name]);
     }

+ 6 - 9
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html

@@ -130,15 +130,12 @@
 
                             <section formGroupName="customFields" *ngIf="customFields.length">
                                 <label>{{ 'common.custom-fields' | translate }}</label>
-                                <ng-container *ngFor="let customField of customFields">
-                                    <vdr-custom-field-control
-                                        *ngIf="customFieldIsSet(customField.name)"
-                                        entityName="Product"
-                                        [customFieldsFormGroup]="detailForm.get(['product', 'customFields'])"
-                                        [customField]="customField"
-                                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
-                                    ></vdr-custom-field-control>
-                                </ng-container>
+                                <vdr-tabbed-custom-fields
+                                    entityName="Product"
+                                    [customFields]="customFields"
+                                    [customFieldsFormGroup]="detailForm.get(['product', 'customFields'])"
+                                    [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
+                                ></vdr-tabbed-custom-fields>
                             </section>
 
                             <div class="facets">

+ 0 - 4
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.ts

@@ -366,10 +366,6 @@ export class ProductDetailComponent
             );
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['product', 'customFields', name]);
-    }
-
     assetsChanged(): boolean {
         return !!Object.values(this.assetChanges).length;
     }

+ 18 - 14
packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.html

@@ -1,7 +1,11 @@
 <div class="variants-list">
     <div
         class="variant-container card"
-        *ngFor="let variant of variants | paginate: paginationConfig || { itemsPerPage: 10, currentPage: 1 }; trackBy: trackById; let i = index"
+        *ngFor="
+            let variant of variants | paginate: paginationConfig || { itemsPerPage: 10, currentPage: 1 };
+            trackBy: trackById;
+            let i = index
+        "
         [class.disabled]="!formGroupMap.get(variant.id)?.get('enabled')?.value"
     >
         <ng-container *ngIf="formGroupMap.get(variant.id) as formGroup" [formGroup]="formGroup">
@@ -43,7 +47,9 @@
                         <vdr-product-assets
                             [compact]="true"
                             [assets]="pendingAssetChanges[variant.id]?.assets || variant.assets"
-                            [featuredAsset]="pendingAssetChanges[variant.id]?.featuredAsset || variant.featuredAsset"
+                            [featuredAsset]="
+                                pendingAssetChanges[variant.id]?.featuredAsset || variant.featuredAsset
+                            "
                             (change)="onAssetChange(variant.id, $event)"
                         ></vdr-product-assets>
                     </div>
@@ -209,17 +215,13 @@
                         <div class="custom-fields">
                             <div class="variant-form-input-row">
                                 <section formGroupName="customFields" *ngIf="customFields.length">
-                                    <!--<label>{{ 'common.custom-fields' | translate }}</label>-->
-                                    <ng-container *ngFor="let customField of customFields">
-                                        <vdr-custom-field-control
-                                            *ngIf="formGroup.get(['customFields', customField.name])"
-                                            entityName="ProductVariant"
-                                            [compact]="true"
-                                            [customFieldsFormGroup]="formGroup.get('customFields')"
-                                            [readonly]="!(updatePermission | hasPermission)"
-                                            [customField]="customField"
-                                        ></vdr-custom-field-control>
-                                    </ng-container>
+                                    <vdr-tabbed-custom-fields
+                                        entityName="ProductVariant"
+                                        [customFields]="customFields"
+                                        [compact]="true"
+                                        [customFieldsFormGroup]="formGroup.get('customFields')"
+                                        [readonly]="!(updatePermission | hasPermission)"
+                                    ></vdr-tabbed-custom-fields>
                                 </section>
                             </div>
                         </div>
@@ -241,7 +243,9 @@
                                 <span class="option-group-name">{{ optionGroupName(option.groupId) }}</span>
                                 {{ optionName(option) }}
                             </vdr-chip>
-                            <a [routerLink]="['./', 'options']" class="btn btn-link btn-sm">{{ 'catalog.edit-options' | translate }}...</a>
+                            <a [routerLink]="['./', 'options']" class="btn btn-link btn-sm"
+                                >{{ 'catalog.edit-options' | translate }}...</a
+                            >
                         </div>
                     </div>
                     <div class="flex-spacer"></div>

+ 6 - 9
packages/admin-ui/src/lib/catalog/src/components/update-product-option-dialog/update-product-option-dialog.component.html

@@ -18,15 +18,12 @@
 </clr-checkbox-wrapper>
 <section *ngIf="customFields.length">
     <label>{{ 'common.custom-fields' | translate }}</label>
-    <ng-container *ngFor="let customField of customFields">
-        <vdr-custom-field-control
-            *ngIf="customFieldsForm.get(customField.name)"
-            entityName="ProductOption"
-            [customFieldsFormGroup]="customFieldsForm"
-            [customField]="customField"
-            [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
-        ></vdr-custom-field-control>
-    </ng-container>
+    <vdr-tabbed-custom-fields
+        entityName="ProductOption"
+        [customFields]="customFields"
+        [customFieldsFormGroup]="customFieldsForm"
+        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
+    ></vdr-tabbed-custom-fields>
 </section>
 
 <ng-template vdrDialogButtons>

+ 12 - 14
packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.html

@@ -1,20 +1,20 @@
 <form [formGroup]="formGroup">
     <clr-input-container>
         <label>{{ 'customer.full-name' | translate }}</label>
-        <input formControlName="fullName" type="text" clrInput />
+        <input formControlName="fullName" type="text" clrInput/>
     </clr-input-container>
 
     <div class="clr-row">
         <div class="clr-col-md-4">
             <clr-input-container>
                 <label>{{ 'customer.street-line-1' | translate }}</label>
-                <input formControlName="streetLine1" type="text" clrInput />
+                <input formControlName="streetLine1" type="text" clrInput/>
             </clr-input-container>
         </div>
         <div class="clr-col-md-4">
             <clr-input-container>
                 <label>{{ 'customer.street-line-2' | translate }}</label>
-                <input formControlName="streetLine2" type="text" clrInput />
+                <input formControlName="streetLine2" type="text" clrInput/>
             </clr-input-container>
         </div>
     </div>
@@ -22,13 +22,13 @@
         <div class="clr-col-md-4">
             <clr-input-container>
                 <label>{{ 'customer.city' | translate }}</label>
-                <input formControlName="city" type="text" clrInput />
+                <input formControlName="city" type="text" clrInput/>
             </clr-input-container>
         </div>
         <div class="clr-col-md-4">
             <clr-input-container>
                 <label>{{ 'customer.province' | translate }}</label>
-                <input formControlName="province" type="text" clrInput />
+                <input formControlName="province" type="text" clrInput/>
             </clr-input-container>
         </div>
     </div>
@@ -36,7 +36,7 @@
         <div class="clr-col-md-4">
             <clr-input-container>
                 <label>{{ 'customer.postal-code' | translate }}</label>
-                <input formControlName="postalCode" type="text" clrInput />
+                <input formControlName="postalCode" type="text" clrInput/>
             </clr-input-container>
         </div>
         <div class="clr-col-md-4">
@@ -52,16 +52,14 @@
     </div>
     <clr-input-container>
         <label>{{ 'customer.phone-number' | translate }}</label>
-        <input formControlName="phoneNumber" type="text" clrInput />
+        <input formControlName="phoneNumber" type="text" clrInput/>
     </clr-input-container>
     <section formGroupName="customFields" *ngIf="formGroup.get('customFields') as customFieldsGroup">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                entityName="Address"
-                [customFieldsFormGroup]="customFieldsGroup"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Address"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="customFieldsGroup"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 9 - 10
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html

@@ -101,15 +101,13 @@
     </form>
     <section *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Asset"
-                [compact]="true"
-                [customFieldsFormGroup]="customFieldsForm"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Asset"
+            [compact]="true"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="customFieldsForm"
+            [readonly]="!(['UpdateCatalog', 'UpdateAsset'] | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
     <div class="flex-spacer"></div>
     <div class="preview-select">
@@ -132,6 +130,7 @@
         [routerLink]="['/catalog', 'assets', asset.id]"
         (click)="editClick.emit()"
     >
-        <clr-icon shape="edit"></clr-icon> {{ 'common.edit' | translate }}
+        <clr-icon shape="edit"></clr-icon>
+        {{ 'common.edit' | translate }}
     </a>
 </div>

+ 0 - 4
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.ts

@@ -98,10 +98,6 @@ export class AssetPreviewComponent implements OnInit, OnDestroy {
         }
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.customFieldsForm?.get([name]);
-    }
-
     getSourceFileName(): string {
         const parts = this.asset.source.split('/');
         return parts[parts.length - 1];

+ 4 - 0
packages/admin-ui/src/lib/core/src/shared/components/rich-text-editor/rich-text-editor.component.scss

@@ -22,15 +22,19 @@
     background-color: var(--color-component-bg-200);
     color: var(--color-icon-button);
     padding: 6px 12px;
+    display: flex;
+    flex-wrap: wrap;
 }
 
 ::ng-deep .vdr-prosemirror {
     background: var(--color-form-input-bg);
     min-height: 128px;
+    min-width: 200px;
     border: 1px solid var(--color-component-border-200);
     border-radius: 0 0 3px 3px;
     transition: border-color 0.2s;
     overflow: auto;
+    text-align: initial;
 
     &:focus {
         border-color: var(--color-primary-500) !important;

+ 41 - 0
packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.html

@@ -0,0 +1,41 @@
+<ng-container *ngIf="1 < tabbedCustomFields.length; else singleGroup">
+    <clr-tabs>
+        <clr-tab *ngFor="let group of tabbedCustomFields">
+            <button clrTabLink>
+                {{
+                group.tabName === defaultTabName
+                    ? ('common.general' | translate)
+                    : (group.tabName | translate)
+                }}
+            </button>
+            <clr-tab-content *clrIfActive>
+                <div class="mt4">
+                    <ng-container *ngFor="let customField of group.customFields">
+                        <vdr-custom-field-control
+                            *ngIf="customFieldIsSet(customField.name)"
+                            [entityName]="entityName"
+                            [customFieldsFormGroup]="customFieldsFormGroup"
+                            [customField]="customField"
+                            [readonly]="readonly"
+                            [compact]="compact"
+                            [showLabel]="showLabel"
+                        ></vdr-custom-field-control>
+                    </ng-container>
+                </div>
+            </clr-tab-content>
+        </clr-tab>
+    </clr-tabs>
+</ng-container>
+<ng-template #singleGroup>
+    <ng-container *ngFor="let customField of tabbedCustomFields[0]?.customFields">
+        <vdr-custom-field-control
+            *ngIf="customFieldIsSet(customField.name)"
+            [entityName]="entityName"
+            [customFieldsFormGroup]="customFieldsFormGroup"
+            [customField]="customField"
+            [readonly]="readonly"
+            [compact]="compact"
+            [showLabel]="showLabel"
+        ></vdr-custom-field-control>
+    </ng-container>
+</ng-template>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.scss


+ 45 - 0
packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.ts

@@ -0,0 +1,45 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { AbstractControl, FormGroup } from '@angular/forms';
+
+import { CustomFieldConfig } from '../../../common/generated-types';
+import { CustomFieldEntityName } from '../../../providers/custom-field-component/custom-field-component.service';
+
+export type GroupedCustomFields = Array<{ tabName: string; customFields: CustomFieldConfig[] }>;
+
+@Component({
+    selector: 'vdr-tabbed-custom-fields',
+    templateUrl: './tabbed-custom-fields.component.html',
+    styleUrls: ['./tabbed-custom-fields.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class TabbedCustomFieldsComponent implements OnInit {
+    @Input() entityName: CustomFieldEntityName;
+    @Input() customFields: CustomFieldConfig[];
+    @Input() customFieldsFormGroup: AbstractControl;
+    @Input() readonly = false;
+    @Input() compact = false;
+    @Input() showLabel = true;
+    readonly defaultTabName = '__default_tab__';
+    tabbedCustomFields: GroupedCustomFields;
+
+    ngOnInit(): void {
+        this.tabbedCustomFields = this.groupByTabs(this.customFields);
+    }
+
+    customFieldIsSet(name: string): boolean {
+        return !!this.customFieldsFormGroup.get(name);
+    }
+
+    private groupByTabs(customFieldConfigs: CustomFieldConfig[]): GroupedCustomFields {
+        const tabMap = new Map<string, CustomFieldConfig[]>();
+        for (const field of customFieldConfigs) {
+            const tabName = field.ui.tab ?? this.defaultTabName;
+            if (tabMap.has(tabName)) {
+                tabMap.get(tabName)?.push(field);
+            } else {
+                tabMap.set(tabName, [field]);
+            }
+        }
+        return Array.from(tabMap.entries()).map(([tabName, customFields]) => ({ tabName, customFields }));
+    }
+}

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/components/title-input/title-input.component.scss

@@ -9,7 +9,7 @@
         max-width: 100%;
         &:not(:focus) {
             border-color: transparent !important;
-            background-color: transparent !important;
+            background-color: var(--color-component-bg-100) !important;
         }
         &:focus {
             background-color: var(--clr-global-app-background);
@@ -28,7 +28,7 @@
         position: absolute;
         right: 8px;
         top: 6px;
-        color: var(--color-grey-300);
+        color: var(--color-grey-400);
         opacity: 0;
         transition: opacity 0.2s;
     }

+ 3 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.scss

@@ -12,6 +12,9 @@
     height: 340px;
     letter-spacing: normal;
     line-height: 20px;
+    resize: both;
+    text-align: initial;
+    min-width: 200px;
 
     &:focus {
         border-color: var(--color-primary-500);

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

@@ -74,6 +74,7 @@ import { RichTextEditorComponent } from './components/rich-text-editor/rich-text
 import { SelectToggleComponent } from './components/select-toggle/select-toggle.component';
 import { SimpleDialogComponent } from './components/simple-dialog/simple-dialog.component';
 import { StatusBadgeComponent } from './components/status-badge/status-badge.component';
+import { TabbedCustomFieldsComponent } from './components/tabbed-custom-fields/tabbed-custom-fields.component';
 import { TableRowActionComponent } from './components/table-row-action/table-row-action.component';
 import { TagSelectorComponent } from './components/tag-selector/tag-selector.component';
 import { TimelineEntryComponent } from './components/timeline-entry/timeline-entry.component';
@@ -223,6 +224,7 @@ const DECLARATIONS = [
     RelationSelectorDialogComponent,
     RelationCardComponent,
     StatusBadgeComponent,
+    TabbedCustomFieldsComponent,
 ];
 
 const DYNAMIC_FORM_INPUTS = [

+ 5 - 8
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html

@@ -70,14 +70,11 @@
 
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Customer"
-                [customFieldsFormGroup]="detailForm.get(['customer', 'customFields'])"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Customer"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get(['customer', 'customFields'])"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>
 

+ 0 - 4
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts

@@ -129,10 +129,6 @@ export class CustomerDetailComponent
         this.orderListUpdates$.complete();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customer', 'customFields', name]);
-    }
-
     getAddressFormControls(): FormControl[] {
         const formArray = this.detailForm.get(['addresses']) as FormArray;
         return formArray.controls as FormControl[];

+ 5 - 8
packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.html

@@ -13,14 +13,11 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customField.name"
-                entityName="CustomerGroup"
-                [customFieldsFormGroup]="form.get(['customFields'])"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="CustomerGroup"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="form.get('customFields')"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>
 <ng-template vdrDialogButtons>

+ 6 - 8
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.html

@@ -67,14 +67,12 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Promotion"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Promotion"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!('UpdatePromotion' | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 
     <div class="clr-row">

+ 0 - 4
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts

@@ -81,10 +81,6 @@ export class PromotionDetailComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     getAvailableConditions(): ConfigurableOperationDefinition[] {
         return this.allConditions.filter(o => !this.conditions.find(c => c.code === o.code));
     }

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/admin-detail/admin-detail.component.html

@@ -63,14 +63,12 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Administrator"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Administrator"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!('UpdateAdministrator' | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
     <label class="clr-control-label">{{ 'settings.roles' | translate }}</label>
     <ng-select

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/admin-detail/admin-detail.component.ts

@@ -93,10 +93,6 @@ export class AdminDetailComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     rolesChanged(roles: Role[]) {
         this.buildPermissionsMap();
     }

+ 9 - 11
packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.html

@@ -28,10 +28,10 @@
 
 <form class="form" [formGroup]="detailForm">
     <vdr-form-field [label]="'common.code' | translate" for="code">
-        <input id="code" type="text" [readonly]="!(updatePermission | hasPermission)" formControlName="code" />
+        <input id="code" type="text" [readonly]="!(updatePermission | hasPermission)" formControlName="code"/>
     </vdr-form-field>
     <vdr-form-field [label]="'settings.channel-token' | translate" for="token">
-        <input id="token" type="text" [readonly]="!(updatePermission | hasPermission)" formControlName="token" />
+        <input id="token" type="text" [readonly]="!(updatePermission | hasPermission)" formControlName="token"/>
     </vdr-form-field>
     <vdr-form-field [label]="'settings.currency' | translate" for="defaultTaxZoneId">
         <select
@@ -96,7 +96,7 @@
             formControlName="defaultShippingZoneId"
             [vdrDisabled]="!(updatePermission | hasPermission)"
         >
-            <option selected value style="display: none"></option>            
+            <option selected value style="display: none"></option>
             <option *ngFor="let zone of zones$ | async" [value]="zone.id">{{ zone.name }}</option>
         </select>
     </vdr-form-field>
@@ -114,13 +114,11 @@
 
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Channel"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Channel"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.ts

@@ -70,10 +70,6 @@ export class ChannelDetailComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     saveButtonEnabled(): boolean {
         return this.detailForm.dirty && this.detailForm.valid;
     }

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.html

@@ -61,13 +61,11 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Country"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Country"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.ts

@@ -64,10 +64,6 @@ export class CountryDetailComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     create() {
         if (!this.detailForm.dirty) {
             return;

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.html

@@ -62,13 +62,11 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="GlobalSettings"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="GlobalSettings"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.ts

@@ -59,10 +59,6 @@ export class GlobalSettingsComponent extends BaseDetailComponent<GlobalSettings>
         });
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     save() {
         if (!this.detailForm.dirty) {
             return;

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html

@@ -66,14 +66,12 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="PaymentMethod"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="PaymentMethod"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 
     <div class="clr-row mt4">

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts

@@ -99,10 +99,6 @@ export class PaymentMethodDetailComponent
         }
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     configArgsIsPopulated(): boolean {
         const configArgsGroup = this.detailForm.get('configArgs') as FormGroup | undefined;
         if (!configArgsGroup) {

+ 5 - 8
packages/admin-ui/src/lib/settings/src/components/profile/profile.component.html

@@ -32,13 +32,10 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="Administrator"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="Administrator"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/profile/profile.component.ts

@@ -58,10 +58,6 @@ export class ProfileComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     save() {
         this.entity$
             .pipe(

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.html

@@ -75,14 +75,12 @@
 
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="ShippingMethod"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="ShippingMethod"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 
     <div class="clr-row mt4">

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts

@@ -136,10 +136,6 @@ export class ShippingMethodDetailComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     updateCode(currentCode: string, nameValue: string) {
         if (!currentCode) {
             const codeControl = this.detailForm.get(['code']);

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.html

@@ -48,13 +48,11 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="TaxCategory"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="TaxCategory"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.ts

@@ -67,10 +67,6 @@ export class TaxCategoryDetailComponent
         return this.detailForm.dirty && this.detailForm.valid;
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     create() {
         if (!this.detailForm.dirty) {
             return;

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.html

@@ -81,13 +81,11 @@
     </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                *ngIf="customFieldIsSet(customField.name)"
-                entityName="TaxRate"
-                [customFieldsFormGroup]="detailForm.get('customFields')"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
+        <vdr-tabbed-custom-fields
+            entityName="TaxRate"
+            [customFields]="customFields"
+            [customFieldsFormGroup]="detailForm.get('customFields')"
+            [readonly]="!(updatePermission | hasPermission)"
+        ></vdr-tabbed-custom-fields>
     </section>
 </form>

+ 0 - 4
packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.ts

@@ -73,10 +73,6 @@ export class TaxRateDetailComponent
         this.destroy();
     }
 
-    customFieldIsSet(name: string): boolean {
-        return !!this.detailForm.get(['customFields', name]);
-    }
-
     saveButtonEnabled(): boolean {
         return this.detailForm.dirty && this.detailForm.valid;
     }

+ 6 - 8
packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.html

@@ -11,14 +11,12 @@
             [readonly]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)"
         />
     </vdr-form-field>
-    <ng-container *ngFor="let customField of customFields">
-        <vdr-custom-field-control
-            *ngIf="customField.name"
-            entityName="Zone"
-            [customFieldsFormGroup]="form.get(['customFields'])"
-            [customField]="customField"
-        ></vdr-custom-field-control>
-    </ng-container>
+    <vdr-tabbed-custom-fields
+        entityName="Zone"
+        [customFields]="customFields"
+        [customFieldsFormGroup]="form.get('customFields')"
+        [readonly]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)"
+    ></vdr-tabbed-custom-fields>
 </form>
 
 <ng-template vdrDialogButtons>

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

@@ -199,6 +199,7 @@
     "expand-entries": "Otevřít vstupy",
     "extension-running-in-separate-window": "Rozšíření běží v novém okně",
     "filter": "",
+    "general": "",
     "guest": "Host",
     "items-per-page-option": "{ count } na stránku",
     "language": "Jazyk",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Einträge erweitern",
     "extension-running-in-separate-window": "Die Erweiterung läuft in einem separaten Fenster",
     "filter": "Filter",
+    "general": "",
     "guest": "Gast",
     "items-per-page-option": "{ count } pro Seite",
     "language": "Sprache",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Expand entries",
     "extension-running-in-separate-window": "Extension is running in a separate window",
     "filter": "Filter",
+    "general": "General",
     "guest": "Guest",
     "items-per-page-option": "{ count } per page",
     "language": "Language",

+ 2 - 1
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -199,6 +199,7 @@
     "expand-entries": "Mostrar entradas",
     "extension-running-in-separate-window": "La extensión se está ejecutando en una nueva ventana",
     "filter": "Filtro",
+    "general": "",
     "guest": "Invitado",
     "items-per-page-option": "{ count } por página",
     "language": "Idioma",
@@ -658,4 +659,4 @@
     "job-result": "Resultado",
     "job-state": "Estado"
   }
-}
+}

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

@@ -199,6 +199,7 @@
     "expand-entries": "Développer les éléments",
     "extension-running-in-separate-window": "Extension fonctionne dans une fenêtre à part",
     "filter": "Filtre",
+    "general": "",
     "guest": "Invité",
     "items-per-page-option": "{ count } par page",
     "language": "Langue",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Espandi elementi",
     "extension-running-in-separate-window": "L'estensione è in esecuzione in un'altra finestra",
     "filter": "Filtra",
+    "general": "",
     "guest": "Ospite",
     "items-per-page-option": "{ count } per pagina",
     "language": "Lingua",

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

@@ -199,6 +199,7 @@
     "expand-entries": "",
     "extension-running-in-separate-window": "Rozszerzenie jest włączone w innym oknie",
     "filter": "",
+    "general": "",
     "guest": "Gość",
     "items-per-page-option": "{ count } na stronę",
     "language": "Język",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Expandir entradas",
     "extension-running-in-separate-window": "A extensão está sendo executada em uma janela separada",
     "filter": "Filtro",
+    "general": "",
     "guest": "Convidado",
     "items-per-page-option": "{ count } por página",
     "language": "Idioma",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Expandir entradas",
     "extension-running-in-separate-window": "A extensão está a ser executada em uma janela separada",
     "filter": "Filtro",
+    "general": "",
     "guest": "Convidado",
     "items-per-page-option": "{ count } por página",
     "language": "Idioma",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Развернуть записи",
     "extension-running-in-separate-window": "Расширение работает в отдельном окне",
     "filter": "Фильтр",
+    "general": "",
     "guest": "Гость",
     "items-per-page-option": "{ count } на странице",
     "language": "Язык",

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

@@ -199,6 +199,7 @@
     "expand-entries": "Розгорнути записи",
     "extension-running-in-separate-window": "Розширення працює в окремому вікні",
     "filter": "Фільтр",
+    "general": "",
     "guest": "Гість",
     "items-per-page-option": "{ count } на сторінці",
     "language": "Мова",

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

@@ -199,6 +199,7 @@
     "expand-entries": "展开",
     "extension-running-in-separate-window": "扩展已在另一个窗口启动",
     "filter": "过滤",
+    "general": "",
     "guest": "游客",
     "items-per-page-option": "每页显示 { count } 条",
     "language": "语言",

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

@@ -199,6 +199,7 @@
     "expand-entries": "",
     "extension-running-in-separate-window": "扩展已在另一個窗口启動",
     "filter": "",
+    "general": "",
     "guest": "游客",
     "items-per-page-option": "每页顯示 { count } 條",
     "language": "語言",

+ 3 - 2
packages/common/src/shared-types.ts

@@ -174,7 +174,7 @@ type DefaultFormConfigHash = {
 
 export type DefaultFormComponentConfig<T extends DefaultFormComponentId> = DefaultFormConfigHash[T];
 
-export type UiComponentConfig =
+export type UiComponentConfig = (
     | ({ component: 'boolean-form-input' } & DefaultFormComponentConfig<'boolean-form-input'>)
     | ({ component: 'currency-form-input' } & DefaultFormComponentConfig<'currency-form-input'>)
     | ({ component: 'customer-group-form-input' } & DefaultFormComponentConfig<'customer-group-form-input'>)
@@ -191,7 +191,8 @@ export type UiComponentConfig =
     | ({ component: 'select-form-input' } & DefaultFormComponentConfig<'select-form-input'>)
     | ({ component: 'text-form-input' } & DefaultFormComponentConfig<'text-form-input'>)
     | ({ component: 'textarea-form-input' } & DefaultFormComponentConfig<'textarea-form-input'>)
-    | { component: string; [prop: string]: any };
+    | { component: string; [prop: string]: any }
+) & { tab?: string };
 
 export type CustomFieldsObject = { [key: string]: any };