@acorex/platform 20.2.4-next.2 → 20.2.4-next.5

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.
Files changed (36) hide show
  1. package/common/index.d.ts +30 -4
  2. package/core/index.d.ts +5 -1
  3. package/fesm2022/acorex-platform-common.mjs +2 -0
  4. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-core.mjs +4 -1
  6. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-layout-builder.mjs +9 -0
  8. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-components.mjs +26 -36
  10. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-entity.mjs +770 -243
  12. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-views.mjs +37 -24
  14. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  15. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-DXGLsVis.mjs → acorex-platform-themes-default-entity-master-list-view.component-D3VUh8K8.mjs} +5 -4
  16. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D3VUh8K8.mjs.map +1 -0
  17. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-CVaJzWb2.mjs → acorex-platform-themes-default-entity-master-single-view.component-BMkhNfF4.mjs} +3 -3
  18. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BMkhNfF4.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-themes-default.mjs +6 -6
  20. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  21. package/fesm2022/{acorex-platform-widgets-checkbox-widget-column.component-BNBOATPB.mjs → acorex-platform-widgets-checkbox-widget-column.component-DeKpl0uK.mjs} +1 -2
  22. package/fesm2022/acorex-platform-widgets-checkbox-widget-column.component-DeKpl0uK.mjs.map +1 -0
  23. package/fesm2022/{acorex-platform-widgets-file-list-popup.component-B601gPsW.mjs → acorex-platform-widgets-file-list-popup.component-BafU5Lfl.mjs} +4 -2
  24. package/fesm2022/acorex-platform-widgets-file-list-popup.component-BafU5Lfl.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-widgets.mjs +231 -71
  26. package/fesm2022/acorex-platform-widgets.mjs.map +1 -1
  27. package/layout/builder/index.d.ts +4 -0
  28. package/layout/components/index.d.ts +1 -5
  29. package/layout/entity/index.d.ts +4 -5
  30. package/layout/views/index.d.ts +7 -0
  31. package/package.json +1 -1
  32. package/widgets/index.d.ts +25 -2
  33. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DXGLsVis.mjs.map +0 -1
  34. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-CVaJzWb2.mjs.map +0 -1
  35. package/fesm2022/acorex-platform-widgets-checkbox-widget-column.component-BNBOATPB.mjs.map +0 -1
  36. package/fesm2022/acorex-platform-widgets-file-list-popup.component-B601gPsW.mjs.map +0 -1
@@ -10,7 +10,7 @@ import { AXPExpressionEvaluatorService, AXPPlatformScope, AXPDistributedEventLis
10
10
  import * as i2$2 from '@acorex/platform/workflow';
11
11
  import { AXPWorkflowService, ofType, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowModule } from '@acorex/platform/workflow';
12
12
  import * as i2 from '@acorex/platform/layout/builder';
13
- import { AXPPageStatus, AXPWidgetRegistryService, AXPWidgetsCatalog, AXPValueWidgetComponent, AXPWidgetContainerComponent, AXPWidgetRendererDirective, AXPLayoutBuilderModule, AXPWidgetGroupEnum, AXPLayoutWidgetComponent, AXPColumnWidgetComponent, AXP_WIDGETS_EDITOR_CATEGORY } from '@acorex/platform/layout/builder';
13
+ import { AXPPageStatus, AXPWidgetRegistryService, AXPWidgetsCatalog, AXPValueWidgetComponent, AXPWidgetRendererDirective, AXPLayoutBuilderModule, AXPWidgetGroupEnum, AXPLayoutWidgetComponent, AXPColumnWidgetComponent, AXP_WIDGETS_EDITOR_CATEGORY } from '@acorex/platform/layout/builder';
14
14
  import { AXPLayoutThemeService } from '@acorex/platform/themes/shared';
15
15
  import { Subject, takeUntil } from 'rxjs';
16
16
  import { AXPSessionService, AXPAuthGuard } from '@acorex/platform/auth';
@@ -265,7 +265,6 @@ class AXPEntityDetailListViewModel {
265
265
  }, ...(ngDevMode ? [{ debugName: "columns" }] : []));
