@masterteam/customization 0.0.14 → 0.0.16

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';
@@ -8,7 +8,7 @@ import { TextField } from '@masterteam/components/text-field';
8
8
  import { ToggleField } from '@masterteam/components/toggle-field';
9
9
  import { Button } from '@masterteam/components/button';
10
10
  import { Tabs } from '@masterteam/components/tabs';
11
- import { EntitiesManage } from '@masterteam/components/entities';
11
+ import { buildDisplayEntities, isLeafDetailsSyntheticKey, LEAF_DETAILS_KEY_SEPARATOR, EntitiesManage } from '@masterteam/components/entities';
12
12
  import { ModalService } from '@masterteam/components/modal';
13
13
  import { Icon } from '@masterteam/icons';
14
14
  import { Skeleton } from 'primeng/skeleton';
@@ -25,6 +25,7 @@ const BORDER_VIEW_TYPES = [
25
25
  'Currency',
26
26
  'Date',
27
27
  'DateTime',
28
+ 'LeafDetails',
28
29
  ];
29
30
  /** ViewTypes that support user-specific toggles */
30
31
  const USER_VIEW_TYPES = ['User'];
@@ -38,6 +39,7 @@ const EDITABLE_VIEW_TYPES = [
38
39
  'Currency',
39
40
  'Date',
40
41
  'DateTime',
42
+ 'LeafDetails',
41
43
  'Percentage',
42
44
  'Status',
43
45
  'Checkbox',
@@ -211,30 +213,23 @@ let CustomizationState = class CustomizationState extends CrudStateBase {
211
213
  bulkReplaceConfigurations(ctx, action) {
212
214
  const state = ctx.getState();
213
215
  const contextKey = this.buildContextKey(state);
216
+ const previousConfigurations = state.configurations;
217
+ const nextConfigurations = this.toDisplayConfigurations(contextKey, action.items);
214
218
  const req$ = this.http.put(`${this.displayConfigUrl}/bulk-replace`, {
215
219
  contextKey,
216
220
  items: action.items,
217
221
  });
222
+ ctx.patchState({
223
+ configurations: nextConfigurations,
224
+ });
218
225
  return handleApiRequest({
219
226
  ctx,
220
227
  key: CustomizationActionKey.BulkReplaceConfigurations,
221
228
  request$: req$,
222
- onSuccess: () => {
223
- // Rebuild configurations from the items payload
224
- const newConfigs = [];
225
- for (const item of action.items) {
226
- for (const da of item.displayAreas) {
227
- newConfigs.push({
228
- contextKey,
229
- areaKey: da.areaKey,
230
- propertyKey: item.propertyKey,
231
- order: da.order,
232
- configuration: da.configuration,
233
- });
234
- }
235
- }
236
- return { configurations: newConfigs };
237
- },
229
+ onSuccess: () => undefined,
230
+ onError: () => ({
231
+ configurations: previousConfigurations,
232
+ }),
238
233
  });
239
234
  }
240
235
  // ============================================================================
@@ -250,6 +245,21 @@ let CustomizationState = class CustomizationState extends CrudStateBase {
250
245
  }
251
246
  return contextParts.join('/');
252
247
  }
248
+ toDisplayConfigurations(contextKey, items) {
249
+ const configurations = [];
250
+ for (const item of items) {
251
+ for (const displayArea of item.displayAreas) {
252
+ configurations.push({
253
+ contextKey,
254
+ areaKey: displayArea.areaKey,
255
+ propertyKey: item.propertyKey,
256
+ order: displayArea.order,
257
+ configuration: displayArea.configuration,
258
+ });
259
+ }
260
+ }
261
+ return configurations;
262
+ }
253
263
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
254
264
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState });
255
265
  };
