@masterteam/customization 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, computed, input, signal, effect, Component } from '@angular/core';
2
+ import { inject, Injectable, computed, input, signal, effect, Component, output } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
5
5
  import { TranslocoDirective } from '@jsverse/transloco';
@@ -10,10 +10,12 @@ import { Button } from '@masterteam/components/button';
10
10
  import { Tabs } from '@masterteam/components/tabs';
11
11
  import { EntitiesManage } from '@masterteam/components/entities';
12
12
  import { ModalService } from '@masterteam/components/modal';
13
+ import { Icon } from '@masterteam/icons';
13
14
  import { Skeleton } from 'primeng/skeleton';
14
15
  import { HttpClient } from '@angular/common/http';
15
16
  import { Action, Selector, State, Store, select } from '@ngxs/store';
16
17
  import { CrudStateBase, handleApiRequest } from '@masterteam/components';
18
+ import { DOCUMENT } from '@angular/common';
17
19
  import { ModalRef } from '@masterteam/components/dialog';
18
20
 
19
21
  /** ViewTypes that support the 'showBorder' toggle */
@@ -248,8 +250,8 @@ let CustomizationState = class CustomizationState extends CrudStateBase {
248
250
  }
249
251
  return contextParts.join('/');
250
252
  }
251
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: CustomizationState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
252
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: CustomizationState });
253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
254
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState });
253
255
  };
254
256
  __decorate([
255
257
  Action(GetManagePreviewAreas)
@@ -293,7 +295,7 @@ CustomizationState = __decorate([
293
295
  defaults: DEFAULT_STATE,
294
296
  })
295
297
  ], CustomizationState);
296
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: CustomizationState, decorators: [{
298
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState, decorators: [{
297
299
  type: Injectable
298
300
  }], propDecorators: { getAreas: [], getProperties: [], getConfigurations: [], setModuleInfo: [], bulkReplaceConfigurations: [] } });
299
301
  function mapCatalogPropertyToManagePreview(property) {
@@ -329,17 +331,17 @@ class CustomizationFacade {
329
331
  // ============================================================================
330
332
  // Loading Signals - Computed from slice (minimal reactivity)
331
333
  // ============================================================================
332
- isLoadingProperties = computed(() => this.loadingActive().includes(CustomizationActionKey.GetProperties), ...(ngDevMode ? [{ debugName: "isLoadingProperties" }] : []));
333
- isLoadingAreas = computed(() => this.loadingActive().includes(CustomizationActionKey.GetAreas), ...(ngDevMode ? [{ debugName: "isLoadingAreas" }] : []));
334
- isLoadingConfigurations = computed(() => this.loadingActive().includes(CustomizationActionKey.GetConfigurations), ...(ngDevMode ? [{ debugName: "isLoadingConfigurations" }] : []));
335
- isBulkReplacing = computed(() => this.loadingActive().includes(CustomizationActionKey.BulkReplaceConfigurations), ...(ngDevMode ? [{ debugName: "isBulkReplacing" }] : []));
334
+ isLoadingProperties = computed(() => this.loadingActive().includes(CustomizationActionKey.GetProperties), ...(ngDevMode ? [{ debugName: "isLoadingProperties" }] : /* istanbul ignore next */ []));
335
+ isLoadingAreas = computed(() => this.loadingActive().includes(CustomizationActionKey.GetAreas), ...(ngDevMode ? [{ debugName: "isLoadingAreas" }] : /* istanbul ignore next */ []));
336
+ isLoadingConfigurations = computed(() => this.loadingActive().includes(CustomizationActionKey.GetConfigurations), ...(ngDevMode ? [{ debugName: "isLoadingConfigurations" }] : /* istanbul ignore next */ []));
337
+ isBulkReplacing = computed(() => this.loadingActive().includes(CustomizationActionKey.BulkReplaceConfigurations), ...(ngDevMode ? [{ debugName: "isBulkReplacing" }] : /* istanbul ignore next */ []));
336
338
  // ============================================================================
337
339
  // Error Signals - Computed from slice (minimal reactivity)
338
340
  // ============================================================================
339
- propertiesError = computed(() => this.errors()[CustomizationActionKey.GetProperties] ?? null, ...(ngDevMode ? [{ debugName: "propertiesError" }] : []));
340
- areasError = computed(() => this.errors()[CustomizationActionKey.GetAreas] ?? null, ...(ngDevMode ? [{ debugName: "areasError" }] : []));
341
- configurationsError = computed(() => this.errors()[CustomizationActionKey.GetConfigurations] ?? null, ...(ngDevMode ? [{ debugName: "configurationsError" }] : []));
342
- bulkReplaceError = computed(() => this.errors()[CustomizationActionKey.BulkReplaceConfigurations] ?? null, ...(ngDevMode ? [{ debugName: "bulkReplaceError" }] : []));
341
+ propertiesError = computed(() => this.errors()[CustomizationActionKey.GetProperties] ?? null, ...(ngDevMode ? [{ debugName: "propertiesError" }] : /* istanbul ignore next */ []));
342
+ areasError = computed(() => this.errors()[CustomizationActionKey.GetAreas] ?? null, ...(ngDevMode ? [{ debugName: "areasError" }] : /* istanbul ignore next */ []));
343
+ configurationsError = computed(() => this.errors()[CustomizationActionKey.GetConfigurations] ?? null, ...(ngDevMode ? [{ debugName: "configurationsError" }] : /* istanbul ignore next */ []));
344
+ bulkReplaceError = computed(() => this.errors()[CustomizationActionKey.BulkReplaceConfigurations] ?? null, ...(ngDevMode ? [{ debugName: "bulkReplaceError" }] : /* istanbul ignore next */ []));
343
345
  // ============================================================================
344
346
  // Action Dispatchers
345
347
  // ============================================================================
@@ -358,10 +360,10 @@ class CustomizationFacade {
358
360
  setModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
359
361
  return this.store.dispatch(new SetManagePreviewModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath));
360
362
  }
361
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: CustomizationFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
362
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: CustomizationFacade, providedIn: 'root' });
363
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
364
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationFacade, providedIn: 'root' });
363
365
  }