266
266
  this.evaluateExpressions = async (actionData) => {
267
267
  const parentData = this.parent.data;
268
- console.log({ actionData, parentData });
269
268
  const scope = {
270
269
  context: {
271
270
  eval: (path) => {
@@ -972,6 +971,7 @@ class AXPEntityMasterCreateViewModel {
972
971
  const createProps = interfaces?.master?.create?.properties?.map(({ name }) => name) ?? [];
973
972
  const visibleProperties = properties.filter(({ groupId, schema, name }) => groupId && !schema.hidden && createProps.includes(name));
974
973
  const sections = interfaces?.master?.create?.sections?.filter(({ id }) => visibleProperties.some(({ groupId }) => groupId === id)) ?? [];
974
+ console.log({ sections, visibleProperties });
975
975
  return sections.map((section) => new AXPEntityCreateViewSectionViewModel(this.entityDef, section));
976
976
  }, ...(ngDevMode ? [{ debugName: "sections" }] : []));
977
977
  if (!initialData)
@@ -1157,10 +1157,28 @@ class AXPEntityMasterListViewModel {
1157
1157
  const visibleProperties = properties.filter(({ schema }) => !schema?.hidden);
1158
1158
  const visiblePropNames = new Set(visibleProperties.map(({ name }) => name));
1159
1159
  return columns
1160
- .filter(({ name }) => visiblePropNames.has(name))
1160
+ .filter(({ name, showAs }) => visiblePropNames.has(name) || showAs)
1161
1161
  .map((column) => {
1162
- const property = visibleProperties.find(({ name }) => name === column.name);
1163
- return new AXPEntityListViewColumnViewModel(property, column);
1162
+ if (column.showAs) {
1163
+ const widgetConfig = this.widgetResolver.resolve(column.showAs.type);
1164
+ const property = {
1165
+ ...widgetConfig,
1166
+ name: column.name,
1167
+ title: column.title ?? '',
1168
+ schema: {
1169
+ dataType: 'string',
1170
+ interface: {
1171
+ type: column.showAs.type,
1172
+ options: column.showAs.options,
1173
+ },
1174
+ },
1175
+ };
1176
+ return new AXPEntityListViewColumnViewModel(property, column);
1177
+ }
1178
+ else {
1179
+ const property = visibleProperties.find(({ name }) => name === column.name);
1180
+ return new AXPEntityListViewColumnViewModel(property, column);
1181
+ }
1164
1182
  });
1165
1183
  };
1166
1184
  this.visibleColumnCount = () => {
@@ -1459,8 +1477,7 @@ class AXPEntityMasterListViewModel {
1459
1477
  const cols = this.view().columns;
1460
1478
  const cloned = this.allAvailableColumns().map((c) => {
1461
1479
  const column = this.entityDef.columns?.find((cc) => cc.name == c.name);
1462
- const prop = this.entityDef.properties.find((p) => p.name == c.name);
1463
- const col = new AXPEntityListViewColumnViewModel(prop, column);
1480
+ const col = new AXPEntityListViewColumnViewModel(c.property, column);
1464
1481
  col.visible = !cols.some((c) => c == col.name) && col.visible != false;
1465
1482
  return col;
1466
1483
  });
@@ -1976,7 +1993,6 @@ class AXPEntityMasterSingleViewGroupViewModel {
1976
1993
  this.group = this.entity.groups?.find((c) => c.id == this.section.id);
1977
1994
  this.name = signal(this.group.id, ...(ngDevMode ? [{ debugName: "name" }] : []));
1978
1995
  this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1979
- this.type = signal(this.section.type ?? 'section', ...(ngDevMode ? [{ debugName: "type" }] : []));
1980
1996
  this.title = computed(() => {
1981
1997
  return this.group.title ?? this.group.id;
1982
1998
  }, ...(ngDevMode ? [{ debugName: "title" }] : []));
@@ -2556,9 +2572,15 @@ class AXPLayoutAdapterBuilder {
2556
2572
  this.adapter.pages = [...(this.adapter.pages || []), ...relatedPages];
2557
2573
  return this;
2558
2574
  }
2575
+ setPages(pages) {
2576
+ this.adapter.pages = pages;
2577
+ return this;
2578
+ }
2559
2579
  build() {
2560
2580
  return {
2561
- title: this.entity?.formats.plural || this.entity?.title,
2581
+ name: `${this.entity?.module}.${this.entity?.name}`,
2582
+ title: `${this.entity?.interfaces?.master?.single?.title}`,
2583
+ label: this.entity?.formats.plural,
2562
2584
  actions: [],
2563
2585
  breadcrumbs: this.createBreadcrumbs(),
2564
2586
  execute: this.createExecuteFunction(),
@@ -2585,7 +2607,7 @@ class AXPLayoutAdapterBuilder {
2585
2607
  command: {
2586
2608
  name: 'navigate',
2587
2609
  options: {
2588
- path: `/${session.application?.name}/m/${moduleName}/e/${entityName}/${this.rootContext.id}/view`,
2610
+ path: `/${session.application?.name}/m/${moduleName}/e/${entityName}/${this.rootContext.id}/new-view`,
2589
2611
  },
2590
2612
  },
2591
2613
  },
@@ -2655,40 +2677,29 @@ class AXPBaseRelatedEntityConverter {
2655
2677
  groups,
2656
2678
  };
2657
2679
  }
2658
- createGridLayoutStructure(singleInterface, helpers) {
2659
- return {
2660
- type: 'grid-layout',
2661
- options: {
2662
- grid: {
2663
- default: {
2664
- gridTemplateColumns: 'repeat(12, 1fr)',
2665
- gridTemplateRows: 'repeat(1, 1fr)',
2666
- gap: '20px',
2667
- },
2668
- },
2669
- },
2670
- children: singleInterface?.sections.map((s) => ({
2671
- type: 'grid-item-layout',
2672
- name: s.id,
2673
- options: {
2674
- colSpan: s.layout?.positions?.lg?.colSpan ?? 12,
2675
- colStart: s.layout?.positions?.lg?.colStart,
2676
- colEnd: s.layout?.positions?.lg?.colEnd,
2677
- },
2678
- children: [
2679
- {
2680
- type: 'fieldset-layout',
2681
- options: {
2682
- title: helpers.getGroupById(s.id)?.title ?? '',
2683
- collapsible: true,
2684
- },
2685
- children: [this.createPropertyGrid(s.id, helpers)],
2686
- },
2687
- ],
2688
- })),
2689
- };
2680
+ async createGridLayoutStructure(singleInterface, helpers, evaluateExpressions) {
2681
+ return await this.createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions);
2690
2682
  }
2691
- createPropertyGrid(sectionId, helpers) {
2683
+ //#region ---- Hidden Evaluation Helpers ----
2684
+ async getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions) {
2685
+ const properties = helpers.getPropertyByGroupId(sectionId) ?? [];
2686
+ const evaluated = await Promise.all(properties.map(async (property) => {
2687
+ let hidden = property?.schema?.hidden;
2688
+ if (typeof hidden === 'string' && evaluateExpressions) {
2689
+ try {
2690
+ const result = await evaluateExpressions({ hidden });
2691
+ hidden = result.hidden;
2692
+ }
2693
+ catch {
2694
+ hidden = false;
2695
+ }
2696
+ }
2697
+ return { property, hidden: !!hidden };
2698
+ }));
2699
+ return evaluated.filter((x) => !x.hidden).map((x) => x.property);
2700
+ }
2701
+ async createPropertyGrid(sectionId, helpers, evaluateExpressions) {
2702
+ const visibleProperties = await this.getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions);
2692
2703
  return {
2693
2704
  type: 'grid-layout',
2694
2705
  mode: 'edit',
@@ -2701,10 +2712,7 @@ class AXPBaseRelatedEntityConverter {
2701
2712
  },
2702
2713
  },
2703
2714
  },
2704
- children: helpers
2705
- .getPropertyByGroupId(sectionId)
2706
- .filter((property) => !property.schema.hidden)
2707
- .map((p) => {
2715
+ children: visibleProperties.map((p) => {
2708
2716
  const layout = helpers.getPropertyLayout(p.name);
2709
2717
  return {
2710
2718
  type: 'grid-item-layout',
@@ -2719,6 +2727,7 @@ class AXPBaseRelatedEntityConverter {
2719
2727
  type: 'form-field',
2720
2728
  options: {
2721
2729
  label: p.title,
2730
+ showLabel: layout?.label?.visible ?? true,
2722
2731
  },
2723
2732
  children: [
2724
2733
  {
@@ -2738,12 +2747,58 @@ class AXPBaseRelatedEntityConverter {
2738
2747
  }),
2739
2748
  };
2740
2749
  }
2750
+ async createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions) {
2751
+ return {
2752
+ type: 'grid-layout',
2753
+ options: {
2754
+ grid: {
2755
+ default: {
2756
+ gridTemplateColumns: 'repeat(12, 1fr)',
2757
+ gridTemplateRows: 'repeat(1, 1fr)',
2758
+ gap: '20px',
2759
+ },
2760
+ },
2761
+ },
2762
+ children: await Promise.all(singleInterface?.sections.map(async (s) => ({
2763
+ type: 'grid-item-layout',
2764
+ name: s.id,
2765
+ options: {
2766
+ colSpan: s.layout?.positions?.lg?.colSpan ?? 12,
2767
+ colStart: s.layout?.positions?.lg?.colStart,
2768
+ colEnd: s.layout?.positions?.lg?.colEnd,
2769
+ },
2770
+ children: [
2771
+ {
2772
+ type: 'fieldset-layout',
2773
+ options: {
2774
+ title: helpers.getGroupById(s.id)?.title ?? '',
2775
+ collapsible: true,
2776
+ },
2777
+ children: [await this.createPropertyGrid(s.id, helpers, evaluateExpressions)],
2778
+ },
2779
+ ],
2780
+ }))),
2781
+ };
2782
+ }
2741
2783
  }
2742
2784
 
2743
2785
  class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2744
2786
  async convert(relatedEntity, context) {
2745
2787
  const { entityDef } = await this.getEntityDefinition(relatedEntity, context.entityResolver);
2746
- const helpers = this.createEntityHelpers(entityDef);
2788
+ const baseHelpers = this.createEntityHelpers(entityDef);
2789
+ // If referenced strategy with mapping and detail type, exclude mapped properties from UI
2790
+ const p = relatedEntity?.persistence || {};
2791
+ const strategy = p.strategy || 'embedded';
2792
+ const mappedTargets = Object.keys(p.map || {}).map((k) => (k || '').split('.')[0]);
2793
+ const helpers = {
2794
+ ...baseHelpers,
2795
+ getPropertyByGroupId: (groupId) => {
2796
+ const props = baseHelpers.getPropertyByGroupId(groupId) ?? [];
2797
+ if (strategy !== 'referenced' || mappedTargets.length === 0)
2798
+ return props;
2799
+ return props.filter((prop) => !mappedTargets.includes(prop?.name));
2800
+ },
2801
+ };
2747
2802
  const evaluateExpressions = async (actionData) => {
2748
2803
  const scope = {
2749
2804
  context: {
@@ -2754,17 +2809,25 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2754
2809
  };
2755
2810
  return await context.expressionEvaluator.evaluate(actionData, scope);
2756
2811
  };
2812
+ // Build related tabs for the related entity page (mirrors main-entity tab building)
2813
+ const tabDetailEntities = entityDef?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'tab-detail');
2814
+ const tabListEntities = entityDef?.relatedEntities?.filter((re) => !re.hidden && (!re.layout?.type || re.layout?.type === 'tab-list'));
2815
+ const factory = new AXPRelatedEntityConverterFactory();
2816
+ const tabDetailTabs = await this.buildTabDetails(factory, tabDetailEntities ?? [], context);
2817
+ const tabListTabs = await this.buildTabLists(factory, tabListEntities ?? [], context);
2757
2818
  return {
2758
2819
  id: entityDef?.name ?? '',
2759
- title: relatedEntity.title ?? entityDef?.title ?? '',
2820
+ title: `${context.rootTitle}`,
2760
2821
  label: relatedEntity.title ?? entityDef?.formats.displayName ?? '',
2761
2822
  icon: relatedEntity.icon || entityDef.icon,
2762
2823
  settings: this.createPageSettings(),
2763
2824
  load: this.createLoadFunction(entityDef, relatedEntity, evaluateExpressions),
2764
2825
  execute: this.createExecuteFunction(entityDef),
2765
- content: [this.createGridLayoutStructure(helpers.singleInterface, helpers)],
2826
+ // tabs: [...tabDetailTabs, ...tabListTabs],
2827
+ content: [await this.createGridLayoutStructure(helpers.singleInterface, helpers, evaluateExpressions)],
2766
2828
  };
2767
2829
  }
2830
+ //#region ---- Utility Methods ----
2768
2831
  createPageSettings() {
2769
2832
  return {
2770
2833
  commands: {
@@ -2787,7 +2850,7 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2787
2850
  return async (context) => {
2788
2851
  const fn = entityDef?.queries.byKey?.execute;
2789
2852
  const conditionValues = relatedEntity.conditions?.map((c) => c.value) ?? [];
2790
- const evaluatedConditionValues = await evaluateExpressions(conditionValues);
2853
+ const evaluatedConditionValues = await Promise.all(conditionValues.map((c) => evaluateExpressions(c)));
2791
2854
  const id = evaluatedConditionValues[0];
2792
2855
  const result = await fn(id);
2793
2856
  return { success: true, result };
@@ -2808,6 +2871,42 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2808
2871
  }
2809
2872
  };
2810
2873
  }
2874
+ //#endregion
2875
+ //#region ---- Tab Builders ----
2876
+ /**
2877
+ * Builds tab-detail items for a related entity page using the shared converters.
2878
+ */
2879
+ async buildTabDetails(factory, tabDetailEntities, ctx) {
2880
+ if (!ctx?.entityResolver || !tabDetailEntities?.length) {
2881
+ return [];
2882
+ }
2883
+ const tabs = [];
2884
+ for (const re of tabDetailEntities) {
2885
+ const converter = factory.createTabDetailsConverter();
2886
+ tabs.push(await converter.convert(re, {
2887
+ entityResolver: ctx.entityResolver,
2888
+ }));
2889
+ }
2890
+ return tabs;
2891
+ }
2892
+ /**
2893
+ * Builds tab-list items for a related entity page using the shared converters with context-aware filters/actions.
2894
+ */
2895
+ async buildTabLists(factory, tabListEntities, ctx) {
2896
+ if (!ctx?.entityResolver || !tabListEntities?.length) {
2897
+ return [];
2898
+ }
2899
+ const tabs = [];
2900
+ for (const re of tabListEntities) {
2901
+ const converter = factory.createTabListConverter();
2902
+ tabs.push(await converter.convert(re, {
2903
+ entityResolver: ctx.entityResolver,
2904
+ expressionEvaluator: ctx.expressionEvaluator,
2905
+ context: ctx.context,
2906
+ }));
2907
+ }
2908
+ return tabs;
2909
+ }
2811
2910
  }
2812
2911
 
2813
2912
  class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
@@ -2823,6 +2922,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2823
2922
  };
2824
2923
  return await context.expressionEvaluator.evaluate(actionData, scope);
2825
2924
  };
2925
+ const evaluatedActions = await evaluateExpressions(relatedEntity?.actions);
2826
2926
  const filters = relatedEntity.conditions?.map(async (c) => {
2827
2927
  const value = await evaluateExpressions(c.value);
2828
2928
  return {
@@ -2837,17 +2937,13 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2837
2937
  title: `${context.rootTitle}`,
2838
2938
  label: relatedEntity.title,
2839
2939
  icon: relatedEntity.icon || entityDef.icon,
2840
- actions: this.mergeActions(entityDef, relatedEntity)
2940
+ actions: this.mergeActions(entityDef, evaluatedActions)
2841
2941
  ?.filter((a) => a.priority === 'primary')
2842
2942
  ?.map((a) => {
2843
2943
  return {
2844
2944
  ...a,
2845
2945
  zone: 'header',
2846
- // visible:
2847
- // a.scope === AXPEntityCommandScope.Selected
2848
- // ? "{{widget.find('table').outputs().selectedItem().length > 0}}"
2849
- // : true,
2850
- visible: '{{context.eval("table")}}',
2946
+ visible: !a.hidden,
2851
2947
  priority: 'primary',
2852
2948
  name: a.name,
2853
2949
  title: a.title,
@@ -2865,7 +2961,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2865
2961
  execute: async (command, executeContext) => {
2866
2962
  try {
2867
2963
  const commandName = command.name.split('&')[0];
2868
- const mergedActions = this.mergeActions(entityDef, relatedEntity);
2964
+ const mergedActions = this.mergeActions(entityDef, evaluatedActions);
2869
2965
  const action = mergedActions.find((a) => {
2870
2966
  return a.name === commandName || a.name.split('&')[0] === commandName;
2871
2967
  });
@@ -2880,10 +2976,6 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2880
2976
  },
2881
2977
  };
2882
2978
  }
2883
- let evaluatedOptions = command.options;
2884
- if (action.options) {
2885
- evaluatedOptions = await evaluateExpressions(action.options);
2886
- }
2887
2979
  await context.workflowService.execute(commandName, {
2888
2980
  entity: getEntityInfo(entityDef).source,
2889
2981
  entityInfo: {
@@ -2895,8 +2987,8 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2895
2987
  },
2896
2988
  data: action.scope == AXPEntityCommandScope.Selected
2897
2989
  ? executeContext
2898
- : evaluatedOptions?.['process']?.data || null,
2899
- options: evaluatedOptions,
2990
+ : action.options?.['process']?.data || null,
2991
+ options: action.options,
2900
2992
  metadata: action.metadata,
2901
2993
  });
2902
2994
  return { success: true };
@@ -2925,14 +3017,15 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2925
3017
  options: {
2926
3018
  entity: relatedEntity.entity,
2927
3019
  showEntityActions: false,
3020
+ actions: evaluatedActions,
2928
3021
  },
2929
3022
  },
2930
3023
  ],
2931
3024
  };
2932
3025
  }
2933
- mergeActions(entityDef, relatedEntity) {
3026
+ mergeActions(entityDef, relatedEntityActions) {
2934
3027
  const originalList = entityDef?.interfaces?.master?.list?.actions ?? [];
2935
- const relatedEntityActionList = relatedEntity.actions ?? [];
3028
+ const relatedEntityActionList = relatedEntityActions ?? [];
2936
3029
  // Create a map to track which actions from relatedEntityActionList have been used
2937
3030
  const usedOverrideActions = new Set();
2938
3031
  // Start with original actions, applying overrides where they exist
@@ -2964,12 +3057,25 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2964
3057
  class AXPTabDetailsConverter extends AXPBaseRelatedEntityConverter {
2965
3058
  async convert(relatedEntity, context) {
2966
3059
  const { entityDef } = await this.getEntityDefinition(relatedEntity, context.entityResolver);
2967
- const helpers = this.createEntityHelpers(entityDef);
3060
+ const baseHelpers = this.createEntityHelpers(entityDef);
3061
+ // If referenced strategy with mapping and detail type, exclude mapped properties from UI
3062
+ const p = relatedEntity?.persistence || {};
3063
+ const strategy = p.strategy || 'embedded';
3064
+ const mappedTargets = Object.keys(p.map || {}).map((k) => (k || '').split('.')[0]);
3065
+ const helpers = {
3066
+ ...baseHelpers,
3067
+ getPropertyByGroupId: (groupId) => {
3068
+ const props = baseHelpers.getPropertyByGroupId(groupId) ?? [];
3069
+ if (strategy !== 'referenced' || mappedTargets.length === 0)
3070
+ return props;
3071
+ return props.filter((prop) => !mappedTargets.includes(prop?.name));
3072
+ },
3073
+ };
2968
3074
  return {
2969
3075
  id: entityDef?.name ?? '',
2970
3076
  title: relatedEntity.title ?? entityDef?.title ?? '',
2971
3077
  icon: relatedEntity.icon || entityDef.icon,
2972
- content: [this.createGridLayoutStructure(helpers.singleInterface, helpers)],
3078
+ content: [await this.createGridLayoutStructure(helpers.singleInterface, helpers)],
2973
3079
  };
2974
3080
  }
2975
3081
  }
@@ -3066,41 +3172,270 @@ class AXPMainEntityContentBuilder {
3066
3172
  this.workflowService = inject(AXPWorkflowService);
3067
3173
  this.commandService = inject(AXPCommandService);
3068
3174
  }
3069
- async build(entity, rootContext, dependencies) {
3175
+ async build(entity, rootContext, dependencies, rootTitle) {
3070
3176
  const groups = entity?.groups ?? [];
3071
3177
  const singleInterface = entity?.interfaces?.master?.single;
3178
+ // Accumulate groups from main and merge-detail related entities for validation/title lookup
3179
+ const allGroups = [...groups];
3180
+ // Prepare merge-details structures (sections and properties per section)
3181
+ const mergeDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'merge-detail');
3182
+ const mainPropsByGroup = (entity?.properties ?? []).reduce((acc, p) => {
3183
+ if (!acc[p.groupId])
3184
+ acc[p.groupId] = [];
3185
+ acc[p.groupId].push(p);
3186
+ return acc;
3187
+ }, {});
3188
+ // Will hold merged properties per group (section)
3189
+ const mergedPropsByGroup = { ...mainPropsByGroup };
3190
+ // Collect sections from main interface (validated later) and related ones for ordering
3191
+ const mainSections = singleInterface?.sections ?? [];
3192
+ // Track extra sections with entity-level ordering context
3193
+ const beforeExtraSections = [];
3194
+ const middleExtraSections = [];
3195
+ const afterExtraSections = [];
3196
+ const beforeExtraIds = new Set();
3197
+ const middleExtraIds = new Set();
3198
+ const afterExtraIds = new Set();
3199
+ // Effective order overrides for main sections when merged with BEFORE related entities
3200
+ let beforeOverrideSectionOrders = {};
3201
+ // Collect tab-list tabs from merge-detail related entities to merge with main tabs
3202
+ const nestedTabListTabs = [];
3203
+ // Helper to append related-only sections into before/after buckets
3204
+ const queueRelatedSection = (section, position, entityOrder) => {
3205
+ if (!section)
3206
+ return;
3207
+ if (position === 'before') {
3208
+ if (!beforeExtraIds.has(section.id)) {
3209
+ beforeExtraIds.add(section.id);
3210
+ beforeExtraSections.push({ section, entityOrder });
3211
+ }
3212
+ }
3213
+ else if (position === 'middle') {
3214
+ if (!middleExtraIds.has(section.id)) {
3215
+ middleExtraIds.add(section.id);
3216
+ middleExtraSections.push({ section });
3217
+ }
3218
+ }
3219
+ else {
3220
+ if (!afterExtraIds.has(section.id)) {
3221
+ afterExtraIds.add(section.id);
3222
+ afterExtraSections.push({ section, entityOrder });
3223
+ }
3224
+ }
3225
+ };
3226
+ // Resolve and merge merge-detail entities
3227
+ // Sort related entities by position group then by layout.order
3228
+ if (mergeDetailEntities?.length && dependencies?.entityResolver) {
3229
+ const beforeEntities = (mergeDetailEntities || [])
3230
+ .filter((re) => (re.layout?.position ?? 'middle') === 'before')
3231
+ .sort((a, b) => (a.layout?.order ?? Number.MAX_SAFE_INTEGER) - (b.layout?.order ?? Number.MAX_SAFE_INTEGER));
3232
+ const middleEntities = (mergeDetailEntities || []).filter((re) => (re.layout?.position ?? 'middle') === 'middle');
3233
+ const afterEntities = (mergeDetailEntities || [])
3234
+ .filter((re) => (re.layout?.position ?? 'middle') === 'after')
3235
+ .sort((a, b) => (a.layout?.order ?? Number.MAX_SAFE_INTEGER) - (b.layout?.order ?? Number.MAX_SAFE_INTEGER));
3236
+ const processRelated = async (relatedEntity) => {
3237
+ try {
3238
+ const [moduleName, entityName] = relatedEntity.entity.split('.');
3239
+ const entityDef = await dependencies.entityResolver.resolve(moduleName, entityName);
3240
+ if (!entityDef) {
3241
+ return;
3242
+ }
3243
+ const relSingle = entityDef?.interfaces?.master?.single;
3244
+ const relGroups = entityDef?.groups ?? [];
3245
+ // Merge related groups into allGroups (avoid duplicates by id)
3246
+ for (const rg of relGroups) {
3247
+ if (!allGroups.some((g) => g.id === rg.id)) {
3248
+ allGroups.push(rg);
3249
+ }
3250
+ }
3251
+ // Collect nested tab-list related entities from this merge-detail entity and convert to tabs
3252
+ try {
3253
+ const nestedTabListEntities = entityDef?.relatedEntities?.filter((re) => !re.hidden && (!re.layout?.type || re.layout?.type === 'tab-list'));
3254
+ if (nestedTabListEntities?.length) {
3255
+ const converter = this.relatedEntityConverterFactory.createTabListConverter();
3256
+ const nestedDataPath = relatedEntity?.persistence?.dataPath || entityName;
3257
+ const nestedContext = get(rootContext, nestedDataPath) ?? rootContext;
3258
+ for (const nre of nestedTabListEntities) {
3259
+ try {
3260
+ const tab = await converter.convert(nre, {
3261
+ entityResolver: dependencies.entityResolver,
3262
+ expressionEvaluator: dependencies.expressionEvaluator,
3263
+ // Evaluate conditions relative to the merge-detail entity's dataPath
3264
+ context: nestedContext,
3265
+ });
3266
+ nestedTabListTabs.push(tab);
3267
+ }
3268
+ catch { }
3269
+ }
3270
+ }
3271
+ }
3272
+ catch { }
3273
+ // Build related props map per group with metadata: __dataPath and __layout
3274
+ // If referenced strategy with mapping, exclude mapped target property names from UI props
3275
+ const p = relatedEntity?.persistence || {};
3276
+ const strategy = p.strategy || 'embedded';
3277
+ const mappedTargets = Object.keys(p.map || {}).map((k) => (k || '').split('.')[0]);
3278
+ const relPropsByGroup = (entityDef?.properties ?? []).reduce((acc, p) => {
3279
+ if (strategy === 'referenced' &&
3280
+ (relatedEntity?.layout?.type === 'merge-detail' ||
3281
+ relatedEntity?.layout?.type === 'page-detail' ||
3282
+ relatedEntity?.layout?.type === 'tab-detail') &&
3283
+ mappedTargets.includes(p.name)) {
3284
+ return acc; // skip mapped properties
3285
+ }
3286
+ if (!acc[p.groupId])
3287
+ acc[p.groupId] = [];
3288
+ // Clone shallow to not mutate original definitions
3289
+ const propClone = { ...p };
3290
+ propClone.__dataPath = relatedEntity?.persistence?.dataPath || entityName;
3291
+ propClone.__layout = relSingle?.properties?.find((x) => x.name === p.name)?.layout;
3292
+ acc[p.groupId].push(propClone);
3293
+ return acc;
3294
+ }, {});
3295
+ // Merge properties by section id
3296
+ const position = relatedEntity.layout?.position ?? 'middle';
3297
+ const entityOrder = relatedEntity.layout?.order ?? Number.MAX_SAFE_INTEGER;
3298
+ const relSections = (relSingle?.sections ?? []).filter((s) => relGroups.some((g) => g.id === s.id));
3299
+ for (const rs of relSections) {
3300
+ const sectionId = rs.id;
3301
+ const mainHasSection = !!mainSections?.some((s) => s.id === sectionId);
3302
+ const relProps = relPropsByGroup[sectionId] ?? [];
3303
+ if (mainHasSection) {
3304
+ const current = mergedPropsByGroup[sectionId] ?? [];
3305
+ mergedPropsByGroup[sectionId] =
3306
+ position === 'before' ? [...relProps, ...current] : [...current, ...relProps];
3307
+ // Capture override order for main section when position is 'before'
3308
+ if (position === 'before') {
3309
+ const order = rs?.order ?? 0;
3310
+ const prev = beforeOverrideSectionOrders[sectionId] ?? Number.MAX_SAFE_INTEGER;
3311
+ beforeOverrideSectionOrders[sectionId] = Math.min(prev, order);
3312
+ }
3313
+ }
3314
+ else {
3315
+ // Section isn't in main; queue section for rendering and set its props
3316
+ if (!mergedPropsByGroup[sectionId]) {
3317
+ mergedPropsByGroup[sectionId] = [];
3318
+ }
3319
+ mergedPropsByGroup[sectionId] =
3320
+ position === 'before'
3321
+ ? [...(relProps ?? []), ...(mergedPropsByGroup[sectionId] ?? [])]
3322
+ : [...(mergedPropsByGroup[sectionId] ?? []), ...(relProps ?? [])];
3323
+ queueRelatedSection(rs, position, entityOrder);
3324
+ }
3325
+ }
3326
+ }
3327
+ catch {
3328
+ // Silently ignore failures to resolve or merge
3329
+ }
3330
+ };
3331
+ // Process BEFORE entities first (sorted), then AFTER entities
3332
+ for (const re of beforeEntities) {
3333
+ await processRelated(re);
3334
+ }
3335
+ for (const re of middleEntities) {
3336
+ await processRelated(re);
3337
+ }
3338
+ for (const re of afterEntities) {
3339
+ await processRelated(re);
3340
+ }
3341
+ }
3072
3342
  const getGroupById = (id) => {
3073
3343
  return groups.find((s) => s.id === id);
3074
3344
  };
3075
3345
  const filterValidSections = (sections) => {
3076
3346
  return (sections?.filter((section) => {
3077
- return groups.some((group) => group.id === section.id);
3347
+ return allGroups.some((group) => group.id === section.id);
3078
3348
  }) ?? []);
3079
3349
  };
3080
- const getPropertyByGroupId = (groupId) => {
3081
- return entity?.properties.filter((p) => p.groupId === groupId) ?? [];
3350
+ // Utility: sort related-only sections by entity order then section order
3351
+ const sortRelatedSections = (sections) => [...(sections ?? [])]
3352
+ .filter((e) => !!e?.section)
3353
+ .sort((a, b) => {
3354
+ const ae = a.entityOrder ?? Number.MAX_SAFE_INTEGER;
3355
+ const be = b.entityOrder ?? Number.MAX_SAFE_INTEGER;
3356
+ if (ae !== be)
3357
+ return ae - be;
3358
+ const ao = a.section?.order ?? 0;
3359
+ const bo = b.section?.order ?? 0;
3360
+ return ao - bo;
3361
+ })
3362
+ .map((e) => e.section);
3363
+ // Determine effective order for main sections: if any BEFORE related merged that section, use its order; otherwise keep main order
3364
+ const computeEffectiveMainOrder = (s) => {
3365
+ const defaultOrder = s?.order ?? 0;
3366
+ const beforeOverride = beforeOverrideSectionOrders?.[s?.id ?? ''];
3367
+ return beforeOverride != null ? beforeOverride : defaultOrder;
3082
3368
  };
3083
- const getPropertyLayout = (name) => {
3084
- return singleInterface?.properties?.find((p) => p.name === name)?.layout;
3369
+ // Build combined section list:
3370
+ // 1) related-before (entity-sorted)
3371
+ // 2) middle block: main sections + related-middle-only sections sorted by their section.order (with effective order for main)
3372
+ // 3) related-after (entity-sorted)
3373
+ const beforeSections = sortRelatedSections(beforeExtraSections);
3374
+ const mainSectionsList = [...filterValidSections(mainSections)];
3375
+ const middleOnlySections = middleExtraSections.map((e) => e.section);
3376
+ const middleBlock = [...mainSectionsList, ...middleOnlySections].sort((a, b) => {
3377
+ const ao = computeEffectiveMainOrder(a);
3378
+ const bo = computeEffectiveMainOrder(b);
3379
+ return ao - bo;
3380
+ });
3381
+ const afterSections = sortRelatedSections(afterExtraSections);
3382
+ const combinedSections = [...beforeSections, ...middleBlock, ...afterSections];
3383
+ // Debug: Log final sorted sections for verification
3384
+ try {
3385
+ const beforeEntityOrderMap = new Map(beforeExtraSections.map((e) => [e.section?.id, e.entityOrder ?? Number.MAX_SAFE_INTEGER]));
3386
+ const afterEntityOrderMap = new Map(afterExtraSections.map((e) => [e.section?.id, e.entityOrder ?? Number.MAX_SAFE_INTEGER]));
3387
+ const debugSections = [
3388
+ ...beforeSections.map((s) => ({
3389
+ bucket: 'before',
3390
+ id: s?.id,
3391
+ sectionOrder: s?.order ?? 0,
3392
+ entityOrder: beforeEntityOrderMap.get(s?.id) ?? Number.MAX_SAFE_INTEGER,
3393
+ })),
3394
+ ...middleBlock.map((s) => ({
3395
+ bucket: 'middle',
3396
+ id: s?.id,
3397
+ sectionOrder: s?.order ?? 0,
3398
+ effectiveOrder: computeEffectiveMainOrder(s),
3399
+ })),
3400
+ ...afterSections.map((s) => ({
3401
+ bucket: 'after',
3402
+ id: s?.id,
3403
+ sectionOrder: s?.order ?? 0,
3404
+ entityOrder: afterEntityOrderMap.get(s?.id) ?? Number.MAX_SAFE_INTEGER,
3405
+ })),
3406
+ ];
3407
+ // eslint-disable-next-line no-console
3408
+ console.debug('[AXP] merge-details sorted sections', debugSections);
3409
+ }
3410
+ catch { }
3411
+ // Create expression evaluator for actions
3412
+ const evaluateExpressions = dependencies?.expressionEvaluator
3413
+ ? this.createExpressionEvaluator(rootContext, dependencies.expressionEvaluator)
3414
+ : null;
3415
+ const getVisiblePropertyByGroupId = async (groupId) => {
3416
+ return (mergedPropsByGroup[groupId] ?? []);
3417
+ };
3418
+ const getPropertyLayout = (name, prop) => {
3419
+ // Prefer per-property layout (for merged related props), fallback to main entity layout
3420
+ return prop?.__layout ?? singleInterface?.properties?.find((p) => p.name === name)?.layout;
3085
3421
  };
3086
3422
  // Get related entities for tabs
3087
- const tabDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layoutType === 'tab-detail');
3088
- const tabListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && (!re.layoutType || re.layoutType === 'tab-list'));
3423
+ const tabDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'tab-detail');
3424
+ const tabListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && (!re.layout?.type || re.layout?.type === 'tab-list'));
3089
3425
  // Build related tabs if dependencies are provided
3090
3426
  const tabDetailTabs = await this.buildTabDetails(tabDetailEntities ?? [], dependencies);
3091
3427
  const tabListTabs = await this.buildTabLists(tabListEntities ?? [], rootContext, dependencies);
3092
3428
  // Build actions from single interface
3093
3429
  const actions = this.buildActions(entity, singleInterface);
3094
- // Create expression evaluator for actions
3095
- const evaluateExpressions = dependencies?.expressionEvaluator
3096
- ? this.createExpressionEvaluator(rootContext, dependencies.expressionEvaluator)
3097
- : null;
3430
+ console.log({ rootContext, dependencies });
3098
3431
  return {
3099
3432
  id: entity?.name ?? '',
3100
- title: singleInterface?.title ?? entity?.formats.individual ?? '',
3433
+ // title: singleInterface?.title ?? entity?.formats.individual ?? '',
3434
+ title: rootTitle ?? entity?.formats.individual,
3101
3435
  label: entity?.formats.displayName ?? singleInterface?.title ?? '',
3102
3436
  icon: entity?.icon,
3103
3437
  actions: await this.buildEvaluatedActions(actions, evaluateExpressions),
3438
+ isPrimary: true,
3104
3439
  settings: {
3105
3440
  commands: {
3106
3441
  reject: {
@@ -3183,7 +3518,7 @@ class AXPMainEntityContentBuilder {
3183
3518
  };
3184
3519
  }
3185
3520
  },
3186
- tabs: [...tabDetailTabs, ...tabListTabs],
3521
+ tabs: [...tabDetailTabs, ...tabListTabs, ...nestedTabListTabs],
3187
3522
  content: [
3188
3523
  {
3189
3524
  type: 'grid-layout',
@@ -3197,7 +3532,7 @@ class AXPMainEntityContentBuilder {
3197
3532
  },
3198
3533
  },
3199
3534
  },
3200
- children: filterValidSections(singleInterface?.sections ?? []).map((s) => ({
3535
+ children: await Promise.all(combinedSections.map(async (s) => ({
3201
3536
  type: 'grid-item-layout',
3202
3537
  name: s.id,
3203
3538
  options: {
@@ -3209,9 +3544,10 @@ class AXPMainEntityContentBuilder {
3209
3544
  {
3210
3545
  type: 'fieldset-layout',
3211
3546
  options: {
3212
- title: getGroupById(s.id)?.title ?? '',
3547
+ title: allGroups.find((g) => g.id === s.id)?.title ?? '',
3213
3548
  collapsible: true,
3214
3549
  isOpen: !s.collapsed,
3550
+ look: 'card'
3215
3551
  },
3216
3552
  children: [
3217
3553
  {
@@ -3225,29 +3561,30 @@ class AXPMainEntityContentBuilder {
3225
3561
  },
3226
3562
  },
3227
3563
  },
3228
- children: getPropertyByGroupId(s.id)
3229
- .filter((property) => !property.schema.hidden)
3230
- .map((p) => {
3231
- const layout = getPropertyLayout(p.name);
3564
+ children: (await getVisiblePropertyByGroupId(s.id)).map((p) => {
3565
+ const layout = getPropertyLayout(p.name, p);
3566
+ const prefixed = p.__dataPath ? `${p.__dataPath}.${p.name}` : p.name;
3232
3567
  return {
3233
3568
  type: 'grid-item-layout',
3234
- name: p.name,
3569
+ name: prefixed,
3235
3570
  options: {
3236
3571
  colSpan: layout?.positions?.lg?.colSpan,
3237
3572
  colStart: layout?.positions?.lg?.colStart,
3238
3573
  colEnd: layout?.positions?.lg?.colEnd,
3574
+ hidden: p.schema.hidden,
3239
3575
  },
3240
3576
  children: [
3241
3577
  {
3242
3578
  type: 'form-field',
3243
3579
  options: {
3244
3580
  label: p.title,
3581
+ showLabel: layout?.label?.visible ?? true,
3245
3582
  },
3246
3583
  children: [
3247
3584
  {
3248
3585
  type: p.schema.interface?.type ?? '',
3249
- path: p.name,
3250
- name: p.name,
3586
+ path: prefixed,
3587
+ name: prefixed,
3251
3588
  defaultValue: p.schema.defaultValue,
3252
3589
  children: p.schema.interface?.children,
3253
3590
  triggers: p.schema.interface?.triggers,
@@ -3258,6 +3595,8 @@ class AXPMainEntityContentBuilder {
3258
3595
  message: c.message,
3259
3596
  options: c.options,
3260
3597
  })),
3598
+ // Attach dataPath for merged properties to be available in widgets/options if needed
3599
+ dataPath: p.__dataPath,
3261
3600
  }),
3262
3601
  },
3263
3602
  ],
@@ -3269,7 +3608,7 @@ class AXPMainEntityContentBuilder {
3269
3608
  ],
3270
3609
  },
3271
3610
  ],
3272
- })),
3611
+ }))),
3273
3612
  },
3274
3613
  ],
3275
3614
  };
@@ -3283,7 +3622,8 @@ class AXPMainEntityContentBuilder {
3283
3622
  const scope = {
3284
3623
  context: {
3285
3624
  eval: (path) => {
3286
- return get(context, path);
3625
+ const value = get(context, path);
3626
+ return value;
3287
3627
  },
3288
3628
  },
3289
3629
  };
@@ -3395,11 +3735,15 @@ class AXPLayoutAdapterFactory {
3395
3735
  throw new Error(`Entity ${moduleName}.${entityName} not found`);
3396
3736
  }
3397
3737
  const rootContext = await this.loadRootContext(entity, id);
3738
+ // Build main and related pages
3739
+ const mainPage = await this.buildMainPage(entity, rootContext, dependencies);
3740
+ const relatedPages = await this.buildRelatedPages(entity, rootContext, dependencies);
3741
+ // Compose ordered pages around the primary page
3742
+ const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entity);
3398
3743
  return this.layoutAdapterBuilder
3399
3744
  .setEntity(entity, rootContext)
3400
3745
  .setDependencies(dependencies)
3401
- .setMainPage(await this.buildMainPage(entity, rootContext, dependencies))
3402
- .setRelatedPages(await this.buildRelatedPages(entity, rootContext, dependencies))
3746
+ .setPages(orderedPages)
3403
3747
  .build();
3404
3748
  }
3405
3749
  async loadRootContext(entity, id) {
@@ -3407,31 +3751,107 @@ class AXPLayoutAdapterFactory {
3407
3751
  return await fn(id);
3408
3752
  }
3409
3753
  async buildMainPage(entity, rootContext, dependencies) {
3410
- return this.mainEntityContentBuilder.build(entity, rootContext, dependencies);
3754
+ return this.mainEntityContentBuilder.build(entity, rootContext, dependencies, await this.getRootTitle(entity, rootContext, dependencies));
3411
3755
  }
3412
3756
  async buildRelatedPages(entity, rootContext, dependencies) {
3413
3757
  const pages = [];
3758
+ const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
3414
3759
  // Page Details
3415
- const pageDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layoutType === 'page-detail');
3760
+ const pageDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'page-detail');
3416
3761
  for (const relatedEntity of pageDetailEntities || []) {
3417
3762
  const converter = this.relatedEntityConverterFactory.createPageDetailsConverter();
3418
- pages.push(await converter.convert(relatedEntity, { entityResolver: dependencies.entityResolver }));
3763
+ pages.push(await converter.convert(relatedEntity, {
3764
+ ...dependencies,
3765
+ context: rootContext,
3766
+ rootTitle: rootTitle,
3767
+ }));
3419
3768
  }
3420
3769
  // Page Lists
3421
- const pageListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layoutType === 'page-list');
3770
+ const pageListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'page-list');
3422
3771
  for (const relatedEntity of pageListEntities || []) {
3423
3772
  const converter = this.relatedEntityConverterFactory.createPageListConverter();
3424
3773
  pages.push(await converter.convert(relatedEntity, {
3425
3774
  ...dependencies,
3426
3775
  context: rootContext,
3427
- rootTitle: await this.getRootTitle(entity, rootContext, dependencies),
3776
+ rootTitle: rootTitle,
3428
3777
  }));
3429
3778
  }
3779
+ // Include nested page-related entities from merge-detail related entities
3780
+ const mergeDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'merge-detail');
3781
+ for (const mergeRelated of mergeDetailEntities || []) {
3782
+ try {
3783
+ const [moduleName, childEntityName] = (mergeRelated.entity || '').split('.');
3784
+ const childEntity = await dependencies.entityResolver.resolve(moduleName, childEntityName);
3785
+ if (!childEntity) {
3786
+ continue;
3787
+ }
3788
+ const nestedDataPath = mergeRelated?.persistence?.dataPath || childEntityName;
3789
+ const nestedContext = get(rootContext, nestedDataPath) ?? rootContext;
3790
+ const nestedPageDetailEntities = childEntity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'page-detail');
3791
+ for (const nestedRelated of nestedPageDetailEntities || []) {
3792
+ const converter = this.relatedEntityConverterFactory.createPageDetailsConverter();
3793
+ pages.push(await converter.convert(nestedRelated, {
3794
+ ...dependencies,
3795
+ context: nestedContext,
3796
+ rootTitle: rootTitle,
3797
+ }));
3798
+ }
3799
+ const nestedPageListEntities = childEntity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'page-list');
3800
+ for (const nestedRelated of nestedPageListEntities || []) {
3801
+ const converter = this.relatedEntityConverterFactory.createPageListConverter();
3802
+ pages.push(await converter.convert(nestedRelated, {
3803
+ ...dependencies,
3804
+ context: nestedContext,
3805
+ rootTitle: rootTitle,
3806
+ }));
3807
+ }
3808
+ }
3809
+ catch {
3810
+ // Silently ignore failures for nested page inclusions
3811
+ }
3812
+ }
3430
3813
  return pages;
3431
3814
  }
3815
+ composePagesWithPositions(mainPage, relatedPages, entity) {
3816
+ // Only consider related entities with layout types page-detail or page-list
3817
+ const pageEntities = (entity?.relatedEntities || []).filter((re) => !re.hidden && (re.layout?.type === 'page-detail' || re.layout?.type === 'page-list'));
3818
+ // Build a map from entity name to its layout config (order/position)
3819
+ const layoutConfigByEntity = {};
3820
+ for (const re of pageEntities) {
3821
+ const [, entityName] = (re.entity || '').split('.');
3822
+ const key = entityName || re.entity;
3823
+ layoutConfigByEntity[key] = { order: re.layout?.order, position: re.layout?.position };
3824
+ }
3825
+ // Split related pages into before/after buckets based on their relatedEntity layout
3826
+ const before = [];
3827
+ const after = [];
3828
+ for (const page of relatedPages) {
3829
+ const conf = layoutConfigByEntity[page.id];
3830
+ const position = conf?.position ?? 'after';
3831
+ if (position === 'before') {
3832
+ before.push(page);
3833
+ }
3834
+ else {
3835
+ after.push(page);
3836
+ }
3837
+ }
3838
+ // Sort by order within each bucket (undefined orders go last)
3839
+ const sortByOrder = (a, b) => {
3840
+ const ao = layoutConfigByEntity[a.id]?.order ?? Number.POSITIVE_INFINITY;
3841
+ const bo = layoutConfigByEntity[b.id]?.order ?? Number.POSITIVE_INFINITY;
3842
+ return ao - bo;
3843
+ };
3844
+ before.sort(sortByOrder);
3845
+ after.sort(sortByOrder);
3846
+ // Ensure the main page is primary
3847
+ mainPage.isPrimary = true;
3848
+ // Compose final pages: before -> main -> after
3849
+ return [...before, mainPage, ...after];
3850
+ }
3432
3851
  async getRootTitle(entity, rootContext, dependencies) {
3433
3852
  // Logic for getting root title
3434
- return await dependencies.expressionEvaluator.evaluate(entity.interfaces?.master?.single?.title, rootContext);
3853
+ const title = await dependencies.expressionEvaluator.evaluate(entity.interfaces?.master?.single?.title, rootContext);
3854
+ return title;
3435
3855
  }
3436
3856
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: AXPLayoutAdapterFactory, deps: [{ token: AXPRelatedEntityConverterFactory }, { token: AXPMainEntityContentBuilder }, { token: AXPLayoutAdapterBuilder }], target: i0.ɵɵFactoryTarget.Injectable }); }
3437
3857
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: AXPLayoutAdapterFactory, providedIn: 'root' }); }
@@ -3499,6 +3919,81 @@ class AXPEntitySearchDefinitionProvider {
3499
3919
  }