@@ -402,6 +412,7 @@ class PropertyConfigDrawer {
402
412
  // ─── Computed visibility flags ───
403
413
  isBorderViewType = computed(() => BORDER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isBorderViewType" }] : /* istanbul ignore next */ []));
404
414
  isUserViewType = computed(() => USER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isUserViewType" }] : /* istanbul ignore next */ []));
415
+ isLeafDetailsViewType = computed(() => this.viewType() === 'LeafDetails', ...(ngDevMode ? [{ debugName: "isLeafDetailsViewType" }] : /* istanbul ignore next */ []));
405
416
  /** True when the document direction is RTL */
406
417
  isRtl = signal(this.doc.documentElement.getAttribute('dir') === 'rtl', ...(ngDevMode ? [{ debugName: "isRtl" }] : /* istanbul ignore next */ []));
407
418
  /** Align-end icon: right in LTR, left in RTL */
@@ -425,19 +436,27 @@ class PropertyConfigDrawer {
425
436
  .configurations()
426
437
  .find((c) => c.propertyKey === this.propertyKey() && c.areaKey === this.areaKey());
427
438
  const existingSize = existingConfig?.configuration?.['size'];
439
+ const existingLeafLevels = existingConfig?.configuration?.['leafLevels'];
428
440
  const configuration = {};
429
441
  // Carry over size so drag-resize value is not lost
430
442
  if (existingSize != null) {
431
443
  configuration['size'] = existingSize;
432
444
  }
433
- // Keep both keys for backward compatibility while the shared entities
434
- // layer now prefers hideLabel.
435
- configuration['hideName'] = this.hideNameControl.value ?? false;
436
- configuration['hideLabel'] = this.hideNameControl.value ?? false;
445
+ if (existingLeafLevels != null) {
446
+ configuration['leafLevels'] = existingLeafLevels;
447
+ }
448
+ if (!this.isLeafDetailsViewType()) {
449
+ // Keep both keys for backward compatibility while the shared entities
450
+ // layer now prefers hideLabel.
451
+ configuration['hideName'] = this.hideNameControl.value ?? false;
452
+ configuration['hideLabel'] = this.hideNameControl.value ?? false;
453
+ }
437
454
  configuration['labelPosition'] = this.labelOnTopControl.value
438
455
  ? 'top'
439
456
  : 'bottom';
440
- configuration['alignEnd'] = this.alignEndControl.value ?? false;
457
+ if (!this.isLeafDetailsViewType()) {
458
+ configuration['alignEnd'] = this.alignEndControl.value ?? false;
459
+ }
441
460
  if (this.isBorderViewType()) {
442
461
  configuration['showBorder'] = this.showBorderControl.value ?? false;
443
462
  }
@@ -487,13 +506,67 @@ class PropertyConfigDrawer {
487
506
  this.ref.close(null);
488
507
  }
489
508
  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"] }] });
509
+ 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 @if (!isLeafDetailsViewType()) {\r\n <!-- Hide Name Toggle -->\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\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 @if (!isLeafDetailsViewType()) {\r\n <!-- Align End Toggle -->\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\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"] }] });
491
510
  }
492
511
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PropertyConfigDrawer, decorators: [{
493
512
  type: Component,
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" }]
513
+ 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 @if (!isLeafDetailsViewType()) {\r\n <!-- Hide Name Toggle -->\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\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 @if (!isLeafDetailsViewType()) {\r\n <!-- Align End Toggle -->\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\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" }]
495
514
  }], 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 }] }] } });
496
515
 