364
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: CustomizationFacade, decorators: [{
366
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationFacade, decorators: [{
365
367
  type: Injectable,
366
368
  args: [{
367
369
  providedIn: 'root',
@@ -372,31 +374,38 @@ class PropertyConfigDrawer {
372
374
  modalService = inject(ModalService);
373
375
  ref = inject(ModalRef);
374
376
  facade = inject(CustomizationFacade);
377
+ doc = inject(DOCUMENT);
375
378
  // ─── Inputs (passed via drawer inputValues) ───
376
- propertyKey = input('', ...(ngDevMode ? [{ debugName: "propertyKey" }] : []));
377
- propertyName = input('', ...(ngDevMode ? [{ debugName: "propertyName" }] : []));
378
- viewType = input('Text', ...(ngDevMode ? [{ debugName: "viewType" }] : []));
379
- areaKey = input('card', ...(ngDevMode ? [{ debugName: "areaKey" }] : []));
380
- currentOrder = input(1, ...(ngDevMode ? [{ debugName: "currentOrder" }] : []));
379
+ propertyKey = input('', ...(ngDevMode ? [{ debugName: "propertyKey" }] : /* istanbul ignore next */ []));
380
+ propertyName = input('', ...(ngDevMode ? [{ debugName: "propertyName" }] : /* istanbul ignore next */ []));
381
+ viewType = input('Text', ...(ngDevMode ? [{ debugName: "viewType" }] : /* istanbul ignore next */ []));
382
+ areaKey = input('card', ...(ngDevMode ? [{ debugName: "areaKey" }] : /* istanbul ignore next */ []));
383
+ currentOrder = input(1, ...(ngDevMode ? [{ debugName: "currentOrder" }] : /* istanbul ignore next */ []));
381
384
  initialConfig = input({
382
385
  hideName: false,
383
386
  labelPosition: 'bottom',
384
387
  showBorder: false,
388
+ alignEnd: false,
385
389
  showDisplayName: true,
386
390
  showPhoneNumber: false,
387
391
  showEmail: false,
388
- }, ...(ngDevMode ? [{ debugName: "initialConfig" }] : []));
392
+ }, ...(ngDevMode ? [{ debugName: "initialConfig" }] : /* istanbul ignore next */ []));
389
393
  // ─── UI state ───
390
- submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
394
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
391
395
  hideNameControl = new FormControl(false);
392
396
  labelOnTopControl = new FormControl(false);
393
397
  showBorderControl = new FormControl(false);
398
+ alignEndControl = new FormControl(false);
394
399
  showDisplayNameControl = new FormControl(false);
395
400
  showPhoneNumberControl = new FormControl(false);
396
401
  showEmailControl = new FormControl(false);
397
402
  // ─── Computed visibility flags ───
398
- isBorderViewType = computed(() => BORDER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isBorderViewType" }] : []));
399
- isUserViewType = computed(() => USER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isUserViewType" }] : []));
403
+ isBorderViewType = computed(() => BORDER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isBorderViewType" }] : /* istanbul ignore next */ []));
404
+ isUserViewType = computed(() => USER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isUserViewType" }] : /* istanbul ignore next */ []));
405
+ /** True when the document direction is RTL */
406
+ isRtl = signal(this.doc.documentElement.getAttribute('dir') === 'rtl', ...(ngDevMode ? [{ debugName: "isRtl" }] : /* istanbul ignore next */ []));
407
+ /** Align-end icon: right in LTR, left in RTL */
408
+ alignEndIcon = computed(() => this.isRtl() ? 'layout.align-left-01' : 'layout.align-right-01', ...(ngDevMode ? [{ debugName: "alignEndIcon" }] : /* istanbul ignore next */ []));
400
409
  constructor() {
401
410
  // Initialize form from input config
402
411
  effect(() => {
@@ -404,6 +413,7 @@ class PropertyConfigDrawer {
404
413
  this.hideNameControl.patchValue(config.hideName ?? false);
405
414
  this.labelOnTopControl.patchValue(config.labelPosition === 'top');
406
415
  this.showBorderControl.patchValue(config.showBorder ?? false);
416
+ this.alignEndControl.patchValue(config.alignEnd ?? false);
407
417
  this.showDisplayNameControl.patchValue(config.showDisplayName ?? true);
408
418
  this.showPhoneNumberControl.patchValue(config.showPhoneNumber ?? false);
409
419
  this.showEmailControl.patchValue(config.showEmail ?? false);
@@ -427,6 +437,7 @@ class PropertyConfigDrawer {
427
437
  configuration['labelPosition'] = this.labelOnTopControl.value
428
438
  ? 'top'
429
439
  : 'bottom';
440
+ configuration['alignEnd'] = this.alignEndControl.value ?? false;
430
441
  if (this.isBorderViewType()) {
431
442
  configuration['showBorder'] = this.showBorderControl.value ?? false;
432
443
  }
@@ -475,33 +486,94 @@ class PropertyConfigDrawer {
475
486
  onCancel() {
476
487
  this.ref.close(null);
477
488
  }
478
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PropertyConfigDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
479
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: PropertyConfigDrawer, isStandalone: true, selector: "mt-property-config-drawer", inputs: { propertyKey: { classPropertyName: "propertyKey", publicName: "propertyKey", isSignal: true, isRequired: false, transformFunction: null }, propertyName: { classPropertyName: "propertyName", publicName: "propertyName", isSignal: true, isRequired: false, transformFunction: null }, viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: true, isRequired: false, transformFunction: null }, areaKey: { classPropertyName: "areaKey", publicName: "areaKey", isSignal: true, isRequired: false, transformFunction: null }, currentOrder: { classPropertyName: "currentOrder", publicName: "currentOrder", isSignal: true, isRequired: false, transformFunction: null }, initialConfig: { classPropertyName: "initialConfig", publicName: "initialConfig", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\n <!-- Hide Name Toggle (all entity types) -->\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('hide-name')\"\n icon=\"general.eye-off\"\n [formControl]=\"hideNameControl\"\n ></mt-toggle-field>\n\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('label-on-top')\"\n [formControl]=\"labelOnTopControl\"\n ></mt-toggle-field>\n\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\n @if (isBorderViewType()) {\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-border')\"\n icon=\"layout.layout-grid-01\"\n [formControl]=\"showBorderControl\"\n ></mt-toggle-field>\n }\n\n <!-- User-specific Toggles -->\n @if (isUserViewType()) {\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-display-name')\"\n icon=\"user.user-circle\"\n [formControl]=\"showDisplayNameControl\"\n ></mt-toggle-field>\n\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-phone-number')\"\n icon=\"communication.phone\"\n [formControl]=\"showPhoneNumberControl\"\n ></mt-toggle-field>\n\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-email')\"\n icon=\"communication.mail-01\"\n [formControl]=\"showEmailControl\"\n ></mt-toggle-field>\n }\n </div>\n </div>\n\n <div [class]=\"modalService.footerClass\">\n <mt-button\n [label]=\"t('cancel')\"\n severity=\"secondary\"\n [disabled]=\"submitting()\"\n (onClick)=\"onCancel()\"\n ></mt-button>\n <mt-button\n [label]=\"t('save')\"\n severity=\"primary\"\n [loading]=\"submitting()\"\n (onClick)=\"onSave()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
489
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PropertyConfigDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
490
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PropertyConfigDrawer, isStandalone: true, selector: "mt-property-config-drawer", inputs: { propertyKey: { classPropertyName: "propertyKey", publicName: "propertyKey", isSignal: true, isRequired: false, transformFunction: null }, propertyName: { classPropertyName: "propertyName", publicName: "propertyName", isSignal: true, isRequired: false, transformFunction: null }, viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: true, isRequired: false, transformFunction: null }, areaKey: { classPropertyName: "areaKey", publicName: "areaKey", isSignal: true, isRequired: false, transformFunction: null }, currentOrder: { classPropertyName: "currentOrder", publicName: "currentOrder", isSignal: true, isRequired: false, transformFunction: null }, initialConfig: { classPropertyName: "initialConfig", publicName: "initialConfig", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n <!-- Hide Name Toggle (all entity types) -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('hide-name')\"\r\n icon=\"general.eye-off\"\r\n [formControl]=\"hideNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('label-on-top')\"\r\n icon=\"layout.align-right-02\"\r\n [formControl]=\"labelOnTopControl\"\r\n ></mt-toggle-field>\r\n\r\n <!-- Align End Toggle (all entity types) -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('align-end')\"\r\n [icon]=\"alignEndIcon()\"\r\n [formControl]=\"alignEndControl\"\r\n ></mt-toggle-field>\r\n\r\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\r\n @if (isBorderViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-border')\"\r\n icon=\"layout.layout-grid-01\"\r\n [formControl]=\"showBorderControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- User-specific Toggles -->\r\n @if (isUserViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-display-name')\"\r\n icon=\"user.user-circle\"\r\n [formControl]=\"showDisplayNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-phone-number')\"\r\n icon=\"communication.phone\"\r\n [formControl]=\"showPhoneNumberControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-email')\"\r\n icon=\"communication.mail-01\"\r\n [formControl]=\"showEmailControl\"\r\n ></mt-toggle-field>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
480
491
  }
481
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PropertyConfigDrawer, decorators: [{
492
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PropertyConfigDrawer, decorators: [{
482
493
  type: Component,
483
- args: [{ selector: 'mt-property-config-drawer', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, Button, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\n <!-- Hide Name Toggle (all entity types) -->\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('hide-name')\"\n icon=\"general.eye-off\"\n [formControl]=\"hideNameControl\"\n ></mt-toggle-field>\n\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('label-on-top')\"\n [formControl]=\"labelOnTopControl\"\n ></mt-toggle-field>\n\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\n @if (isBorderViewType()) {\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-border')\"\n icon=\"layout.layout-grid-01\"\n [formControl]=\"showBorderControl\"\n ></mt-toggle-field>\n }\n\n <!-- User-specific Toggles -->\n @if (isUserViewType()) {\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-display-name')\"\n icon=\"user.user-circle\"\n [formControl]=\"showDisplayNameControl\"\n ></mt-toggle-field>\n\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-phone-number')\"\n icon=\"communication.phone\"\n [formControl]=\"showPhoneNumberControl\"\n ></mt-toggle-field>\n\n <mt-toggle-field\n toggleShape=\"card\"\n [label]=\"t('show-email')\"\n icon=\"communication.mail-01\"\n [formControl]=\"showEmailControl\"\n ></mt-toggle-field>\n }\n </div>\n </div>\n\n <div [class]=\"modalService.footerClass\">\n <mt-button\n [label]=\"t('cancel')\"\n severity=\"secondary\"\n [disabled]=\"submitting()\"\n (onClick)=\"onCancel()\"\n ></mt-button>\n <mt-button\n [label]=\"t('save')\"\n severity=\"primary\"\n [loading]=\"submitting()\"\n (onClick)=\"onSave()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
494
+ args: [{ selector: 'mt-property-config-drawer', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, Button, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n <!-- Hide Name Toggle (all entity types) -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('hide-name')\"\r\n icon=\"general.eye-off\"\r\n [formControl]=\"hideNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('label-on-top')\"\r\n icon=\"layout.align-right-02\"\r\n [formControl]=\"labelOnTopControl\"\r\n ></mt-toggle-field>\r\n\r\n <!-- Align End Toggle (all entity types) -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('align-end')\"\r\n [icon]=\"alignEndIcon()\"\r\n [formControl]=\"alignEndControl\"\r\n ></mt-toggle-field>\r\n\r\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\r\n @if (isBorderViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-border')\"\r\n icon=\"layout.layout-grid-01\"\r\n [formControl]=\"showBorderControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- User-specific Toggles -->\r\n @if (isUserViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-display-name')\"\r\n icon=\"user.user-circle\"\r\n [formControl]=\"showDisplayNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-phone-number')\"\r\n icon=\"communication.phone\"\r\n [formControl]=\"showPhoneNumberControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-email')\"\r\n icon=\"communication.mail-01\"\r\n [formControl]=\"showEmailControl\"\r\n ></mt-toggle-field>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
484
495
  }], ctorParameters: () => [], propDecorators: { propertyKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyKey", required: false }] }], propertyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyName", required: false }] }], viewType: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewType", required: false }] }], areaKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "areaKey", required: false }] }], currentOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentOrder", required: false }] }], initialConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialConfig", required: false }] }] } });
485
496
 
497
+ class LeafDetailsSelector {
498
+ property = input.required(...(ngDevMode ? [{ debugName: "property" }] : /* istanbul ignore next */ []));
499
+ leafLevels = input({}, ...(ngDevMode ? [{ debugName: "leafLevels" }] : /* istanbul ignore next */ []));
500
+ configChange = output();
501
+ levels = computed(() => this.property().configuration ?? [], ...(ngDevMode ? [{ debugName: "levels" }] : /* istanbul ignore next */ []));
502
+ isLevelEnabled(levelId) {
503
+ return this.leafLevels()[String(levelId)] != null;
504
+ }
505
+ isStatusSelected(levelId, statusKey) {
506
+ return this.leafLevels()[String(levelId)]?.selectedStatuses.includes(statusKey) ?? false;
507
+ }
508
+ /** Section toggle — show or hide the entire level in the preview */
509
+ toggleLevel(levelId, enabled) {
510
+ const current = { ...this.leafLevels() };
511
+ if (enabled) {
512
+ const level = this.levels().find((l) => l.levelId === levelId);
513
+ current[String(levelId)] = {
514
+ order: Object.keys(current).length + 1,
515
+ selectedStatuses: level?.statuses.map((s) => s.key) ?? [],
516
+ };
517
+ }
518
+ else {
519
+ delete current[String(levelId)];
520
+ }
521
+ this.configChange.emit(current);
522
+ }
523
+ /** Status toggle — show or hide a single status badge in the level preview */
524
+ toggleStatus(levelId, statusKey) {
525
+ const current = { ...this.leafLevels() };
526
+ const saved = current[String(levelId)];
527
+ if (!saved)
528
+ return;
529
+ const idx = saved.selectedStatuses.indexOf(statusKey);
530
+ current[String(levelId)] = {
531
+ ...saved,
532
+ selectedStatuses: idx >= 0
533
+ ? saved.selectedStatuses.filter((k) => k !== statusKey)
534
+ : [...saved.selectedStatuses, statusKey],
535
+ };
536
+ this.configChange.emit(current);
537
+ }
538
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
539
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: LeafDetailsSelector, isStandalone: true, selector: "mt-leaf-details-selector", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, leafLevels: { classPropertyName: "leafLevels", publicName: "leafLevels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { configChange: "configChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div class=\"space-y-2 pt-1\">\n\n <p class=\"text-sm font-semibold text-color px-1\">\n {{ t('leaf-details-target-statuses') }}\n </p>\n\n @for (level of levels(); track $index) {\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\n\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\n <mt-icon [name]=\"level.levelIcon\" size=\"xs\" class=\"text-color-secondary shrink-0\" />\n <span class=\"flex-1 text-sm font-medium truncate\">{{ level.levelName }}</span>\n <mt-toggle-field\n [ngModel]=\"isLevelEnabled(level.levelId)\"\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\n @if (isLevelEnabled(level.levelId)) {\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\n @for (status of level.statuses; track $index) {\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\n ></span>\n <span class=\"text-sm flex-1 truncate\">{{ status.display }}</span>\n <mt-toggle-field\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n }\n </div>\n }\n\n </div>\n }\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
540
+ }
541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, decorators: [{
542
+ type: Component,
543
+ args: [{ selector: 'mt-leaf-details-selector', standalone: true, imports: [FormsModule, TranslocoDirective, Icon, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div class=\"space-y-2 pt-1\">\n\n <p class=\"text-sm font-semibold text-color px-1\">\n {{ t('leaf-details-target-statuses') }}\n </p>\n\n @for (level of levels(); track $index) {\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\n\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\n <mt-icon [name]=\"level.levelIcon\" size=\"xs\" class=\"text-color-secondary shrink-0\" />\n <span class=\"flex-1 text-sm font-medium truncate\">{{ level.levelName }}</span>\n <mt-toggle-field\n [ngModel]=\"isLevelEnabled(level.levelId)\"\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\n @if (isLevelEnabled(level.levelId)) {\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\n @for (status of level.statuses; track $index) {\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\n ></span>\n <span class=\"text-sm flex-1 truncate\">{{ status.display }}</span>\n <mt-toggle-field\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n }\n </div>\n }\n\n </div>\n }\n </div>\n</ng-container>\n" }]
544
+ }], propDecorators: { property: [{ type: i0.Input, args: [{ isSignal: true, alias: "property", required: true }] }], leafLevels: [{ type: i0.Input, args: [{ isSignal: true, alias: "leafLevels", required: false }] }], configChange: [{ type: i0.Output, args: ["configChange"] }] } });
545
+
546
+ /** Separator used to build synthetic entity keys for LeafDetails sub-entities */
547
+ const LEAF_DETAILS_SEP = '::leaflevel::';
486
548
  class Customization {
487
549
  facade = inject(CustomizationFacade);
488
550
  modalService = inject(ModalService);
489
551
  /** Set of viewTypes that have editable fields in the drawer (beyond size) */
490
552
  editableViewTypes = new Set(EDITABLE_VIEW_TYPES);
491
- searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
492
- activeArea = signal(null, ...(ngDevMode ? [{ debugName: "activeArea" }] : []));
553
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
554
+ activeArea = signal(null, ...(ngDevMode ? [{ debugName: "activeArea" }] : /* istanbul ignore next */ []));
493
555
  // ── Derived signals ──
494
556
  properties = this.facade.properties;
495
557
  areas = this.facade.areas;
496
558
  isLoading = this.facade.isLoadingProperties;
497
559
  isLoadingAreas = this.facade.isLoadingAreas;
498
560
  isLoadingPreview = computed(() => this.facade.isLoadingConfigurations() ||
499
- this.facade.isLoadingProperties(), ...(ngDevMode ? [{ debugName: "isLoadingPreview" }] : []));
500
- areasTabs = computed(() => this.areas().map((area) => ({ label: area.name, value: area.key })), ...(ngDevMode ? [{ debugName: "areasTabs" }] : []));
561
+ this.facade.isLoadingProperties(), ...(ngDevMode ? [{ debugName: "isLoadingPreview" }] : /* istanbul ignore next */ []));
562
+ areasTabs = computed(() => this.areas().map((area) => ({ label: area.name, value: area.key })), ...(ngDevMode ? [{ debugName: "areasTabs" }] : /* istanbul ignore next */ []));
563
+ /**
564
+ * Configurations filtered to exclude properties that no longer exist in the catalog.
565
+ * Prevents sending orphaned property references on save.
566
+ */
567
+ validConfigurations = computed(() => {
568
+ const configs = this.facade.configurations();
569
+ const props = this.properties();
570
+ const propertyKeys = new Set(props.map((p) => p.key));
571
+ return configs.filter((c) => propertyKeys.has(c.propertyKey));
572
+ }, ...(ngDevMode ? [{ debugName: "validConfigurations" }] : /* istanbul ignore next */ []));
501
573
  /** Properties enriched with `enabled` based on current area configurations */
502
574
  propertiesWithEnabled = computed(() => {
503
575
  const props = this.properties();
504
- const configs = this.facade.configurations();
576
+ const configs = this.validConfigurations();
505
577
  const area = this.activeArea();
506
578
  if (!area)
507
579
  return props.map((p) => ({ ...p, enabled: false }));
@@ -509,21 +581,45 @@ class Customization {
509
581
  ...p,
510
582
  enabled: configs.some((c) => c.areaKey === area && c.propertyKey === p.key),
511
583
  }));
512
- }, ...(ngDevMode ? [{ debugName: "propertiesWithEnabled" }] : []));
584
+ }, ...(ngDevMode ? [{ debugName: "propertiesWithEnabled" }] : /* istanbul ignore next */ []));
513
585
  filteredProperties = computed(() => {
514
586
  const query = this.searchQuery().toLowerCase().trim();
515
587
  const list = this.propertiesWithEnabled();
588
+ // LeafDetails always first in the list regardless of active state
589
+ const leaf = list.filter((p) => p.viewType === 'LeafDetails');
590
+ const regular = list.filter((p) => p.viewType !== 'LeafDetails');
591
+ const ordered = [...leaf, ...regular];
516
592
  if (!query)
517
- return list;
518
- return list.filter((prop) => {
593
+ return ordered;
594
+ return ordered.filter((prop) => {
519
595
  const name = typeof prop.name === 'string'
520
596
  ? prop.name
521
597
  : Object.values(prop.name).join(' ');
522
598
  return name.toLowerCase().includes(query);
523
599
  });
524
- }, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : []));
600
+ }, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : /* istanbul ignore next */ []));
601
+ /**
602
+ * LeafDetails saved configs indexed by propertyKey for the active area.
603
+ * Used by the inline selector in the property list.
604
+ */
605
+ leafConfigsByProp = computed(() => {
606
+ const configs = this.validConfigurations();
607
+ const area = this.activeArea();
608
+ if (!area)
609
+ return {};
610
+ const result = {};
611
+ for (const config of configs) {
612
+ if (config.areaKey !== area)
613
+ continue;
614
+ const leafLevels = config.configuration?.['leafLevels'];
615
+ if (leafLevels && typeof leafLevels === 'object' && !Array.isArray(leafLevels)) {
616
+ result[config.propertyKey] = leafLevels;
617
+ }
618
+ }
619
+ return result;
620
+ }, ...(ngDevMode ? [{ debugName: "leafConfigsByProp" }] : /* istanbul ignore next */ []));
525
621
  /** Preview entities built from configurations + properties for the active area */
526
- previewEntities = signal([], ...(ngDevMode ? [{ debugName: "previewEntities" }] : []));
622
+ previewEntities = signal([], ...(ngDevMode ? [{ debugName: "previewEntities" }] : /* istanbul ignore next */ []));
527
623
  constructor() {
528
624
  // Set default active area when areas load
529
625
  effect(() => {
@@ -534,7 +630,7 @@ class Customization {
534
630
  });
535
631
  // Rebuild preview entities when configs, properties, or active area change
536
632
  effect(() => {
537
- const configs = this.facade.configurations();
633
+ const configs = this.validConfigurations();
538
634
  const props = this.properties();
539
635
  const area = this.activeArea();
540
636
  if (!area || !props.length) {
@@ -542,86 +638,245 @@ class Customization {
542
638
  return;
543
639
  }
544
640
  const areaConfigs = configs.filter((c) => c.areaKey === area);
545
- const entities = areaConfigs
546
- .map((config) => this.buildEntityFromConfig(config, props))
547
- .filter((e) => e !== null)
641
+ const all = areaConfigs.flatMap((config) => this.buildEntitiesFromConfig(config, props));
642
+ // Regular entities come first, LeafDetails always last.
643
+ // Orders are reassigned sequentially so EntitiesManage's internal sort stays consistent.
644
+ const regular = all
645
+ .filter((e) => !e.key?.includes(LEAF_DETAILS_SEP))
548
646
  .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
647
+ const leafEntities = all
648
+ .filter((e) => e.key?.includes(LEAF_DETAILS_SEP))
649
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
650
+ const entities = [
651
+ ...regular.map((e, i) => ({ ...e, order: i + 1 })),
652
+ ...leafEntities.map((e, i) => ({ ...e, order: regular.length + i + 1 })),
653
+ ];
549
654
  this.previewEntities.set(entities);
550
655
  });
551
656
  }
552
657
  ngOnInit() {
553
- // Load display areas
554
658
  this.facade.loadAreas();
555
659
  this.facade.loadProperties();
556
660
  this.facade.loadConfigurations();
557
661
  }
558
662
  onPropertyToggle(prop, enabled) {
663
+ // LeafDetails is managed entirely via the inline status selector — skip
664
+ if (prop.viewType === 'LeafDetails')
665
+ return;
559
666
  if (enabled) {
560
667
  this.activateProperty(prop, this.editableViewTypes.has(prop.viewType));
561
668
  return;
562
669
  }
563
- // Remove this property and bulk-replace with remaining checked props
564
670
  this.bulkSaveWithout(prop.key);
565
671
  }
566
672
  onEditProperty(prop) {
567
673
  const area = this.activeArea();
568
674
  if (!area)
569
675
  return;
570
- const configs = this.facade.configurations();
676
+ const configs = this.validConfigurations();
571
677
  const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
572
678
  this.openDrawer(prop, {
573
679
  currentOrder: existingConfig?.order,
574
680
  configuration: existingConfig?.configuration,
575
681
  });
576
682
  }
683
+ onEntityClicked(entity) {
684
+ // LeafDetails sub-entities are configured inline — not via drawer
685
+ if (entity.key?.includes(LEAF_DETAILS_SEP))
686
+ return;
687
+ const area = this.activeArea();
688
+ if (!area || !entity.key)
689
+ return;
690
+ if (!this.editableViewTypes.has(entity.viewType))
691
+ return;
692
+ const prop = this.properties().find((p) => p.key === entity.key);
693
+ if (!prop)
694
+ return;
695
+ const configs = this.validConfigurations();
696
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
697
+ this.openDrawer(prop, {
698
+ currentOrder: existingConfig?.order,
699
+ configuration: existingConfig?.configuration,
700
+ });
701
+ }
577
702
  onEntityResized(event) {
578
703
  const area = this.activeArea();
579
704
  if (!area)
580
705
  return;
581
- const configs = this.facade.configurations();
582
- // Update the configuration for the resized entity in the current area
706
+ const configs = this.validConfigurations();
707
+ // ── LeafDetails sub-entity resize ──
708
+ if (event.entity.key?.includes(LEAF_DETAILS_SEP)) {
709
+ const [propKey, levelId] = event.entity.key.split(LEAF_DETAILS_SEP);
710
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
711
+ const leafLevels = {
712
+ ...(existingConfig?.configuration?.['leafLevels'] ?? {}),
713
+ };
714
+ if (leafLevels[levelId]) {
715
+ leafLevels[levelId] = { ...leafLevels[levelId], size: event.newSize };
716
+ }
717
+ const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === propKey
718
+ ? { ...c, configuration: { ...c.configuration, leafLevels } }
719
+ : c);
720
+ this.facade.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs)).subscribe();
721
+ return;
722
+ }
723
+ // ── Regular entity resize ──
583
724
  const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === event.entity.key
584
725
  ? { ...c, configuration: { ...c.configuration, size: event.newSize } }
585
726
  : c);
586
- // Build bulk-replace items from updated configs
587
- const items = this.groupConfigsByProperty(updatedConfigs);
588
- this.facade.bulkReplaceConfigurations(items).subscribe();
727
+ this.facade.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs)).subscribe();
589
728
  }
590
729
  onEntitiesReordered(entities) {
591
730
  const area = this.activeArea();
592
731
  if (!area)
593
732
  return;
594
- const configs = this.facade.configurations();
595
- const items = entities.map((entity, index) => {
733
+ const configs = this.validConfigurations();
734
+ // Separate regular entities from LeafDetails sub-entities
735
+ const regularEntities = entities.filter((e) => !e.key?.includes(LEAF_DETAILS_SEP));
736
+ const leafEntities = entities.filter((e) => e.key?.includes(LEAF_DETAILS_SEP));
737
+ // Regular entities → BulkReplaceItems
738
+ const regularItems = regularEntities.map((entity) => {
596
739
  const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
597
740
  return {
598
741
  propertyKey: entity.key,
599
742
  displayAreas: [
600
743
  {
601
744
  areaKey: area,
602
- order: index + 1,
745
+ order: entity.order,
603
746
  configuration: existingConfig?.configuration ?? {},
604
747
  },
605
748
  ],
606
749
  };
607
750
  });
608
- // Include configs from other areas that are not being reordered
609
- const otherAreaConfigs = configs.filter((c) => c.areaKey !== area);
610
- const otherAreaItems = this.groupConfigsByProperty(otherAreaConfigs);
611
- // Merge: for properties that exist in both, combine their displayAreas
612
- const mergedItems = this.mergeItems(items, otherAreaItems);
613
- this.facade.bulkReplaceConfigurations(mergedItems).subscribe();
751
+ // Leaf entities group by parent propertyKey, update per-level order
752
+ const leafByProp = new Map();
753
+ for (const entity of leafEntities) {
754
+ const [propKey, levelId] = entity.key.split(LEAF_DETAILS_SEP);
755
+ if (!leafByProp.has(propKey))
756
+ leafByProp.set(propKey, []);
757
+ leafByProp.get(propKey).push({ levelId, order: entity.order });
758
+ }
759
+ const leafItems = [];
760
+ for (const [propKey, levelUpdates] of leafByProp) {
761
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
762
+ const leafLevels = {
763
+ ...(existingConfig?.configuration?.['leafLevels'] ?? {}),
764
+ };
765
+ for (const { levelId, order } of levelUpdates) {
766
+ if (leafLevels[levelId]) {
767
+ leafLevels[levelId] = { ...leafLevels[levelId], order };
768
+ }
769
+ }
770
+ const minOrder = Math.min(...levelUpdates.map((l) => l.order));
771
+ leafItems.push({
772
+ propertyKey: propKey,
773
+ displayAreas: [
774
+ {
775
+ areaKey: area,
776
+ order: existingConfig?.order ?? minOrder,
777
+ configuration: { ...existingConfig?.configuration, leafLevels },
778
+ },
779
+ ],
780
+ });
781
+ }
782
+ // Configs from other areas (preserve as-is)
783
+ const otherAreaItems = this.groupConfigsByProperty(configs.filter((c) => c.areaKey !== area));
784
+ this.facade
785
+ .bulkReplaceConfigurations(this.mergeItems([...regularItems, ...leafItems], otherAreaItems))
786
+ .subscribe();
787
+ }
788
+ /**
789
+ * Called by the inline LeafDetailsSelector when the user changes status selections.
790
+ * Immediately persists the updated leafLevels configuration.
791
+ */
792
+ onLeafConfigChange(prop, leafLevels) {
793
+ const area = this.activeArea();
794
+ if (!area)
795
+ return;
796
+ const configs = this.validConfigurations();
797
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
798
+ // Ensure every level has an order assigned
799
+ let nextOrder = configs.filter((c) => c.areaKey === area && c.propertyKey !== prop.key).length + 1;
800
+ const normalizedLevels = {};
801
+ for (const [levelId, levelCfg] of Object.entries(leafLevels)) {
802
+ normalizedLevels[levelId] = levelCfg.order
803
+ ? levelCfg
804
+ : { ...levelCfg, order: nextOrder++ };
805
+ }
806
+ // Other configs excluding this leaf property in the current area
807
+ const otherItems = this.groupConfigsByProperty(configs.filter((c) => !(c.areaKey === area && c.propertyKey === prop.key)));
808
+ if (Object.keys(normalizedLevels).length === 0) {
809
+ // All levels deselected — remove property from display config
810
+ this.facade.bulkReplaceConfigurations(otherItems).subscribe();
811
+ return;
812
+ }
813
+ const minOrder = Math.min(...Object.values(normalizedLevels).map((l) => l.order));
814
+ const newItem = {
815
+ propertyKey: prop.key,
816
+ displayAreas: [
817
+ {
818
+ areaKey: area,
819
+ order: existingConfig?.order ?? minOrder,
820
+ configuration: { leafLevels: normalizedLevels },
821
+ },
822
+ ],
823
+ };
824
+ this.facade.bulkReplaceConfigurations(this.mergeItems([newItem], otherItems)).subscribe();
614
825
  }
615
826
  // ── Private helpers ──
616
827
  /**
617
- * Add a property to the active area immediately, then optionally open its configuration drawer.
618
- * This keeps the property active even if the user closes the drawer without saving extra options.
828
+ * Builds zero or more EntityData entries from a single DisplayConfiguration.
829
+ * Regular properties one entity. LeafDetails one entity per enabled level.
619
830
  */
831
+ buildEntitiesFromConfig(config, props) {
832
+ const prop = props.find((p) => p.key === config.propertyKey);
833
+ if (!prop)
834
+ return [];
835
+ if (prop.viewType === 'LeafDetails') {
836
+ return this.buildLeafDetailsEntities(config, prop);
837
+ }
838
+ const entity = this.buildEntityFromConfig(config, props);
839
+ return entity ? [entity] : [];
840
+ }
841
+ /** Builds one EntityData per enabled level from a LeafDetails display config. */
842
+ buildLeafDetailsEntities(config, prop) {
843
+ const levels = prop.configuration ?? [];
844
+ const leafLevels = config.configuration?.['leafLevels'];
845
+ if (!leafLevels)
846
+ return [];
847
+ const result = [];
848
+ for (const level of levels) {
849
+ const levelCfg = leafLevels[String(level.levelId)];
850
+ if (!levelCfg)
851
+ continue;
852
+ const selectedKeys = new Set(levelCfg.selectedStatuses);
853
+ const value = {
854
+ levelId: level.levelId,
855
+ levelName: level.levelName,
856
+ levelIcon: level.levelIcon,
857
+ statuses: level.statuses
858
+ .filter((s) => selectedKeys.has(s.key))
859
+ .map((s) => ({ key: s.key, display: s.display, color: s.color, count: 0 })),
860
+ };
861
+ result.push({
862
+ id: prop.id,
863
+ propertyId: prop.id,
864
+ key: `${prop.key}${LEAF_DETAILS_SEP}${level.levelId}`,
865
+ name: level.levelName,
866
+ // Cast needed until entities package is rebuilt with LeafDetailsEntityValue in the union
867
+ value: value,
868
+ viewType: 'LeafDetails',
869
+ order: levelCfg.order,
870
+ configuration: levelCfg.size ? { size: levelCfg.size } : undefined,
871
+ });
872
+ }
873
+ return result;
874
+ }
620
875
  activateProperty(prop, openConfigAfterSave = false) {
621
876
  const area = this.activeArea();
622
877
  if (!area)
623
878
  return;
624
- const configs = this.facade.configurations();
879
+ const configs = this.validConfigurations();
625
880
  const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
626
881
  if (existingConfig) {
627
882
  if (openConfigAfterSave) {
@@ -635,60 +890,38 @@ class Customization {
635
890
  const areaConfigs = configs.filter((c) => c.areaKey === area);
636
891
  const nextOrder = areaConfigs.length + 1;
637
892
  const configuration = {};
638
- const newDisplayArea = {
639
- areaKey: area,
640
- order: nextOrder,
641
- configuration,
642
- };
643
- // Build bulk items from existing configs + new property
644
893
  const itemsMap = new Map();
645
894
  for (const c of configs) {
646
- if (!itemsMap.has(c.propertyKey)) {
895
+ if (!itemsMap.has(c.propertyKey))
647
896
  itemsMap.set(c.propertyKey, []);
648
- }
649
897
  itemsMap.get(c.propertyKey).push({
650
898
  areaKey: c.areaKey,
651
899
  order: c.order,
652
900
  configuration: c.configuration,
653
901
  });
654
902
  }
655
- // Add the new property
656
- if (!itemsMap.has(prop.key)) {
903
+ if (!itemsMap.has(prop.key))
657
904
  itemsMap.set(prop.key, []);
658
- }
659
- itemsMap.get(prop.key).push(newDisplayArea);
905
+ itemsMap.get(prop.key).push({ areaKey: area, order: nextOrder, configuration });
660
906
  const items = Array.from(itemsMap.entries()).map(([propertyKey, displayAreas]) => ({ propertyKey, displayAreas }));
661
907
  this.facade.bulkReplaceConfigurations(items).subscribe({
662
908
  next: () => {
663
- if (!openConfigAfterSave) {
664
- return;
909
+ if (openConfigAfterSave) {
910
+ this.openDrawer(prop, { currentOrder: nextOrder, configuration });
665
911
  }
666
- this.openDrawer(prop, {
667
- currentOrder: nextOrder,
668
- configuration,
669
- });
670
912
  },
671
913
  });
672
914
  }
673
- /**
674
- * Build bulk items from all current configurations, excluding a specific property.
675
- * Then call bulk-replace so the backend replaces the entire context atomically.
676
- */
677
915
  bulkSaveWithout(excludePropertyKey) {
678
- const configs = this.facade.configurations();
679
- const remaining = configs.filter((c) => c.propertyKey !== excludePropertyKey);
680
- const items = this.groupConfigsByProperty(remaining);
916
+ const configs = this.validConfigurations();
917
+ const items = this.groupConfigsByProperty(configs.filter((c) => c.propertyKey !== excludePropertyKey));
681
918
  this.facade.bulkReplaceConfigurations(items).subscribe();
682
919
  }
683
- /**
684
- * Group flat DisplayConfiguration[] into bulk-replace items grouped by propertyKey.
685
- */
686
920
  groupConfigsByProperty(configs) {
687
921
  const map = new Map();
688
922
  for (const c of configs) {
689
- if (!map.has(c.propertyKey)) {
923
+ if (!map.has(c.propertyKey))
690
924
  map.set(c.propertyKey, []);
691
- }
692
925
  map.get(c.propertyKey).push({
693
926
  areaKey: c.areaKey,
694
927
  order: c.order,
@@ -700,23 +933,16 @@ class Customization {
700
933
  displayAreas,
701
934
  }));
702
935
  }
703
- /**
704
- * Merge two sets of bulk items. If a propertyKey exists in both,
705
- * combine their displayAreas arrays.
706
- */
707
936
  mergeItems(primary, secondary) {
708
937
  const map = new Map();
709
- for (const item of primary) {
938
+ for (const item of primary)
710
939
  map.set(item.propertyKey, [...item.displayAreas]);
711
- }
712
940
  for (const item of secondary) {
713
941
  const existing = map.get(item.propertyKey);
714
- if (existing) {
942
+ if (existing)
715
943
  existing.push(...item.displayAreas);
716
- }
717
- else {
944
+ else
718
945
  map.set(item.propertyKey, [...item.displayAreas]);
719
- }
720
946
  }
721
947
  return Array.from(map.entries()).map(([propertyKey, displayAreas]) => ({
722
948
  propertyKey,
@@ -727,7 +953,7 @@ class Customization {
727
953
  const area = this.activeArea();
728
954
  if (!area)
729
955
  return;
730
- const configs = this.facade.configurations();
956
+ const configs = this.validConfigurations();
731
957
  const areaConfigs = configs.filter((c) => c.areaKey === area);
732
958
  const nextOrder = existingConfig?.currentOrder ?? areaConfigs.length + 1;
733
959
  const displayName = this.getPropertyDisplayName(prop);
@@ -758,6 +984,7 @@ class Customization {
758
984
  false,
759
985
  labelPosition,
760
986
  showBorder: configuration?.['showBorder'] ?? false,
987
+ alignEnd: configuration?.['alignEnd'] ?? false,
761
988
  showDisplayName: configuration?.['showDisplayName'] ?? true,
762
989
  showPhoneNumber: configuration?.['showPhoneNumber'] ?? false,
763
990
  showEmail: configuration?.['showEmail'] ?? false,
@@ -769,7 +996,7 @@ class Customization {
769
996
  return null;
770
997
  const displayName = this.getPropertyDisplayName(prop);
771
998
  const viewType = prop.viewType;
772
- const entity = {
999
+ return {
773
1000
  id: prop.id,
774
1001
  propertyId: prop.id,
775
1002
  key: prop.key,
@@ -780,7 +1007,6 @@ class Customization {
780
1007
  order: config.order,
781
1008
  configuration: config.configuration,
782
1009
  };
783
- return entity;
784
1010
  }
785
1011
  getPropertyDisplayName(prop) {
786
1012
  if (typeof prop.name === 'string')
@@ -805,7 +1031,7 @@ class Customization {
805
1031
  case 'Percentage':
806
1032
  return '75%';
807
1033
  case 'Status':
808
- return { key: 'sample', display: name, color: '#3B82F6' };
1034
+ return { key: 'sample', display: 'in Progress', color: '#3B82F6' };
809
1035
  case 'Lookup':
810
1036
  return {
811
1037
  key: 'sample',
@@ -838,10 +1064,10 @@ class Customization {
838
1064
  return name;
839
1065
  }
840
1066
  }
841
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Customization, deps: [], target: i0.ɵɵFactoryTarget.Component });
842
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: Customization, isStandalone: true, selector: "mt-customization", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-150 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex items-center justify-center h-full text-muted-color\"\r\n >\r\n <p class=\"text-sm\">{{ t(\"no-preview-items\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: EntitiesManage, selector: "mt-entities-manage", inputs: ["entities"], outputs: ["entitiesChange", "entitiesReordered"] }] });
1067
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, deps: [], target: i0.ɵɵFactoryTarget.Component });
1068
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: Customization, isStandalone: true, selector: "mt-customization", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n @if (prop.viewType === 'LeafDetails') {\r\n <!-- \u2500\u2500 LeafDetails: always first, title is \"Target Statuses\" inside the selector \u2500\u2500 -->\r\n <div class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\">\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- \u2500\u2500 Normal property toggle row \u2500\u2500 -->\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: EntitiesManage, selector: "mt-entities-manage", inputs: ["entities"], outputs: ["entitiesChange", "entitiesReordered", "entityClicked"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: LeafDetailsSelector, selector: "mt-leaf-details-selector", inputs: ["property", "leafLevels"], outputs: ["configChange"] }] });
843
1069
  }
844
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Customization, decorators: [{
1070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, decorators: [{
845
1071
  type: Component,
846
1072
  args: [{ selector: 'mt-customization', standalone: true, imports: [
847
1073
  FormsModule,
@@ -853,7 +1079,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
853
1079
  Tabs,
854
1080
  Skeleton,
855
1081
  EntitiesManage,
856
- ], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-150 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex items-center justify-center h-full text-muted-color\"\r\n >\r\n <p class=\"text-sm\">{{ t(\"no-preview-items\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"] }]
1082
+ Icon,
1083
+ LeafDetailsSelector,
1084
+ ], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n @if (prop.viewType === 'LeafDetails') {\r\n <!-- \u2500\u2500 LeafDetails: always first, title is \"Target Statuses\" inside the selector \u2500\u2500 -->\r\n <div class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\">\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- \u2500\u2500 Normal property toggle row \u2500\u2500 -->\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"] }]
857
1085
  }], ctorParameters: () => [] });
858
1086
 
859
1087
  /**