3500
3920
  }
3501
3921
 
3922
+ //#region ---- Column Mapping Helpers ----
3923
+ /**
3924
+ * Maps an entity property to a list widget column configuration.
3925
+ * Preserves readonly behavior and leverages the property's interface options when available.
3926
+ */
3927
+ function mapPropertyToWidgetColumn(property) {
3928
+ return {
3929
+ name: property.name,
3930
+ title: property.title,
3931
+ visible: true,
3932
+ widget: property.schema?.interface
3933
+ ? {
3934
+ type: property.schema.interface.type || 'text-editor',
3935
+ path: property.name,
3936
+ options: {
3937
+ readonly: true,
3938
+ ...property.schema.interface.options,
3939
+ },
3940
+ }
3941
+ : {
3942
+ type: 'text-editor',
3943
+ path: property.name,
3944
+ options: { readonly: true },
3945
+ },
3946
+ };
3947
+ }
3948
+ /**
3949
+ * Maps an entity column metadata (and its related property) to a list widget column configuration.
3950
+ */
3951
+ function mapEntityColumnToWidgetColumn(entity, column) {
3952
+ const property = entity.properties.find((p) => p.name === column.name);
3953
+ return {
3954
+ name: column.name,
3955
+ title: column.title || property?.title || column.name,
3956
+ width: column.options?.width,
3957
+ visible: column.options?.visible !== false,
3958
+ widget: property?.schema?.interface
3959
+ ? {
3960
+ type: property.schema.interface.type || 'text-editor',
3961
+ path: column.options?.dataPath || column.name,
3962
+ options: {
3963
+ readonly: true,
3964
+ ...property.schema.interface.options,
3965
+ },
3966
+ }
3967
+ : {
3968
+ type: 'text-editor',
3969
+ path: column.options?.dataPath || column.name,
3970
+ options: { readonly: true },
3971
+ },
3972
+ };
3973
+ }
3974
+ //#endregion
3975
+ //#region ---- Include/Exclude Utilities ----
3976
+ /**
3977
+ * Applies include and exclude filters to a named collection while preserving order.
3978
+ */
3979
+ function applyIncludeExclude(items, include = [], exclude = []) {
3980
+ let result = items;
3981
+ if (include.length > 0) {
3982
+ result = result.filter((i) => include.includes(i.name));
3983
+ }
3984
+ if (exclude.length > 0) {
3985
+ result = result.filter((i) => !exclude.includes(i.name));
3986
+ }
3987
+ return result;
3988
+ }
3989
+ /**
3990
+ * Returns only visible (non-hidden) properties of an entity.
3991
+ */
3992
+ function getVisibleProperties(entity) {
3993
+ return entity.properties.filter((prop) => !prop.schema?.hidden);
3994
+ }
3995
+ //#endregion
3996
+
3502
3997
  class AXPEntityListTableService {
3503
3998
  constructor() {
3504
3999
  //#region ---- Services & Dependencies ----
@@ -3506,6 +4001,9 @@ class AXPEntityListTableService {
3506
4001
  this.workflow = inject(AXPWorkflowService);
3507
4002
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
3508
4003
  this.evaluateExpressions = async (options, data) => {
4004
+ if (!options) {
4005
+ return {};
4006
+ }
3509
4007
  const scope = {
3510
4008
  context: {
3511
4009
  eval: (path) => {
@@ -3521,8 +4019,7 @@ class AXPEntityListTableService {
3521
4019
  /**
3522
4020
  * Convert Entity to List Widget Options
3523
4021
  */
3524
- async convertEntityToListOptions(entity, options) {
3525
- const allActions = entity.interfaces?.master?.list?.actions?.map((tr) => new AXPEntityCommandTriggerViewModel(entity, tr)) ?? [];
4022
+ async convertEntityToListOptions(entity, options, allActions) {
3526
4023
  const listOptions = {
3527
4024
  // 📊 Data Source
3528
4025
  dataSource: this.createDataSource(entity),
@@ -3574,72 +4071,17 @@ class AXPEntityListTableService {
3574
4071
  createColumnsFromProperties(entity, options) {
3575
4072
  const excludeColumns = options?.excludeColumns || [];
3576
4073
  const includeColumns = options?.includeColumns || [];
3577
- let columns = [];
3578
- // If columns are defined, use them
3579
- if (entity.columns && entity.columns.length > 0) {
3580
- columns = entity.columns.map((col) => this.mapEntityColumnToWidgetColumn(entity, col));
3581
- }
3582
- else {
3583
- // Otherwise use properties
3584
- columns = entity.properties
3585
- .filter((prop) => !prop.schema.hidden) // Only visible properties
3586
- .map((prop) => ({
3587
- name: prop.name,
3588
- title: prop.title,
3589
- visible: true,
3590
- widget: prop.schema.interface
3591
- ? {
3592
- type: prop.schema.interface.type || 'text-editor',
3593
- path: prop.name,
3594
- options: {
3595
- readonly: true,
3596
- ...prop.schema.interface.options,
3597
- },
3598
- }
3599
- : {
3600
- type: 'text-editor',
3601
- path: prop.name,
3602
- options: { readonly: true },
3603
- },
3604
- }));
3605
- }
3606
- // Apply include/exclude filters
3607
- if (includeColumns.length > 0) {
3608
- // If includeColumns is specified, only include those columns
3609
- columns = columns.filter((col) => includeColumns.includes(col.name));
3610
- }
3611
- if (excludeColumns.length > 0) {
3612
- // If excludeColumns is specified, exclude those columns
3613
- columns = columns.filter((col) => !excludeColumns.includes(col.name));
3614
- }
3615
- return columns;
4074
+ // If columns are defined, use them; otherwise use visible properties
4075
+ const baseColumns = entity.columns && entity.columns.length > 0
4076
+ ? entity.columns.map((col) => this.mapEntityColumnToWidgetColumn(entity, col))
4077
+ : getVisibleProperties(entity).map((prop) => mapPropertyToWidgetColumn(prop));
4078
+ return applyIncludeExclude(baseColumns, includeColumns, excludeColumns);
3616
4079
  }
3617
4080
  /**
3618
4081
  * Map EntityTableColumn to ListWidgetColumn
3619
4082
  */
3620
4083
  mapEntityColumnToWidgetColumn(entity, column) {
3621
- // Find corresponding property
3622
- const property = entity.properties.find((p) => p.name === column.name);
3623
- return {
3624
- name: column.name,
3625
- title: column.title || property?.title || column.name,
3626
- width: column.options?.width,
3627
- visible: column.options?.visible !== false,
3628
- widget: property?.schema.interface
3629
- ? {
3630
- type: property.schema.interface.type || 'text-editor',
3631
- path: column.options?.dataPath || column.name,
3632
- options: {
3633
- readonly: true,
3634
- ...property.schema.interface.options,
3635
- },
3636
- }
3637
- : {
3638
- type: 'text-editor',
3639
- path: column.options?.dataPath || column.name,
3640
- options: { readonly: true },
3641
- },
3642
- };
4084
+ return mapEntityColumnToWidgetColumn(entity, column);
3643
4085
  }
3644
4086
  /**
3645
4087
  * Convert Entity Actions to Row Commands
@@ -3655,7 +4097,7 @@ class AXPEntityListTableService {
3655
4097
  icon: action.icon,
3656
4098
  color: action.color,
3657
4099
  look: 'outline',
3658
- visible: action.hidden,
4100
+ visible: !action.hidden,
3659
4101
  disabled: action.disabled,
3660
4102
  }));
3661
4103
  }
@@ -3666,6 +4108,36 @@ class AXPEntityListTableService {
3666
4108
  const actions = entity.interfaces?.master?.list?.actions || [];
3667
4109
  return actions.some((action) => action.scope === AXPEntityCommandScope.Selected);
3668
4110
  }
4111
+ /**
4112
+ * Handle execution of a row command (shared by double-click and command handlers)
4113
+ */
4114
+ async handleRowCommand(e, selectedRows, entity, allActions) {
4115
+ const data = e.data;
4116
+ const commandName = e.name;
4117
+ const action = allActions.find((c) => {
4118
+ return (c.name == e.name &&
4119
+ ((selectedRows?.length
4120
+ ? c.scope == AXPEntityCommandScope.Selected
4121
+ : c.scope == AXPEntityCommandScope.Individual) ||
4122
+ c.scope == AXPEntityCommandScope.TypeLevel));
4123
+ });
4124
+ const command = commandName.split('&')[0];
4125
+ const options = await this.evaluateExpressions(action?.options, data);
4126
+ await this.workflow.execute(command, {
4127
+ entity: getEntityInfo(entity).source,
4128
+ entityInfo: {
4129
+ name: entity.name,
4130
+ module: entity.module,
4131
+ title: entity.title,
4132
+ parentKey: entity.parentKey,
4133
+ source: entity.source,
4134
+ },
4135
+ data: action?.scope == AXPEntityCommandScope.Selected ? selectedRows : data,
4136
+ options: options,
4137
+ metadata: action?.metadata,
4138
+ });
4139
+ console.log('Entity List - Row command:', e.name, e.data);
4140
+ }
3669
4141
  /**
3670
4142
  * Create default events
3671
4143
  */
@@ -3674,38 +4146,27 @@ class AXPEntityListTableService {
3674
4146
  onRowClick: (row) => {
3675
4147
  console.log('Entity List - Row clicked:', row);
3676
4148
  },
3677
- onRowDoubleClick: (row) => {
3678
- console.log('Entity List - Row double clicked:', row);
4149
+ onRowDoubleClick: (e) => {
4150
+ console.log('Entity List - Row double clicked:', e);
4151
+ const defaultAction = allActions.find((c) => {
4152
+ const commandName = c.name.split('&')[0];
4153
+ return (c.default || commandName === 'open-entity') && !c.hidden;
4154
+ });
4155
+ if (!defaultAction) {
4156
+ return;
4157
+ }
4158
+ const d = {
4159
+ component: e.component,
4160
+ name: defaultAction.name,
4161
+ data: e.data,
4162
+ };
4163
+ this.handleRowCommand(d, undefined, entity, allActions);
3679
4164
  },
3680
4165
  onSelectionChange: (selectedRows) => {
3681
4166
  console.log('Entity List - Selection changed:', selectedRows);
3682
4167
  },
3683
4168
  onRowCommand: async (e, selectedRows) => {
3684
- const data = e.data;
3685
- const commandName = e.name;
3686
- const action = allActions.find((c) => {
3687
- return (c.name == e.name &&
3688
- ((selectedRows?.length
3689
- ? c.scope == AXPEntityCommandScope.Selected
3690
- : c.scope == AXPEntityCommandScope.Individual) ||
3691
- c.scope == AXPEntityCommandScope.TypeLevel));
3692
- });
3693
- const command = commandName.split('&')[0];
3694
- const options = await this.evaluateExpressions(action?.options, data);
3695
- await this.workflow.execute(command, {
3696
- entity: getEntityInfo(entity).source,
3697
- entityInfo: {
3698
- name: entity.name,
3699
- module: entity.module,
3700
- title: entity.title,
3701
- parentKey: entity.parentKey,
3702
- source: entity.source,
3703
- },
3704
- data: action?.scope == AXPEntityCommandScope.Selected ? selectedRows : data,
3705
- options: options,
3706
- metadata: action?.metadata,
3707
- });
3708
- console.log('Entity List - Row command:', e.name, e.data);
4169
+ await this.handleRowCommand(e, selectedRows, entity, allActions);
3709
4170
  },
3710
4171
  };
3711
4172
  }
@@ -3767,29 +4228,29 @@ class AXPEntityListToolbarService {
3767
4228
  * Create Column Definitions for Toolbar
3768
4229
  */
3769
4230
  createColumnDefinitions(entity, options) {
3770
- const { columns = [], properties } = entity;
4231
+ const { columns = [] } = entity;
3771
4232
  const excludeColumns = options?.excludeColumns || [];
3772
4233
  const includeColumns = options?.includeColumns || [];
3773
- const visibleProperties = properties.filter(({ schema }) => !schema?.hidden);
4234
+ const visibleProperties = getVisibleProperties(entity);
3774
4235
  const visiblePropNames = new Set(visibleProperties.map(({ name }) => name));
3775
- let filteredColumns = columns.filter(({ name }) => visiblePropNames.has(name));
3776
- // Apply include/exclude filters
3777
- if (includeColumns.length > 0) {
3778
- // If includeColumns is specified, only include those columns
3779
- filteredColumns = filteredColumns.filter((col) => includeColumns.includes(col.name));
3780
- }
3781
- if (excludeColumns.length > 0) {
3782
- // If excludeColumns is specified, exclude those columns
3783
- filteredColumns = filteredColumns.filter((col) => !excludeColumns.includes(col.name));
3784
- }
3785
- return filteredColumns.map((column) => {
3786
- const property = visibleProperties.find(({ name }) => name === column.name);
3787
- return {
3788
- name: column.name,
3789
- title: property?.title,
3790
- visible: column?.options?.visible ?? true,
3791
- };
3792
- });
4236
+ // Prefer explicit entity.columns if present; fallback to visible properties
4237
+ const baseColumns = columns.length > 0
4238
+ ? columns
4239
+ .filter(({ name }) => visiblePropNames.has(name))
4240
+ .map((column) => {
4241
+ const property = visibleProperties.find(({ name }) => name === column.name);
4242
+ return {
4243
+ name: column.name,
4244
+ title: property?.title,
4245
+ visible: column?.options?.visible ?? true,
4246
+ };
4247
+ })
4248
+ : visibleProperties.map((property) => ({
4249
+ name: property.name,
4250
+ title: property.title,
4251
+ visible: true,
4252
+ }));
4253
+ return applyIncludeExclude(baseColumns, includeColumns, excludeColumns);
3793
4254
  }
3794
4255
  /**
3795
4256
  * Create Sort Definitions for Toolbar
@@ -3818,7 +4279,6 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3818
4279
  this.entityListTableService = inject(AXPEntityListTableService);
3819
4280
  this.entityListToolbarService = inject(AXPEntityListToolbarService);
3820
4281
  this.layoutThemeService = inject(AXPLayoutThemeService);
3821
- this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
3822
4282
  this.isMounted = signal(false, ...(ngDevMode ? [{ debugName: "isMounted" }] : []));
3823
4283
  this.entity = signal(null, ...(ngDevMode ? [{ debugName: "entity" }] : []));
3824
4284
  this.listNode = signal(null, ...(ngDevMode ? [{ debugName: "listNode" }] : []));
@@ -3826,7 +4286,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3826
4286
  this.allWidgets = viewChildren(AXPWidgetRendererDirective, ...(ngDevMode ? [{ debugName: "allWidgets" }] : []));
3827
4287
  this.listWidget = linkedSignal(() => this.allWidgets().find((widget) => widget.node()?.type === AXPWidgetsCatalog.list));
3828
4288
  this.toolbarWidget = computed(() => this.allWidgets().find((widget) => widget.node()?.type === AXPWidgetsCatalog.listToolbar), ...(ngDevMode ? [{ debugName: "toolbarWidget" }] : []));
3829
- this.selectedItems = computed(() => this.getValue()?.table || [], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
4289
+ this.selectedItems = signal([], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
3830
4290
  this.toolbarNode = signal(null, ...(ngDevMode ? [{ debugName: "toolbarNode" }] : []));
3831
4291
  this.destroyed = new Subject();
3832
4292
  //options
@@ -3839,7 +4299,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3839
4299
  //actions
3840
4300
  this.allActions = computed(() => {
3841
4301
  const originalList = this.entity()?.interfaces?.master?.list?.actions ?? [];
3842
- const externalActionList = (this.options()['actions'] ?? []);
4302
+ const externalActionList = this.externalActions() ?? [];
3843
4303
  const usedOverrideActions = new Set();
3844
4304
  const mergedActions = originalList.map((originalAction) => {
3845
4305
  const originalCommandName = typeof originalAction.command === 'string' ? originalAction.command : originalAction.command?.name;
@@ -3948,7 +4408,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3948
4408
  parentKey: this.entity()?.parentKey,
3949
4409
  source: this.entity()?.source,
3950
4410
  },
3951
- data: action?.scope == AXPEntityCommandScope.Selected ? this.selectedItems() : data,
4411
+ data: action?.scope == AXPEntityCommandScope.Selected
4412
+ ? this.selectedItems()
4413
+ : action?.options?.['process']?.data || null,
3952
4414
  options: action?.options,
3953
4415
  metadata: action?.metadata,
3954
4416
  });
@@ -4014,10 +4476,30 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4014
4476
  * TODO: Implement column change logic
4015
4477
  */
4016
4478
  handleColumnChanges(changeTracker) {
4017
- if (changeTracker.isColumnsChanged) {
4018
- // TODO: Implement column change handling
4019
- console.log('Column changes detected - implementation needed');
4479
+ if (!changeTracker.isColumnsChanged) {
4480
+ return;
4020
4481
  }
4482
+ const listInstance = this.listWidget()?.instance;
4483
+ const toolbarState = this.getValue()?.toolbar;
4484
+ if (!listInstance || !toolbarState?.columns) {
4485
+ return;
4486
+ }
4487
+ // Current columns from list options
4488
+ const currentColumns = (listInstance.options()['columns'] || []);
4489
+ const columnByName = new Map(currentColumns.map((c) => [c.name, c]));
4490
+ // Build new ordered columns array based on toolbar order/visibility
4491
+ const updatedColumns = toolbarState.columns
4492
+ .map((q) => {
4493
+ const base = columnByName.get(q.name);
4494
+ if (!base) {
4495
+ return null;
4496
+ }
4497
+ // Preserve all existing column config but update visibility
4498
+ return { ...base, visible: q.visible };
4499
+ })
4500
+ .filter((c) => c != null);
4501
+ // Apply updated columns to the list widget and refresh
4502
+ listInstance.setOptions({ columns: updatedColumns });
4021
4503
  }
4022
4504
  async ngOnInit() {
4023
4505
  super.ngOnInit();
@@ -4035,7 +4517,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4035
4517
  excludeColumns: this.excludeColumns(),
4036
4518
  includeColumns: this.includeColumns(),
4037
4519
  };
4038
- const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options);
4520
+ const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options, this.allActions());
4039
4521
  const toolbarOptions = await this.entityListToolbarService.convertEntityToolbarOptions(resolvedEntity, options);
4040
4522
  this.listNode.set({
4041
4523
  type: AXPWidgetsCatalog.list,
@@ -4057,7 +4539,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4057
4539
  },
4058
4540
  });
4059
4541
  }
4060
- ngAfterViewInit() {
4542
+ async ngAfterViewInit() {
4061
4543
  this.workflow.events$
4062
4544
  .pipe(ofType(AXPRefreshEvent))
4063
4545
  .pipe(takeUntil(this.destroyed))
@@ -4066,6 +4548,15 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4066
4548
  this.listWidget()?.instance.call('refresh');
4067
4549
  }
4068
4550
  });
4551
+ const listWidget = (await this.layoutService.waitForWidget(`${this.entitySource()}-tab-list_table`, 500));
4552
+ if (listWidget?.api && typeof listWidget.api === 'function') {
4553
+ const onSelectionChange = listWidget.api()['onSelectionChange'];
4554
+ if (onSelectionChange) {
4555
+ onSelectionChange.pipe(takeUntil(this.destroyed)).subscribe((e) => {
4556
+ this.selectedItems.set(e);
4557
+ });
4558
+ }
4559
+ }
4069
4560
  }
4070
4561
  ngOnDestroy() {
4071
4562
  this.listWidget.set(undefined);
@@ -4073,7 +4564,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4073
4564
  this.destroyed.complete();
4074
4565
  }
4075
4566
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: AXPEntityListWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
4076
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: AXPEntityListWidgetViewComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class": "\"ax-h-full\"" } }, providers: [AXPEntityListTableService, AXPEntityListToolbarService], viewQueries: [{ propertyName: "container", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }, { propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "allWidgets", predicate: AXPWidgetRendererDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
4567
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: AXPEntityListWidgetViewComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class": "\"ax-h-full\"" } }, providers: [AXPEntityListTableService, AXPEntityListToolbarService], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "allWidgets", predicate: AXPWidgetRendererDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
4077
4568
  @if (showEntityActions()) {
4078
4569
  <div class="ax-flex ax-gap-2 ax-justify-end ax-mb-4">
4079
4570
  @for (action of primaryActions(); track $index) {
@@ -4804,6 +5295,7 @@ class AXPLookupWidgetSelectorViewModel {
4804
5295
  this.options = options;
4805
5296
  this.workflow = this.injector.get(AXPWorkflowService);
4806
5297
  this.filterOperatorMiddleware = this.injector.get(AXPFilterOperatorMiddlewareService);
5298
+ this.widgetResolver = this.injector.get(AXPWidgetRegistryService);
4807
5299
  this.dataSource = new AXDataSource({
4808
5300
  byKey: (key) => {
4809
5301
  const func = this.entityDef.queries.byKey.execute;
@@ -4833,15 +5325,33 @@ class AXPLookupWidgetSelectorViewModel {
4833
5325
  return this.inlineFiltersPlaceholders().length > 0;
4834
5326
  }, ...(ngDevMode ? [{ debugName: "hasInlineFilters" }] : []));
4835
5327
  this.columns = () => {
4836
- const listColumns = this.entityDef.columns ?? [];
4837
- const columns = listColumns?.map((c) => c.name) ?? [];
4838
- const props = this.entityDef.properties.filter((p) => p.schema.hidden != true);
4839
- const displayColumns = props.filter((p) => (this.options.columns?.length ?? 0) > 0
4840
- ? this.options.columns.some((c) => c == p.name)
4841
- : columns.some((c) => c == p.name));
4842
- //
4843
- return displayColumns.map((p) => {
4844
- return new AXPEntityListViewColumnViewModel(p, listColumns?.find((c) => c.name == p.name));
5328
+ const { columns = [], properties } = this.entityDef;
5329
+ const visibleProperties = properties.filter(({ schema }) => !schema?.hidden);
5330
+ const visiblePropNames = new Set(visibleProperties.map(({ name }) => name));
5331
+ return columns
5332
+ .filter(({ name, showAs }) => visiblePropNames.has(name) || showAs)
5333
+ .filter(({ name }) => ((this.options.columns?.length ?? 0) == 0) || this.options.columns?.includes(name))
5334
+ .map((column) => {
5335
+ if (column.showAs) {
5336
+ const widgetConfig = this.widgetResolver.resolve(column.showAs.type);
5337
+ const property = {
5338
+ ...widgetConfig,
5339
+ name: column.name,
5340
+ title: column.title ?? '',
5341
+ schema: {
5342
+ dataType: 'string',
5343
+ interface: {
5344
+ type: column.showAs.type,
5345
+ options: column.showAs.options,
5346
+ },
5347
+ },
5348
+ };
5349
+ return new AXPEntityListViewColumnViewModel(property, column);
5350
+ }
5351
+ else {
5352
+ const property = visibleProperties.find(({ name }) => name === column.name);
5353
+ return new AXPEntityListViewColumnViewModel(property, column);
5354
+ }
4845
5355
  });
4846
5356
  };
4847
5357
  this.inlineFilters = {
@@ -4950,13 +5460,24 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
4950
5460
  this.entity = computed(() => this.options()['entity'], ...(ngDevMode ? [{ debugName: "entity" }] : []));
4951
5461
  this.disabled = computed(() => this.options()['disabled'], ...(ngDevMode ? [{ debugName: "disabled" }] : []));
4952
5462
  this.columns = computed(() => this.options()['columns'] ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : []));
5463
+ this.textField = computed(() => this.options()['textField'] ?? '', ...(ngDevMode ? [{ debugName: "textField" }] : []));
4953
5464
  this.customFilter = computed(() => this.options()['filter'], ...(ngDevMode ? [{ debugName: "customFilter" }] : []));
4954
5465
  this.multiple = computed(() => (this.options()['multiple'] ?? false), ...(ngDevMode ? [{ debugName: "multiple" }] : []));
4955
5466
  this.look = computed(() => this.options()['look'] ?? 'lookup', ...(ngDevMode ? [{ debugName: "look" }] : []));
4956
5467
  this.allowClear = computed(() => (this.options()['allowClear'] ?? false), ...(ngDevMode ? [{ debugName: "allowClear" }] : []));
4957
- this.textField = computed(() => {
4958
- return (this.entityDef()?.formats.lookup ?? this.entityDef()?.properties.find((c) => c.name != 'id')?.name ?? 'title');
4959
- }, ...(ngDevMode ? [{ debugName: "textField" }] : []));
5468
+ this.defaultTextField = computed(() => {
5469
+ const textField = this.entityDef()?.formats.lookup ?? this.entityDef()?.properties.find((c) => c.name != 'id')?.name ?? 'title';
5470
+ return textField;
5471
+ }, ...(ngDevMode ? [{ debugName: "defaultTextField" }] : []));
5472
+ this.displayField = computed(() => {
5473
+ if (this.textField()) {
5474
+ return this.textField();
5475
+ }
5476
+ return this.defaultTextField();
5477
+ }, ...(ngDevMode ? [{ debugName: "displayField" }] : []));
5478
+ this.selectedItemsText = computed(() => {
5479
+ return this.selectedItems().map((item) => get(item, this.displayField())).join(', ');
5480
+ }, ...(ngDevMode ? [{ debugName: "selectedItemsText" }] : []));
4960
5481
  this.valueField = computed(() => this.entityDef()?.properties.find((c) => c.name == 'id')?.name ?? 'id', ...(ngDevMode ? [{ debugName: "valueField" }] : []));
4961
5482
  this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : []));
4962
5483
  this.searchTerm = signal(null, ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
@@ -5096,6 +5617,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5096
5617
  this.selectedItems.set(items);
5097
5618
  //
5098
5619
  const keys = items.map((item) => get(item, this.valueField()));
5620
+ const text = items.map((item) => get(item, this.displayField()));
5099
5621
  //
5100
5622
  // extract data from valueField and set context by expose path
5101
5623
  if (this.expose()) {
@@ -5147,11 +5669,12 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5147
5669
  }
5148
5670
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: AXPLookupWidgetEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
5149
5671
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: AXPLookupWidgetEditComponent, isStandalone: true, selector: "axp-lookup-widget-edit", viewQueries: [{ propertyName: "textbox", first: true, predicate: AXTagBoxComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
5672
+ @if(vm()) {
5150
5673
  @if (look() == 'select') {
5151
5674
  <ax-select-box
5152
5675
  [dataSource]="vm()?.dataSource!"
5153
5676
  [ngModel]="selectedItems()"
5154
- [textField]="textField()"
5677
+ [textField]="displayField()"
5155
5678
  [valueField]="valueField()"
5156
5679
  [disabled]="disabled()"
5157
5680
  [multiple]="multiple()"
@@ -5167,7 +5690,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5167
5690
  } @else {
5168
5691
  <ax-tag-box
5169
5692
  [ngModel]="selectedItems()"
5170
- [textField]="textField()"
5693
+ [textField]="displayField()"
5171
5694
  [valueField]="valueField()"
5172
5695
  (onValueChanged)="handleValueChange($event)"
5173
5696
  [placeholder]="placeholder() | translate | async"
@@ -5204,6 +5727,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5204
5727
  </ax-suffix>
5205
5728
  </ax-tag-box>
5206
5729
  }
5730
+ }
5207
5731
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type:
5208
5732
  //
5209
5733
  AXButtonModule }, { kind: "component", type: i3$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "component", type: i3.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$1.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "ngmodule", type: AXTagBoxModule }, { kind: "component", type: i6.AXTagBoxComponent, selector: "ax-tag-box", inputs: ["disabled", "tabIndex", "readonly", "value", "state", "name", "id", "placeholder", "allowNull", "type", "look", "addOnComma", "addOnEnter", "valueField", "textField", "readonlyField", "allowDuplicateValues"], outputs: ["onBlur", "onFocus", "valueChange", "stateChange", "onValueChanged", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "component", type: i7$1.AXSelectBoxComponent, selector: "ax-select-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "minValue", "maxValue", "value", "state", "name", "id", "type", "look", "multiple", "valueField", "textField", "disabledField", "textTemplate", "selectedItems", "isItemTruncated", "showItemTooltip", "dataSource", "minRecordsForSearch", "caption", "itemTemplate", "selectedTemplate", "emptyTemplate", "loadingTemplate", "dropdownWidth", "searchBoxAutoFocus"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed", "onItemSelected", "onItemClick"] }, { kind: "component", type: AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i8.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -5213,11 +5737,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
5213
5737
  args: [{
5214
5738
  selector: 'axp-lookup-widget-edit',
5215
5739
  template: `
5740
+ @if(vm()) {
5216
5741
  @if (look() == 'select') {
5217
5742
  <ax-select-box
5218
5743
  [dataSource]="vm()?.dataSource!"
5219
5744
  [ngModel]="selectedItems()"
5220
- [textField]="textField()"
5745
+ [textField]="displayField()"
5221
5746
  [valueField]="valueField()"
5222
5747
  [disabled]="disabled()"
5223
5748
  [multiple]="multiple()"
@@ -5233,7 +5758,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
5233
5758
  } @else {
5234
5759
  <ax-tag-box
5235
5760
  [ngModel]="selectedItems()"
5236
- [textField]="textField()"
5761
+ [textField]="displayField()"
5237
5762
  [valueField]="valueField()"
5238
5763
  (onValueChanged)="handleValueChange($event)"
5239
5764
  [placeholder]="placeholder() | translate | async"
@@ -5270,6 +5795,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
5270
5795
  </ax-suffix>
5271
5796
  </ax-tag-box>
5272
5797
  }
5798
+ }
5273
5799
  `,
5274
5800
  changeDetection: ChangeDetectionStrategy.OnPush,
5275
5801
  imports: [
@@ -6155,7 +6681,7 @@ class AXPShowDetailViewAction extends AXPWorkflowAction {
6155
6681
  const [module, entity] = context.getVariable('entity').split('.');
6156
6682
  const { id } = context.getVariable('data');
6157
6683
  const newPayload = {
6158
- commands: `/${this.sessionService.application?.name}/m/${module}/e/${entity}/${id}/view`,
6684
+ commands: `/${this.sessionService.application?.name}/m/${module}/e/${entity}/${id}/new-view`,
6159
6685
  };
6160
6686
  context.setVariable('payload', newPayload);
6161
6687
  this.navigation.execute(context);
@@ -6616,7 +7142,7 @@ function entityDetailsSimpleCondition(fk) {
6616
7142
  value: '{{context.eval("id")}}',
6617
7143
  };
6618
7144
  }
6619
- function entityDetailsReferenceCondition() {
7145
+ function entityDetailsReferenceCondition(type) {
6620
7146
  return [
6621
7147
  {
6622
7148
  name: 'reference.id',
@@ -6626,7 +7152,7 @@ function entityDetailsReferenceCondition() {
6626
7152
  {
6627
7153
  name: 'reference.type',
6628
7154
  operator: { type: 'equal' },
6629
- value: '{{context.eval("entityName")}}',
7155
+ value: type,
6630
7156
  },
6631
7157
  ];
6632
7158
  }
@@ -6636,6 +7162,7 @@ function entityDetailsEditAction() {
6636
7162
  command: 'quick-modify-entity',
6637
7163
  priority: 'secondary',
6638
7164
  type: 'update',
7165
+ default: true,
6639
7166
  scope: AXPEntityCommandScope.Individual,
6640
7167
  };
6641
7168
  }
@@ -6652,7 +7179,7 @@ function entityOverrideDetailsViewAction() {
6652
7179
  function entityDetailsCrudActions(parentId) {
6653
7180
  return [entityDetailsCreateActions(parentId), entityDetailsEditAction(), entityOverrideDetailsViewAction()];
6654
7181
  }
6655
- function entityDetailsReferenceCreateActions() {
7182
+ function entityDetailsReferenceCreateActions(type) {
6656
7183
  return [
6657
7184
  {
6658
7185
  title: '@general:actions.create.title',
@@ -6665,7 +7192,7 @@ function entityDetailsReferenceCreateActions() {
6665
7192
  data: {
6666
7193
  reference: {
6667
7194
  id: '{{context.eval("id")}}',
6668
- type: '{{context.eval("entityName")}}',
7195
+ type: type,
6669
7196
  },
6670
7197
  },
6671
7198
  },