516
+ class LeafDetailsSelector {
517
+ property = input.required(...(ngDevMode ? [{ debugName: "property" }] : /* istanbul ignore next */ []));
518
+ leafLevels = input({}, ...(ngDevMode ? [{ debugName: "leafLevels" }] : /* istanbul ignore next */ []));
519
+ showEditAction = input(false, ...(ngDevMode ? [{ debugName: "showEditAction" }] : /* istanbul ignore next */ []));
520
+ configChange = output();
521
+ editRequested = output();
522
+ levels = computed(() => this.property().configuration ?? [], ...(ngDevMode ? [{ debugName: "levels" }] : /* istanbul ignore next */ []));
523
+ isLevelEnabled(levelId) {
524
+ return this.leafLevels()[String(levelId)] != null;
525
+ }
526
+ isStatusSelected(levelId, statusKey) {
527
+ return (this.leafLevels()[String(levelId)]?.selectedStatuses.includes(statusKey) ?? false);
528
+ }
529
+ /** Section toggle — show or hide the entire level in the preview */
530
+ toggleLevel(levelId, enabled) {
531
+ const current = { ...this.leafLevels() };
532
+ if (enabled) {
533
+ const level = this.levels().find((l) => l.levelId === levelId);
534
+ current[String(levelId)] = {
535
+ order: Object.keys(current).length + 1,
536
+ selectedStatuses: level?.statuses.map((s) => s.key) ?? [],
537
+ };
538
+ }
539
+ else {
540
+ delete current[String(levelId)];
541
+ }
542
+ this.configChange.emit(current);
543
+ }
544
+ /** Status toggle — show or hide a single status badge in the level preview */
545
+ toggleStatus(levelId, statusKey) {
546
+ const current = { ...this.leafLevels() };
547
+ const saved = current[String(levelId)];
548
+ if (!saved)
549
+ return;
550
+ const idx = saved.selectedStatuses.indexOf(statusKey);
551
+ current[String(levelId)] = {
552
+ ...saved,
553
+ selectedStatuses: idx >= 0
554
+ ? saved.selectedStatuses.filter((k) => k !== statusKey)
555
+ : [...saved.selectedStatuses, statusKey],
556
+ };
557
+ this.configChange.emit(current);
558
+ }
559
+ onEditClick() {
560
+ this.editRequested.emit();
561
+ }
562
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
563
+ 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 }, showEditAction: { classPropertyName: "showEditAction", publicName: "showEditAction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { configChange: "configChange", editRequested: "editRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div class=\"space-y-2 pt-1\">\r\n <div class=\"flex items-center gap-1 px-1\">\r\n <p class=\"text-sm font-semibold text-color\">\r\n {{ t(\"leaf-details-target-statuses\") }}\r\n </p>\r\n @if (showEditAction()) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditClick()\"\r\n />\r\n }\r\n </div>\r\n\r\n @for (level of levels(); track $index) {\r\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\r\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\r\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\r\n <mt-icon\r\n [icon]=\"level.levelIcon\"\r\n size=\"xs\"\r\n class=\"text-color-secondary shrink-0\"\r\n />\r\n <span class=\"flex-1 text-sm font-medium truncate\">{{\r\n level.levelName\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isLevelEnabled(level.levelId)\"\r\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n\r\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\r\n @if (isLevelEnabled(level.levelId)) {\r\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\r\n @for (status of level.statuses; track $index) {\r\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\r\n <span\r\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\r\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\r\n ></span>\r\n <span class=\"text-sm flex-1 truncate\">{{\r\n status.display\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\r\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\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"] }, { 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"] }] });
564
+ }
565
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, decorators: [{
566
+ type: Component,
567
+ args: [{ selector: 'mt-leaf-details-selector', standalone: true, imports: [FormsModule, TranslocoDirective, Icon, ToggleField, Button], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div class=\"space-y-2 pt-1\">\r\n <div class=\"flex items-center gap-1 px-1\">\r\n <p class=\"text-sm font-semibold text-color\">\r\n {{ t(\"leaf-details-target-statuses\") }}\r\n </p>\r\n @if (showEditAction()) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditClick()\"\r\n />\r\n }\r\n </div>\r\n\r\n @for (level of levels(); track $index) {\r\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\r\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\r\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\r\n <mt-icon\r\n [icon]=\"level.levelIcon\"\r\n size=\"xs\"\r\n class=\"text-color-secondary shrink-0\"\r\n />\r\n <span class=\"flex-1 text-sm font-medium truncate\">{{\r\n level.levelName\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isLevelEnabled(level.levelId)\"\r\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n\r\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\r\n @if (isLevelEnabled(level.levelId)) {\r\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\r\n @for (status of level.statuses; track $index) {\r\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\r\n <span\r\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\r\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\r\n ></span>\r\n <span class=\"text-sm flex-1 truncate\">{{\r\n status.display\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\r\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
568
+ }], propDecorators: { property: [{ type: i0.Input, args: [{ isSignal: true, alias: "property", required: true }] }], leafLevels: [{ type: i0.Input, args: [{ isSignal: true, alias: "leafLevels", required: false }] }], showEditAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditAction", required: false }] }], configChange: [{ type: i0.Output, args: ["configChange"] }], editRequested: [{ type: i0.Output, args: ["editRequested"] }] } });
569
+
497
570
  class Customization {
498
571
  facade = inject(CustomizationFacade);
499
572
  modalService = inject(ModalService);
@@ -534,15 +607,41 @@ class Customization {
534
607
  filteredProperties = computed(() => {
535
608
  const query = this.searchQuery().toLowerCase().trim();
536
609
  const list = this.propertiesWithEnabled();
610
+ // LeafDetails always first in the list regardless of active state
611
+ const leaf = list.filter((p) => p.viewType === 'LeafDetails');
612
+ const regular = list.filter((p) => p.viewType !== 'LeafDetails');
613
+ const ordered = [...leaf, ...regular];
537
614
  if (!query)
538
- return list;
539
- return list.filter((prop) => {
615
+ return ordered;
616
+ return ordered.filter((prop) => {
540
617
  const name = typeof prop.name === 'string'
541
618
  ? prop.name
542
619
  : Object.values(prop.name).join(' ');
543
620
  return name.toLowerCase().includes(query);
544
621
  });
545
622
  }, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : /* istanbul ignore next */ []));
623
+ /**
624
+ * LeafDetails saved configs indexed by propertyKey for the active area.
625
+ * Used by the inline selector in the property list.
626
+ */
627
+ leafConfigsByProp = computed(() => {
628
+ const configs = this.validConfigurations();
629
+ const area = this.activeArea();
630
+ if (!area)
631
+ return {};
632
+ const result = {};
633
+ for (const config of configs) {
634
+ if (config.areaKey !== area)
635
+ continue;
636
+ const leafLevels = config.configuration?.['leafLevels'];
637
+ if (leafLevels &&
638
+ typeof leafLevels === 'object' &&
639
+ !Array.isArray(leafLevels)) {
640
+ result[config.propertyKey] = leafLevels;
641
+ }
642
+ }
643
+ return result;
644
+ }, ...(ngDevMode ? [{ debugName: "leafConfigsByProp" }] : /* istanbul ignore next */ []));
546
645
  /** Preview entities built from configurations + properties for the active area */
547
646
  previewEntities = signal([], ...(ngDevMode ? [{ debugName: "previewEntities" }] : /* istanbul ignore next */ []));
548
647
  constructor() {
@@ -563,25 +662,23 @@ class Customization {
563
662
  return;
564
663
  }
565
664
  const areaConfigs = configs.filter((c) => c.areaKey === area);
566
- const entities = areaConfigs
567
- .map((config) => this.buildEntityFromConfig(config, props))
568
- .filter((e) => e !== null)
569
- .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
665
+ const entities = buildDisplayEntities(areaConfigs.flatMap((config) => this.buildEntitiesFromConfig(config, props)));
570
666
  this.previewEntities.set(entities);
571
667
  });
572
668
  }
573
669
  ngOnInit() {
574
- // Load display areas
575
670
  this.facade.loadAreas();
576
671
  this.facade.loadProperties();
577
672
  this.facade.loadConfigurations();
578
673
  }
579
674
  onPropertyToggle(prop, enabled) {
675
+ // LeafDetails is managed entirely via the inline status selector — skip
676
+ if (prop.viewType === 'LeafDetails')
677
+ return;
580
678
  if (enabled) {
581
679
  this.activateProperty(prop, this.editableViewTypes.has(prop.viewType));
582
680
  return;
583
681
  }
584
- // Remove this property and bulk-replace with remaining checked props
585
682
  this.bulkSaveWithout(prop.key);
586
683
  }
587
684
  onEditProperty(prop) {
@@ -596,16 +693,20 @@ class Customization {
596
693
  });
597
694
  }
598
695
  onEntityClicked(entity) {
696
+ // LeafDetails sub-entities are configured inline — not via drawer
599
697
  const area = this.activeArea();
600
698
  if (!area || !entity.key)
601
699
  return;
602
- if (!this.editableViewTypes.has(entity.viewType))
603
- return;
604
- const prop = this.properties().find((p) => p.key === entity.key);
700
+ const propertyKey = isLeafDetailsSyntheticKey(entity.key)
701
+ ? entity.key.split(LEAF_DETAILS_KEY_SEPARATOR)[0]
702
+ : entity.key;
703
+ const prop = this.properties().find((p) => p.key === propertyKey);
605
704
  if (!prop)
606
705
  return;
706
+ if (!this.editableViewTypes.has(prop.viewType))
707
+ return;
607
708
  const configs = this.validConfigurations();
608
- const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
709
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propertyKey);
609
710
  this.openDrawer(prop, {
610
711
  currentOrder: existingConfig?.order,
611
712
  configuration: existingConfig?.configuration,
@@ -616,44 +717,183 @@ class Customization {
616
717
  if (!area)
617
718
  return;
618
719
  const configs = this.validConfigurations();
619
- // Update the configuration for the resized entity in the current area
720
+ // ── LeafDetails sub-entity resize ──
721
+ if (isLeafDetailsSyntheticKey(event.entity.key)) {
722
+ const [propKey, levelId] = event.entity.key.split(LEAF_DETAILS_KEY_SEPARATOR);
723
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
724
+ const leafLevels = {
725
+ ...(existingConfig?.configuration?.['leafLevels'] ?? {}),
726
+ };
727
+ if (leafLevels[levelId]) {
728
+ leafLevels[levelId] = { ...leafLevels[levelId], size: event.newSize };
729
+ }
730
+ const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === propKey
731
+ ? { ...c, configuration: { ...c.configuration, leafLevels } }
732
+ : c);
733
+ this.facade
734
+ .bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs))
735
+ .subscribe();
736
+ return;
737
+ }
738
+ // ── Regular entity resize ──
620
739
  const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === event.entity.key
621
740
  ? { ...c, configuration: { ...c.configuration, size: event.newSize } }
622
741
  : c);
623
- // Build bulk-replace items from updated configs
624
- const items = this.groupConfigsByProperty(updatedConfigs);
625
- this.facade.bulkReplaceConfigurations(items).subscribe();
742
+ this.facade
743
+ .bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs))
744
+ .subscribe();
626
745
  }
