@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.
- package/assets/customization.css +1 -1
- package/assets/i18n/ar.json +24 -19
- package/assets/i18n/en.json +28 -23
- package/fesm2022/masterteam-customization.mjs +338 -110
- package/fesm2022/masterteam-customization.mjs.map +1 -1
- package/package.json +8 -7
- package/types/masterteam-customization.d.ts +61 -17
|
@@ -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.
|
|
252
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
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.
|
|
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.
|
|
362
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
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.
|
|
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.
|
|
479
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
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.
|
|
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.
|
|
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
|
|
518
|
-
return
|
|
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.
|
|
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
|
|
546
|
-
|
|
547
|
-
|
|
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.
|
|
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.
|
|
582
|
-
//
|
|
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
|
-
|
|
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.
|
|
595
|
-
|
|
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:
|
|
745
|
+
order: entity.order,
|
|
603
746
|
configuration: existingConfig?.configuration ?? {},
|
|
604
747
|
},
|
|
605
748
|
],
|
|
606
749
|
};
|
|
607
750
|
});
|
|
608
|
-
//
|
|
609
|
-
const
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
*
|
|
618
|
-
*
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
664
|
-
|
|
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.
|
|
679
|
-
const
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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.
|
|
842
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
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.
|
|
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
|
-
|
|
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
|
/**
|