627
746
  onEntitiesReordered(entities) {
628
747
  const area = this.activeArea();
629
748
  if (!area)
630
749
  return;
631
750
  const configs = this.validConfigurations();
632
- const items = entities.map((entity, index) => {
751
+ // Separate regular entities from LeafDetails sub-entities
752
+ const regularEntities = entities.filter((e) => !isLeafDetailsSyntheticKey(e.key));
753
+ const leafEntities = entities.filter((e) => isLeafDetailsSyntheticKey(e.key));
754
+ // Regular entities → BulkReplaceItems
755
+ const regularItems = regularEntities.map((entity) => {
633
756
  const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
634
757
  return {
635
758
  propertyKey: entity.key,
636
759
  displayAreas: [
637
760
  {
638
761
  areaKey: area,
639
- order: index + 1,
762
+ order: entity.order,
640
763
  configuration: existingConfig?.configuration ?? {},
641
764
  },
642
765
  ],
643
766
  };
644
767
  });
645
- // Include configs from other areas that are not being reordered
646
- const otherAreaConfigs = configs.filter((c) => c.areaKey !== area);
647
- const otherAreaItems = this.groupConfigsByProperty(otherAreaConfigs);
648
- // Merge: for properties that exist in both, combine their displayAreas
649
- const mergedItems = this.mergeItems(items, otherAreaItems);
650
- this.facade.bulkReplaceConfigurations(mergedItems).subscribe();
768
+ // Leaf entities group by parent propertyKey, update per-level order
769
+ const leafByProp = new Map();
770
+ for (const entity of leafEntities) {
771
+ const [propKey, levelId] = entity.key.split(LEAF_DETAILS_KEY_SEPARATOR);
772
+ if (!leafByProp.has(propKey))
773
+ leafByProp.set(propKey, []);
774
+ leafByProp.get(propKey).push({ levelId, order: entity.order });
775
+ }
776
+ const leafItems = [];
777
+ for (const [propKey, levelUpdates] of leafByProp) {
778
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
779
+ const leafLevels = {
780
+ ...(existingConfig?.configuration?.['leafLevels'] ?? {}),
781
+ };
782
+ for (const { levelId, order } of levelUpdates) {
783
+ if (leafLevels[levelId]) {
784
+ leafLevels[levelId] = { ...leafLevels[levelId], order };
785
+ }
786
+ }
787
+ const minOrder = Math.min(...levelUpdates.map((l) => l.order));
788
+ leafItems.push({
789
+ propertyKey: propKey,
790
+ displayAreas: [
791
+ {
792
+ areaKey: area,
793
+ order: existingConfig?.order ?? minOrder,
794
+ configuration: { ...existingConfig?.configuration, leafLevels },
795
+ },
796
+ ],
797
+ });
798
+ }
799
+ // Configs from other areas (preserve as-is)
800
+ const otherAreaItems = this.groupConfigsByProperty(configs.filter((c) => c.areaKey !== area));
801
+ this.facade
802
+ .bulkReplaceConfigurations(this.mergeItems([...regularItems, ...leafItems], otherAreaItems))
803
+ .subscribe();
804
+ }
805
+ /**
806
+ * Called by the inline LeafDetailsSelector when the user changes status selections.
807
+ * Immediately persists the updated leafLevels configuration.
808
+ */
809
+ onLeafConfigChange(prop, leafLevels) {
810
+ const area = this.activeArea();
811
+ if (!area)
812
+ return;
813
+ const configs = this.validConfigurations();
814
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
815
+ // Ensure every level has an order assigned
816
+ let nextOrder = configs.filter((c) => c.areaKey === area && c.propertyKey !== prop.key)
817
+ .length + 1;
818
+ const normalizedLevels = {};
819
+ for (const [levelId, levelCfg] of Object.entries(leafLevels)) {
820
+ normalizedLevels[levelId] = levelCfg.order
821
+ ? levelCfg
822
+ : { ...levelCfg, order: nextOrder++ };
823
+ }
824
+ // Other configs excluding this leaf property in the current area
825
+ const otherItems = this.groupConfigsByProperty(configs.filter((c) => !(c.areaKey === area && c.propertyKey === prop.key)));
826
+ if (Object.keys(normalizedLevels).length === 0) {
827
+ // All levels deselected — remove property from display config
828
+ this.facade.bulkReplaceConfigurations(otherItems).subscribe();
829
+ return;
830
+ }
831
+ const minOrder = Math.min(...Object.values(normalizedLevels).map((l) => l.order));
832
+ const newItem = {
833
+ propertyKey: prop.key,
834
+ displayAreas: [
835
+ {
836
+ areaKey: area,
837
+ order: existingConfig?.order ?? minOrder,
838
+ configuration: {
839
+ ...existingConfig?.configuration,
840
+ leafLevels: normalizedLevels,
841
+ },
842
+ },
843
+ ],
844
+ };
845
+ this.facade
846
+ .bulkReplaceConfigurations(this.mergeItems([newItem], otherItems))
847
+ .subscribe();
651
848
  }
652
849
  // ── Private helpers ──
653
850
  /**
654
- * Add a property to the active area immediately, then optionally open its configuration drawer.
655
- * This keeps the property active even if the user closes the drawer without saving extra options.
851
+ * Builds zero or more EntityData entries from a single DisplayConfiguration.
852
+ * Regular properties one entity. LeafDetails one entity per enabled level.
656
853
  */
854
+ buildEntitiesFromConfig(config, props) {
855
+ const prop = props.find((p) => p.key === config.propertyKey);
856
+ if (!prop)
857
+ return [];
858
+ if (prop.viewType === 'LeafDetails') {
859
+ return this.buildLeafDetailsEntities(config, prop);
860
+ }
861
+ const entity = this.buildEntityFromConfig(config, props);
862
+ return entity ? [entity] : [];
863
+ }
864
+ /** Builds the shared LeafDetails entity that preview/manage will expand. */
865
+ buildLeafDetailsEntities(config, prop) {
866
+ const levels = prop.configuration ?? [];
867
+ const leafLevels = config.configuration?.['leafLevels'];
868
+ if (!leafLevels)
869
+ return [];
870
+ return [
871
+ {
872
+ id: prop.id,
873
+ propertyId: prop.id,
874
+ key: prop.key,
875
+ name: this.getPropertyDisplayName(prop),
876
+ propertyConfiguration: prop.configuration,
877
+ value: levels.map((level) => ({
878
+ levelId: level.levelId,
879
+ levelName: level.levelName,
880
+ levelIcon: level.levelIcon,
881
+ statuses: level.statuses.map((status) => ({
882
+ key: status.key,
883
+ display: status.display,
884
+ color: status.color,
885
+ count: 0,
886
+ })),
887
+ })),
888
+ viewType: 'LeafDetails',
889
+ order: config.order,
890
+ configuration: {
891
+ ...config.configuration,
892
+ leafLevels,
893
+ },
894
+ },
895
+ ];
896
+ }
657
897
  activateProperty(prop, openConfigAfterSave = false) {
658
898
  const area = this.activeArea();
659
899
  if (!area)
@@ -672,60 +912,40 @@ class Customization {
672
912
  const areaConfigs = configs.filter((c) => c.areaKey === area);
673
913
  const nextOrder = areaConfigs.length + 1;
674
914
  const configuration = {};
675
- const newDisplayArea = {
676
- areaKey: area,
677
- order: nextOrder,
678
- configuration,
679
- };
680
- // Build bulk items from existing configs + new property
681
915
  const itemsMap = new Map();
682
916
  for (const c of configs) {
683
- if (!itemsMap.has(c.propertyKey)) {
917
+ if (!itemsMap.has(c.propertyKey))
684
918
  itemsMap.set(c.propertyKey, []);
685
- }
686
919
  itemsMap.get(c.propertyKey).push({
687
920
  areaKey: c.areaKey,
688
921
  order: c.order,
689
922
  configuration: c.configuration,
690
923
  });
691
924
  }
692
- // Add the new property
693
- if (!itemsMap.has(prop.key)) {
925
+ if (!itemsMap.has(prop.key))
694
926
  itemsMap.set(prop.key, []);
695
- }
696
- itemsMap.get(prop.key).push(newDisplayArea);
927
+ itemsMap
928
+ .get(prop.key)
929
+ .push({ areaKey: area, order: nextOrder, configuration });
697
930
  const items = Array.from(itemsMap.entries()).map(([propertyKey, displayAreas]) => ({ propertyKey, displayAreas }));
698
931
  this.facade.bulkReplaceConfigurations(items).subscribe({
699
932
  next: () => {
700
- if (!openConfigAfterSave) {
701
- return;
933
+ if (openConfigAfterSave) {
934
+ this.openDrawer(prop, { currentOrder: nextOrder, configuration });
702
935
  }
703
- this.openDrawer(prop, {
704
- currentOrder: nextOrder,
705
- configuration,
706
- });
707
936
  },
708
937
  });
709
938
  }
710
- /**
711
- * Build bulk items from all current configurations, excluding a specific property.
712
- * Then call bulk-replace so the backend replaces the entire context atomically.
713
- */
714
939
  bulkSaveWithout(excludePropertyKey) {
715
940
  const configs = this.validConfigurations();
716
- const remaining = configs.filter((c) => c.propertyKey !== excludePropertyKey);
717
- const items = this.groupConfigsByProperty(remaining);
941
+ const items = this.groupConfigsByProperty(configs.filter((c) => c.propertyKey !== excludePropertyKey));
718
942
  this.facade.bulkReplaceConfigurations(items).subscribe();
719
943
  }
720
- /**
721
- * Group flat DisplayConfiguration[] into bulk-replace items grouped by propertyKey.
722
- */
723
944
  groupConfigsByProperty(configs) {
724
945
  const map = new Map();
725
946
  for (const c of configs) {
726
- if (!map.has(c.propertyKey)) {
947
+ if (!map.has(c.propertyKey))
727
948
  map.set(c.propertyKey, []);
728
- }
729
949
  map.get(c.propertyKey).push({
730
950
  areaKey: c.areaKey,
731
951
  order: c.order,
@@ -737,23 +957,16 @@ class Customization {
737
957
  displayAreas,
738
958
  }));
739
959
  }
740
- /**
741
- * Merge two sets of bulk items. If a propertyKey exists in both,
742
- * combine their displayAreas arrays.
743
- */
744
960
  mergeItems(primary, secondary) {
745
961
  const map = new Map();
746
- for (const item of primary) {
962
+ for (const item of primary)
747
963
  map.set(item.propertyKey, [...item.displayAreas]);
748
- }
749
964
  for (const item of secondary) {
750
965
  const existing = map.get(item.propertyKey);
751
- if (existing) {
966
+ if (existing)
752
967
  existing.push(...item.displayAreas);
753
- }
754
- else {
968
+ else
755
969
  map.set(item.propertyKey, [...item.displayAreas]);
756
- }
757
970
  }
758
971
  return Array.from(map.entries()).map(([propertyKey, displayAreas]) => ({
759
972
  propertyKey,
@@ -807,7 +1020,7 @@ class Customization {
807
1020
  return null;
808
1021
  const displayName = this.getPropertyDisplayName(prop);
809
1022
  const viewType = prop.viewType;
810
- const entity = {
1023
+ return {
811
1024
  id: prop.id,
812
1025
  propertyId: prop.id,
813
1026
  key: prop.key,
@@ -816,9 +1029,9 @@ class Customization {
816
1029
  rawValue: this.getPlaceholderRawValue(viewType, displayName),
817
1030
  viewType,
818
1031
  order: config.order,
1032
+ propertyConfiguration: prop.configuration,
819
1033
  configuration: config.configuration,
820
1034
  };
821
- return entity;
822
1035
  }
823
1036
  getPropertyDisplayName(prop) {
824
1037
  if (typeof prop.name === 'string')
@@ -877,7 +1090,7 @@ class Customization {
877
1090
  }
878
1091
  }
879
1092
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, deps: [], target: i0.ɵɵFactoryTarget.Component });
880
- 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 <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 @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"] }] });
1093
+ 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\r\n class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\"\r\n >\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n [showEditAction]=\"prop.enabled && editableViewTypes.has(prop.viewType)\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n (editRequested)=\"onEditProperty(prop)\"\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", "showEditAction"], outputs: ["configChange", "editRequested"] }] });
881
1094
  }
882
1095
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, decorators: [{
883
1096
  type: Component,
@@ -892,7 +1105,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
892
1105
  Skeleton,
893
1106
  EntitiesManage,
894
1107
  Icon,
895
- ], 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 size=\"small\"\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-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"] }]
1108
+ LeafDetailsSelector,
1109
+ ], 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\r\n class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\"\r\n >\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n [showEditAction]=\"prop.enabled && editableViewTypes.has(prop.viewType)\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n (editRequested)=\"onEditProperty(prop)\"\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"] }]
896
1110
  }], ctorParameters: () => [] });
897
1111
 
898
1112
  /**