@acorex/platform 20.2.4-next.1 → 20.2.4-next.3

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 (26) hide show
  1. package/common/index.d.ts +9 -1
  2. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  3. package/fesm2022/acorex-platform-layout-builder.mjs +1 -0
  4. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-layout-components.mjs +26 -36
  6. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-layout-entity.mjs +509 -230
  8. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-views.mjs +106 -24
  10. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  11. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-DXGLsVis.mjs → acorex-platform-themes-default-entity-master-list-view.component-e3Lxk5ZT.mjs} +3 -3
  12. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-e3Lxk5ZT.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-themes-default.mjs +3 -3
  14. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  15. package/fesm2022/{acorex-platform-widgets-file-list-popup.component-B601gPsW.mjs → acorex-platform-widgets-file-list-popup.component-BafU5Lfl.mjs} +4 -2
  16. package/fesm2022/acorex-platform-widgets-file-list-popup.component-BafU5Lfl.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-widgets.mjs +84 -14
  18. package/fesm2022/acorex-platform-widgets.mjs.map +1 -1
  19. package/layout/builder/index.d.ts +1 -0
  20. package/layout/components/index.d.ts +1 -5
  21. package/layout/entity/index.d.ts +2 -2
  22. package/layout/views/index.d.ts +34 -0
  23. package/package.json +5 -5
  24. package/widgets/index.d.ts +8 -0
  25. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DXGLsVis.mjs.map +0 -1
  26. package/fesm2022/acorex-platform-widgets-file-list-popup.component-B601gPsW.mjs.map +0 -1
@@ -6,11 +6,11 @@ import { AXDataSource, AXCommonModule } from '@acorex/cdk/common';
6
6
  import { AXFormatService } from '@acorex/core/format';
7
7
  import { resolveActionLook, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPSettingService, AXPRefreshEvent, AXPReloadEvent, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
8
8
  import * as i1$4 from '@acorex/platform/core';
9
- import { AXPExpressionEvaluatorService, AXPPlatformScope, AXPDistributedEventListenerService, getSmart, getChangedPaths, extractValue, setSmart } from '@acorex/platform/core';
9
+ import { AXPExpressionEvaluatorService, AXPPlatformScope, AXPDistributedEventListenerService, getChangedPaths, extractValue, setSmart } from '@acorex/platform/core';
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) => {
@@ -1157,10 +1156,28 @@ class AXPEntityMasterListViewModel {
1157
1156
  const visibleProperties = properties.filter(({ schema }) => !schema?.hidden);
1158
1157
  const visiblePropNames = new Set(visibleProperties.map(({ name }) => name));
1159
1158
  return columns
1160
- .filter(({ name }) => visiblePropNames.has(name))
1159
+ .filter(({ name, showAs }) => visiblePropNames.has(name) || showAs)
1161
1160
  .map((column) => {
1162
- const property = visibleProperties.find(({ name }) => name === column.name);
1163
- return new AXPEntityListViewColumnViewModel(property, column);
1161
+ if (column.showAs) {
1162
+ const widgetConfig = this.widgetResolver.resolve(column.showAs.type);
1163
+ const property = {
1164
+ ...widgetConfig,
1165
+ name: column.name,
1166
+ title: column.title ?? '',
1167
+ schema: {
1168
+ dataType: 'string',
1169
+ interface: {
1170
+ type: column.showAs.type,
1171
+ options: column.showAs.options,
1172
+ },
1173
+ },
1174
+ };
1175
+ return new AXPEntityListViewColumnViewModel(property, column);
1176
+ }
1177
+ else {
1178
+ const property = visibleProperties.find(({ name }) => name === column.name);
1179
+ return new AXPEntityListViewColumnViewModel(property, column);
1180
+ }
1164
1181
  });
1165
1182
  };
1166
1183
  this.visibleColumnCount = () => {
@@ -1459,8 +1476,7 @@ class AXPEntityMasterListViewModel {
1459
1476
  const cols = this.view().columns;
1460
1477
  const cloned = this.allAvailableColumns().map((c) => {
1461
1478
  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);
1479
+ const col = new AXPEntityListViewColumnViewModel(c.property, column);
1464
1480
  col.visible = !cols.some((c) => c == col.name) && col.visible != false;
1465
1481
  return col;
1466
1482
  });
@@ -2556,9 +2572,14 @@ 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
+ title: `${this.entity?.interfaces?.master?.single?.title}`,
2582
+ label: this.entity?.formats.plural,
2562
2583
  actions: [],
2563
2584
  breadcrumbs: this.createBreadcrumbs(),
2564
2585
  execute: this.createExecuteFunction(),
@@ -2585,7 +2606,7 @@ class AXPLayoutAdapterBuilder {
2585
2606
  command: {
2586
2607
  name: 'navigate',
2587
2608
  options: {
2588
- path: `/${session.application?.name}/m/${moduleName}/e/${entityName}/${this.rootContext.id}/new-view`,
2609
+ path: `/${session.application?.name}/m/${moduleName}/e/${entityName}/${this.rootContext.id}/view`,
2589
2610
  },
2590
2611
  },
2591
2612
  },
@@ -2655,40 +2676,29 @@ class AXPBaseRelatedEntityConverter {
2655
2676
  groups,
2656
2677
  };
2657
2678
  }
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
- };
2679
+ async createGridLayoutStructure(singleInterface, helpers, evaluateExpressions) {
2680
+ return await this.createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions);
2681
+ }
2682
+ //#region ---- Hidden Evaluation Helpers ----
2683
+ async getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions) {
2684
+ const properties = helpers.getPropertyByGroupId(sectionId) ?? [];
2685
+ const evaluated = await Promise.all(properties.map(async (property) => {
2686
+ let hidden = property?.schema?.hidden;
2687
+ if (typeof hidden === 'string' && evaluateExpressions) {
2688
+ try {
2689
+ const result = await evaluateExpressions({ hidden });
2690
+ hidden = result.hidden;
2691
+ }
2692
+ catch {
2693
+ hidden = false;
2694
+ }
2695
+ }
2696
+ return { property, hidden: !!hidden };
2697
+ }));
2698
+ return evaluated.filter((x) => !x.hidden).map((x) => x.property);
2690
2699
  }
2691
- createPropertyGrid(sectionId, helpers) {
2700
+ async createPropertyGrid(sectionId, helpers, evaluateExpressions) {
2701
+ const visibleProperties = await this.getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions);
2692
2702
  return {
2693
2703
  type: 'grid-layout',
2694
2704
  mode: 'edit',
@@ -2701,10 +2711,7 @@ class AXPBaseRelatedEntityConverter {
2701
2711
  },
2702
2712
  },
2703
2713
  },
2704
- children: helpers
2705
- .getPropertyByGroupId(sectionId)
2706
- .filter((property) => !property.schema.hidden)
2707
- .map((p) => {
2714
+ children: visibleProperties.map((p) => {
2708
2715
  const layout = helpers.getPropertyLayout(p.name);
2709
2716
  return {
2710
2717
  type: 'grid-item-layout',
@@ -2719,6 +2726,7 @@ class AXPBaseRelatedEntityConverter {
2719
2726
  type: 'form-field',
2720
2727
  options: {
2721
2728
  label: p.title,
2729
+ showLabel: layout?.label?.visible ?? true,
2722
2730
  },
2723
2731
  children: [
2724
2732
  {
@@ -2738,23 +2746,74 @@ class AXPBaseRelatedEntityConverter {
2738
2746
  }),
2739
2747
  };
2740
2748
  }
2749
+ async createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions) {
2750
+ return {
2751
+ type: 'grid-layout',
2752
+ options: {
2753
+ grid: {
2754
+ default: {
2755
+ gridTemplateColumns: 'repeat(12, 1fr)',
2756
+ gridTemplateRows: 'repeat(1, 1fr)',
2757
+ gap: '20px',
2758
+ },
2759
+ },
2760
+ },
2761
+ children: await Promise.all(singleInterface?.sections.map(async (s) => ({
2762
+ type: 'grid-item-layout',
2763
+ name: s.id,
2764
+ options: {
2765
+ colSpan: s.layout?.positions?.lg?.colSpan ?? 12,
2766
+ colStart: s.layout?.positions?.lg?.colStart,
2767
+ colEnd: s.layout?.positions?.lg?.colEnd,
2768
+ },
2769
+ children: [
2770
+ {
2771
+ type: 'fieldset-layout',
2772
+ options: {
2773
+ title: helpers.getGroupById(s.id)?.title ?? '',
2774
+ collapsible: true,
2775
+ },
2776
+ children: [await this.createPropertyGrid(s.id, helpers, evaluateExpressions)],
2777
+ },
2778
+ ],
2779
+ }))),
2780
+ };
2781
+ }
2741
2782
  }
2742
2783
 
2743
2784
  class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2744
2785
  async convert(relatedEntity, context) {
2745
2786
  const { entityDef } = await this.getEntityDefinition(relatedEntity, context.entityResolver);
2746
2787
  const helpers = this.createEntityHelpers(entityDef);
2788
+ const evaluateExpressions = async (actionData) => {
2789
+ const scope = {
2790
+ context: {
2791
+ eval: (path) => {
2792
+ return get(context.context, path);
2793
+ },
2794
+ },
2795
+ };
2796
+ return await context.expressionEvaluator.evaluate(actionData, scope);
2797
+ };
2798
+ // Build related tabs for the related entity page (mirrors main-entity tab building)
2799
+ const tabDetailEntities = entityDef?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'tab-detail');
2800
+ const tabListEntities = entityDef?.relatedEntities?.filter((re) => !re.hidden && (!re.layout?.type || re.layout?.type === 'tab-list'));
2801
+ const factory = new AXPRelatedEntityConverterFactory();
2802
+ const tabDetailTabs = await this.buildTabDetails(factory, tabDetailEntities ?? [], context);
2803
+ const tabListTabs = await this.buildTabLists(factory, tabListEntities ?? [], context);
2747
2804
  return {
2748
2805
  id: entityDef?.name ?? '',
2749
- title: relatedEntity.title ?? entityDef?.title ?? '',
2806
+ title: `${context.rootTitle}`,
2750
2807
  label: relatedEntity.title ?? entityDef?.formats.displayName ?? '',
2751
2808
  icon: relatedEntity.icon || entityDef.icon,
2752
2809
  settings: this.createPageSettings(),
2753
- load: this.createLoadFunction(entityDef, relatedEntity),
2810
+ load: this.createLoadFunction(entityDef, relatedEntity, evaluateExpressions),
2754
2811
  execute: this.createExecuteFunction(entityDef),
2755
- content: [this.createGridLayoutStructure(helpers.singleInterface, helpers)],
2812
+ // tabs: [...tabDetailTabs, ...tabListTabs],
2813
+ content: [await this.createGridLayoutStructure(helpers.singleInterface, helpers, evaluateExpressions)],
2756
2814
  };
2757
2815
  }
2816
+ //#region ---- Utility Methods ----
2758
2817
  createPageSettings() {
2759
2818
  return {
2760
2819
  commands: {
@@ -2773,11 +2832,12 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2773
2832
  },
2774
2833
  };
2775
2834
  }
2776
- createLoadFunction(entityDef, relatedEntity) {
2835
+ createLoadFunction(entityDef, relatedEntity, evaluateExpressions) {
2777
2836
  return async (context) => {
2778
2837
  const fn = entityDef?.queries.byKey?.execute;
2779
- const conditionNames = relatedEntity.conditions?.map((c) => c.name) ?? [];
2780
- const id = getSmart(context, conditionNames[0]);
2838
+ const conditionValues = relatedEntity.conditions?.map((c) => c.value) ?? [];
2839
+ const evaluatedConditionValues = await Promise.all(conditionValues.map((c) => evaluateExpressions(c)));
2840
+ const id = evaluatedConditionValues[0];
2781
2841
  const result = await fn(id);
2782
2842
  return { success: true, result };
2783
2843
  };
@@ -2797,6 +2857,42 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
2797
2857
  }
2798
2858
  };
2799
2859
  }
2860
+ //#endregion
2861
+ //#region ---- Tab Builders ----
2862
+ /**
2863
+ * Builds tab-detail items for a related entity page using the shared converters.
2864
+ */
2865
+ async buildTabDetails(factory, tabDetailEntities, ctx) {
2866
+ if (!ctx?.entityResolver || !tabDetailEntities?.length) {
2867
+ return [];
2868
+ }
2869
+ const tabs = [];
2870
+ for (const re of tabDetailEntities) {
2871
+ const converter = factory.createTabDetailsConverter();
2872
+ tabs.push(await converter.convert(re, {
2873
+ entityResolver: ctx.entityResolver,
2874
+ }));
2875
+ }
2876
+ return tabs;
2877
+ }
2878
+ /**
2879
+ * Builds tab-list items for a related entity page using the shared converters with context-aware filters/actions.
2880
+ */
2881
+ async buildTabLists(factory, tabListEntities, ctx) {
2882
+ if (!ctx?.entityResolver || !tabListEntities?.length) {
2883
+ return [];
2884
+ }
2885
+ const tabs = [];
2886
+ for (const re of tabListEntities) {
2887
+ const converter = factory.createTabListConverter();
2888
+ tabs.push(await converter.convert(re, {
2889
+ entityResolver: ctx.entityResolver,
2890
+ expressionEvaluator: ctx.expressionEvaluator,
2891
+ context: ctx.context,
2892
+ }));
2893
+ }
2894
+ return tabs;
2895
+ }
2800
2896
  }
2801
2897
 
2802
2898
  class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
@@ -2812,6 +2908,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2812
2908
  };
2813
2909
  return await context.expressionEvaluator.evaluate(actionData, scope);
2814
2910
  };
2911
+ const evaluatedActions = await evaluateExpressions(relatedEntity?.actions);
2815
2912
  const filters = relatedEntity.conditions?.map(async (c) => {
2816
2913
  const value = await evaluateExpressions(c.value);
2817
2914
  return {
@@ -2826,17 +2923,13 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2826
2923
  title: `${context.rootTitle}`,
2827
2924
  label: relatedEntity.title,
2828
2925
  icon: relatedEntity.icon || entityDef.icon,
2829
- actions: this.mergeActions(entityDef, relatedEntity)
2926
+ actions: this.mergeActions(entityDef, evaluatedActions)
2830
2927
  ?.filter((a) => a.priority === 'primary')
2831
2928
  ?.map((a) => {
2832
2929
  return {
2833
2930
  ...a,
2834
2931
  zone: 'header',
2835
- // visible:
2836
- // a.scope === AXPEntityCommandScope.Selected
2837
- // ? "{{widget.find('table').outputs().selectedItem().length > 0}}"
2838
- // : true,
2839
- visible: '{{context.eval("table")}}',
2932
+ visible: !a.hidden,
2840
2933
  priority: 'primary',
2841
2934
  name: a.name,
2842
2935
  title: a.title,
@@ -2854,7 +2947,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2854
2947
  execute: async (command, executeContext) => {
2855
2948
  try {
2856
2949
  const commandName = command.name.split('&')[0];
2857
- const mergedActions = this.mergeActions(entityDef, relatedEntity);
2950
+ const mergedActions = this.mergeActions(entityDef, evaluatedActions);
2858
2951
  const action = mergedActions.find((a) => {
2859
2952
  return a.name === commandName || a.name.split('&')[0] === commandName;
2860
2953
  });
@@ -2869,10 +2962,6 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2869
2962
  },
2870
2963
  };
2871
2964
  }
2872
- let evaluatedOptions = command.options;
2873
- if (action.options) {
2874
- evaluatedOptions = await evaluateExpressions(action.options);
2875
- }
2876
2965
  await context.workflowService.execute(commandName, {
2877
2966
  entity: getEntityInfo(entityDef).source,
2878
2967
  entityInfo: {
@@ -2884,8 +2973,8 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2884
2973
  },
2885
2974
  data: action.scope == AXPEntityCommandScope.Selected
2886
2975
  ? executeContext
2887
- : evaluatedOptions?.['process']?.data || null,
2888
- options: evaluatedOptions,
2976
+ : action.options?.['process']?.data || null,
2977
+ options: action.options,
2889
2978
  metadata: action.metadata,
2890
2979
  });
2891
2980
  return { success: true };
@@ -2914,14 +3003,15 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
2914
3003
  options: {
2915
3004
  entity: relatedEntity.entity,
2916
3005
  showEntityActions: false,
3006
+ actions: evaluatedActions,
2917
3007
  },
2918
3008
  },
2919
3009
  ],
2920
3010
  };
2921
3011
  }
2922
- mergeActions(entityDef, relatedEntity) {
3012
+ mergeActions(entityDef, relatedEntityActions) {
2923
3013
  const originalList = entityDef?.interfaces?.master?.list?.actions ?? [];
2924
- const relatedEntityActionList = relatedEntity.actions ?? [];
3014
+ const relatedEntityActionList = relatedEntityActions ?? [];
2925
3015
  // Create a map to track which actions from relatedEntityActionList have been used
2926
3016
  const usedOverrideActions = new Set();
2927
3017
  // Start with original actions, applying overrides where they exist
@@ -2958,7 +3048,7 @@ class AXPTabDetailsConverter extends AXPBaseRelatedEntityConverter {
2958
3048
  id: entityDef?.name ?? '',
2959
3049
  title: relatedEntity.title ?? entityDef?.title ?? '',
2960
3050
  icon: relatedEntity.icon || entityDef.icon,
2961
- content: [this.createGridLayoutStructure(helpers.singleInterface, helpers)],
3051
+ content: [await this.createGridLayoutStructure(helpers.singleInterface, helpers)],
2962
3052
  };
2963
3053
  }
2964
3054
  }
@@ -2986,6 +3076,7 @@ class AXPTabListConverter extends AXPBaseRelatedEntityConverter {
2986
3076
  hidden: true,
2987
3077
  };
2988
3078
  }) ?? [];
3079
+ const actions = await evaluateExpressions(relatedEntity.actions);
2989
3080
  return {
2990
3081
  id: entityDef?.name ?? '',
2991
3082
  title: relatedEntity.title ?? entityDef?.title ?? '',
@@ -2993,7 +3084,7 @@ class AXPTabListConverter extends AXPBaseRelatedEntityConverter {
2993
3084
  content: [
2994
3085
  {
2995
3086
  type: AXPWidgetsCatalog.entityList,
2996
- name: 'tab-list',
3087
+ name: `${relatedEntity.entity}-tab-list`,
2997
3088
  defaultValue: {
2998
3089
  toolbar: {
2999
3090
  filters: await Promise.all(filters),
@@ -3002,7 +3093,8 @@ class AXPTabListConverter extends AXPBaseRelatedEntityConverter {
3002
3093
  options: {
3003
3094
  entity: relatedEntity.entity,
3004
3095
  showEntityActions: true,
3005
- showToolbar: true,
3096
+ showToolbar: false,
3097
+ actions: actions,
3006
3098
  },
3007
3099
  },
3008
3100
  ],
@@ -3064,30 +3156,46 @@ class AXPMainEntityContentBuilder {
3064
3156
  return groups.some((group) => group.id === section.id);
3065
3157
  }) ?? []);
3066
3158
  };
3067
- const getPropertyByGroupId = (groupId) => {
3068
- return entity?.properties.filter((p) => p.groupId === groupId) ?? [];
3159
+ // Create expression evaluator for actions
3160
+ const evaluateExpressions = dependencies?.expressionEvaluator
3161
+ ? this.createExpressionEvaluator(rootContext, dependencies.expressionEvaluator)
3162
+ : null;
3163
+ const getVisiblePropertyByGroupId = async (groupId) => {
3164
+ const properties = entity?.properties.filter((p) => p.groupId === groupId) ?? [];
3165
+ const evaluated = await Promise.all(properties.map(async (p) => {
3166
+ // If you later need expression-based hidden logic, evaluate it here
3167
+ let hidden = p?.schema?.hidden;
3168
+ if (typeof hidden === 'string' && evaluateExpressions) {
3169
+ try {
3170
+ const res = await evaluateExpressions({ hidden });
3171
+ hidden = res.hidden;
3172
+ }
3173
+ catch {
3174
+ hidden = false;
3175
+ }
3176
+ }
3177
+ return { prop: p, hidden: !!hidden };
3178
+ }));
3179
+ return evaluated.filter((x) => !x.hidden).map((x) => x.prop);
3069
3180
  };
3070
3181
  const getPropertyLayout = (name) => {
3071
3182
  return singleInterface?.properties?.find((p) => p.name === name)?.layout;
3072
3183
  };
3073
3184
  // Get related entities for tabs
3074
- const tabDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layoutType === 'tab-detail');
3075
- const tabListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && (!re.layoutType || re.layoutType === 'tab-list'));
3185
+ const tabDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'tab-detail');
3186
+ const tabListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && (!re.layout?.type || re.layout?.type === 'tab-list'));
3076
3187
  // Build related tabs if dependencies are provided
3077
3188
  const tabDetailTabs = await this.buildTabDetails(tabDetailEntities ?? [], dependencies);
3078
3189
  const tabListTabs = await this.buildTabLists(tabListEntities ?? [], rootContext, dependencies);
3079
3190
  // Build actions from single interface
3080
3191
  const actions = this.buildActions(entity, singleInterface);
3081
- // Create expression evaluator for actions
3082
- const evaluateExpressions = dependencies?.expressionEvaluator
3083
- ? this.createExpressionEvaluator(rootContext, dependencies.expressionEvaluator)
3084
- : null;
3085
3192
  return {
3086
3193
  id: entity?.name ?? '',
3087
3194
  title: singleInterface?.title ?? entity?.formats.individual ?? '',
3088
3195
  label: entity?.formats.displayName ?? singleInterface?.title ?? '',
3089
3196
  icon: entity?.icon,
3090
3197
  actions: await this.buildEvaluatedActions(actions, evaluateExpressions),
3198
+ isPrimary: true,
3091
3199
  settings: {
3092
3200
  commands: {
3093
3201
  reject: {
@@ -3184,7 +3292,7 @@ class AXPMainEntityContentBuilder {
3184
3292
  },
3185
3293
  },
3186
3294
  },
3187
- children: filterValidSections(singleInterface?.sections ?? []).map((s) => ({
3295
+ children: await Promise.all(filterValidSections(singleInterface?.sections ?? []).map(async (s) => ({
3188
3296
  type: 'grid-item-layout',
3189
3297
  name: s.id,
3190
3298
  options: {
@@ -3212,9 +3320,7 @@ class AXPMainEntityContentBuilder {
3212
3320
  },
3213
3321
  },
3214
3322
  },
3215
- children: getPropertyByGroupId(s.id)
3216
- .filter((property) => !property.schema.hidden)
3217
- .map((p) => {
3323
+ children: (await getVisiblePropertyByGroupId(s.id)).map((p) => {
3218
3324
  const layout = getPropertyLayout(p.name);
3219
3325
  return {
3220
3326
  type: 'grid-item-layout',
@@ -3229,6 +3335,7 @@ class AXPMainEntityContentBuilder {
3229
3335
  type: 'form-field',
3230
3336
  options: {
3231
3337
  label: p.title,
3338
+ showLabel: layout?.label?.visible ?? true,
3232
3339
  },
3233
3340
  children: [
3234
3341
  {
@@ -3256,7 +3363,7 @@ class AXPMainEntityContentBuilder {
3256
3363
  ],
3257
3364
  },
3258
3365
  ],
3259
- })),
3366
+ }))),
3260
3367
  },
3261
3368
  ],
3262
3369
  };
@@ -3270,7 +3377,8 @@ class AXPMainEntityContentBuilder {
3270
3377
  const scope = {
3271
3378
  context: {
3272
3379
  eval: (path) => {
3273
- return get(context, path);
3380
+ const value = get(context, path);
3381
+ return value;
3274
3382
  },
3275
3383
  },
3276
3384
  };
@@ -3382,11 +3490,15 @@ class AXPLayoutAdapterFactory {
3382
3490
  throw new Error(`Entity ${moduleName}.${entityName} not found`);
3383
3491
  }
3384
3492
  const rootContext = await this.loadRootContext(entity, id);
3493
+ // Build main and related pages
3494
+ const mainPage = await this.buildMainPage(entity, rootContext, dependencies);
3495
+ const relatedPages = await this.buildRelatedPages(entity, rootContext, dependencies);
3496
+ // Compose ordered pages around the primary page
3497
+ const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entity);
3385
3498
  return this.layoutAdapterBuilder
3386
3499
  .setEntity(entity, rootContext)
3387
3500
  .setDependencies(dependencies)
3388
- .setMainPage(await this.buildMainPage(entity, rootContext, dependencies))
3389
- .setRelatedPages(await this.buildRelatedPages(entity, rootContext, dependencies))
3501
+ .setPages(orderedPages)
3390
3502
  .build();
3391
3503
  }
3392
3504
  async loadRootContext(entity, id) {
@@ -3399,13 +3511,17 @@ class AXPLayoutAdapterFactory {
3399
3511
  async buildRelatedPages(entity, rootContext, dependencies) {
3400
3512
  const pages = [];
3401
3513
  // Page Details
3402
- const pageDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layoutType === 'page-detail');
3514
+ const pageDetailEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'page-detail');
3403
3515
  for (const relatedEntity of pageDetailEntities || []) {
3404
3516
  const converter = this.relatedEntityConverterFactory.createPageDetailsConverter();
3405
- pages.push(await converter.convert(relatedEntity, { entityResolver: dependencies.entityResolver }));
3517
+ pages.push(await converter.convert(relatedEntity, {
3518
+ ...dependencies,
3519
+ context: rootContext,
3520
+ rootTitle: await this.getRootTitle(entity, rootContext, dependencies),
3521
+ }));
3406
3522
  }
3407
3523
  // Page Lists
3408
- const pageListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layoutType === 'page-list');
3524
+ const pageListEntities = entity?.relatedEntities?.filter((re) => !re.hidden && re.layout?.type === 'page-list');
3409
3525
  for (const relatedEntity of pageListEntities || []) {
3410
3526
  const converter = this.relatedEntityConverterFactory.createPageListConverter();
3411
3527
  pages.push(await converter.convert(relatedEntity, {
@@ -3416,9 +3532,46 @@ class AXPLayoutAdapterFactory {
3416
3532
  }
3417
3533
  return pages;
3418
3534
  }
3535
+ composePagesWithPositions(mainPage, relatedPages, entity) {
3536
+ // Only consider related entities with layout types page-detail or page-list
3537
+ const pageEntities = (entity?.relatedEntities || []).filter((re) => !re.hidden && (re.layout?.type === 'page-detail' || re.layout?.type === 'page-list'));
3538
+ // Build a map from entity name to its layout config (order/position)
3539
+ const layoutConfigByEntity = {};
3540
+ for (const re of pageEntities) {
3541
+ const [, entityName] = (re.entity || '').split('.');
3542
+ const key = entityName || re.entity;
3543
+ layoutConfigByEntity[key] = { order: re.layout?.order, position: re.layout?.position };
3544
+ }
3545
+ // Split related pages into before/after buckets based on their relatedEntity layout
3546
+ const before = [];
3547
+ const after = [];
3548
+ for (const page of relatedPages) {
3549
+ const conf = layoutConfigByEntity[page.id];
3550
+ const position = conf?.position ?? 'after';
3551
+ if (position === 'before') {
3552
+ before.push(page);
3553
+ }
3554
+ else {
3555
+ after.push(page);
3556
+ }
3557
+ }
3558
+ // Sort by order within each bucket (undefined orders go last)
3559
+ const sortByOrder = (a, b) => {
3560
+ const ao = layoutConfigByEntity[a.id]?.order ?? Number.POSITIVE_INFINITY;
3561
+ const bo = layoutConfigByEntity[b.id]?.order ?? Number.POSITIVE_INFINITY;
3562
+ return ao - bo;
3563
+ };
3564
+ before.sort(sortByOrder);
3565
+ after.sort(sortByOrder);
3566
+ // Ensure the main page is primary
3567
+ mainPage.isPrimary = true;
3568
+ // Compose final pages: before -> main -> after
3569
+ return [...before, mainPage, ...after];
3570
+ }
3419
3571
  async getRootTitle(entity, rootContext, dependencies) {
3420
3572
  // Logic for getting root title
3421
- return await dependencies.expressionEvaluator.evaluate(entity.interfaces?.master?.single?.title, rootContext);
3573
+ const title = await dependencies.expressionEvaluator.evaluate(entity.interfaces?.master?.single?.title, rootContext);
3574
+ return title;
3422
3575
  }
3423
3576
  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 }); }
3424
3577
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: AXPLayoutAdapterFactory, providedIn: 'root' }); }
@@ -3486,6 +3639,81 @@ class AXPEntitySearchDefinitionProvider {
3486
3639
  }
3487
3640
  }
3488
3641
 
3642
+ //#region ---- Column Mapping Helpers ----
3643
+ /**
3644
+ * Maps an entity property to a list widget column configuration.
3645
+ * Preserves readonly behavior and leverages the property's interface options when available.
3646
+ */
3647
+ function mapPropertyToWidgetColumn(property) {
3648
+ return {
3649
+ name: property.name,
3650
+ title: property.title,
3651
+ visible: true,
3652
+ widget: property.schema?.interface
3653
+ ? {
3654
+ type: property.schema.interface.type || 'text-editor',
3655
+ path: property.name,
3656
+ options: {
3657
+ readonly: true,
3658
+ ...property.schema.interface.options,
3659
+ },
3660
+ }
3661
+ : {
3662
+ type: 'text-editor',
3663
+ path: property.name,
3664
+ options: { readonly: true },
3665
+ },
3666
+ };
3667
+ }
3668
+ /**
3669
+ * Maps an entity column metadata (and its related property) to a list widget column configuration.
3670
+ */
3671
+ function mapEntityColumnToWidgetColumn(entity, column) {
3672
+ const property = entity.properties.find((p) => p.name === column.name);
3673
+ return {
3674
+ name: column.name,
3675
+ title: column.title || property?.title || column.name,
3676
+ width: column.options?.width,
3677
+ visible: column.options?.visible !== false,
3678
+ widget: property?.schema?.interface
3679
+ ? {
3680
+ type: property.schema.interface.type || 'text-editor',
3681
+ path: column.options?.dataPath || column.name,
3682
+ options: {
3683
+ readonly: true,
3684
+ ...property.schema.interface.options,
3685
+ },
3686
+ }
3687
+ : {
3688
+ type: 'text-editor',
3689
+ path: column.options?.dataPath || column.name,
3690
+ options: { readonly: true },
3691
+ },
3692
+ };
3693
+ }
3694
+ //#endregion
3695
+ //#region ---- Include/Exclude Utilities ----
3696
+ /**
3697
+ * Applies include and exclude filters to a named collection while preserving order.
3698
+ */
3699
+ function applyIncludeExclude(items, include = [], exclude = []) {
3700
+ let result = items;
3701
+ if (include.length > 0) {
3702
+ result = result.filter((i) => include.includes(i.name));
3703
+ }
3704
+ if (exclude.length > 0) {
3705
+ result = result.filter((i) => !exclude.includes(i.name));
3706
+ }
3707
+ return result;
3708
+ }
3709
+ /**
3710
+ * Returns only visible (non-hidden) properties of an entity.
3711
+ */
3712
+ function getVisibleProperties(entity) {
3713
+ return entity.properties.filter((prop) => !prop.schema?.hidden);
3714
+ }
3715
+ //#endregion
3716
+
3489
3717
  class AXPEntityListTableService {
3490
3718
  constructor() {
3491
3719
  //#region ---- Services & Dependencies ----
@@ -3493,6 +3721,9 @@ class AXPEntityListTableService {
3493
3721
  this.workflow = inject(AXPWorkflowService);
3494
3722
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
3495
3723
  this.evaluateExpressions = async (options, data) => {
3724
+ if (!options) {
3725
+ return {};
3726
+ }
3496
3727
  const scope = {
3497
3728
  context: {
3498
3729
  eval: (path) => {
@@ -3508,8 +3739,7 @@ class AXPEntityListTableService {
3508
3739
  /**
3509
3740
  * Convert Entity to List Widget Options
3510
3741
  */
3511
- async convertEntityToListOptions(entity, options) {
3512
- const allActions = entity.interfaces?.master?.list?.actions?.map((tr) => new AXPEntityCommandTriggerViewModel(entity, tr)) ?? [];
3742
+ async convertEntityToListOptions(entity, options, allActions) {
3513
3743
  const listOptions = {
3514
3744
  // 📊 Data Source
3515
3745
  dataSource: this.createDataSource(entity),
@@ -3561,72 +3791,17 @@ class AXPEntityListTableService {
3561
3791
  createColumnsFromProperties(entity, options) {
3562
3792
  const excludeColumns = options?.excludeColumns || [];
3563
3793
  const includeColumns = options?.includeColumns || [];
3564
- let columns = [];
3565
- // If columns are defined, use them
3566
- if (entity.columns && entity.columns.length > 0) {
3567
- columns = entity.columns.map((col) => this.mapEntityColumnToWidgetColumn(entity, col));
3568
- }
3569
- else {
3570
- // Otherwise use properties
3571
- columns = entity.properties
3572
- .filter((prop) => !prop.schema.hidden) // Only visible properties
3573
- .map((prop) => ({
3574
- name: prop.name,
3575
- title: prop.title,
3576
- visible: true,
3577
- widget: prop.schema.interface
3578
- ? {
3579
- type: prop.schema.interface.type || 'text-editor',
3580
- path: prop.name,
3581
- options: {
3582
- readonly: true,
3583
- ...prop.schema.interface.options,
3584
- },
3585
- }
3586
- : {
3587
- type: 'text-editor',
3588
- path: prop.name,
3589
- options: { readonly: true },
3590
- },
3591
- }));
3592
- }
3593
- // Apply include/exclude filters
3594
- if (includeColumns.length > 0) {
3595
- // If includeColumns is specified, only include those columns
3596
- columns = columns.filter((col) => includeColumns.includes(col.name));
3597
- }
3598
- if (excludeColumns.length > 0) {
3599
- // If excludeColumns is specified, exclude those columns
3600
- columns = columns.filter((col) => !excludeColumns.includes(col.name));
3601
- }
3602
- return columns;
3794
+ // If columns are defined, use them; otherwise use visible properties
3795
+ const baseColumns = entity.columns && entity.columns.length > 0
3796
+ ? entity.columns.map((col) => this.mapEntityColumnToWidgetColumn(entity, col))
3797
+ : getVisibleProperties(entity).map((prop) => mapPropertyToWidgetColumn(prop));
3798
+ return applyIncludeExclude(baseColumns, includeColumns, excludeColumns);
3603
3799
  }
3604
3800
  /**
3605
3801
  * Map EntityTableColumn to ListWidgetColumn
3606
3802
  */
3607
3803
  mapEntityColumnToWidgetColumn(entity, column) {
3608
- // Find corresponding property
3609
- const property = entity.properties.find((p) => p.name === column.name);
3610
- return {
3611
- name: column.name,
3612
- title: column.title || property?.title || column.name,
3613
- width: column.options?.width,
3614
- visible: column.options?.visible !== false,
3615
- widget: property?.schema.interface
3616
- ? {
3617
- type: property.schema.interface.type || 'text-editor',
3618
- path: column.options?.dataPath || column.name,
3619
- options: {
3620
- readonly: true,
3621
- ...property.schema.interface.options,
3622
- },
3623
- }
3624
- : {
3625
- type: 'text-editor',
3626
- path: column.options?.dataPath || column.name,
3627
- options: { readonly: true },
3628
- },
3629
- };
3804
+ return mapEntityColumnToWidgetColumn(entity, column);
3630
3805
  }
3631
3806
  /**
3632
3807
  * Convert Entity Actions to Row Commands
@@ -3642,7 +3817,7 @@ class AXPEntityListTableService {
3642
3817
  icon: action.icon,
3643
3818
  color: action.color,
3644
3819
  look: 'outline',
3645
- visible: action.hidden,
3820
+ visible: !action.hidden,
3646
3821
  disabled: action.disabled,
3647
3822
  }));
3648
3823
  }
@@ -3653,6 +3828,36 @@ class AXPEntityListTableService {
3653
3828
  const actions = entity.interfaces?.master?.list?.actions || [];
3654
3829
  return actions.some((action) => action.scope === AXPEntityCommandScope.Selected);
3655
3830
  }
3831
+ /**
3832
+ * Handle execution of a row command (shared by double-click and command handlers)
3833
+ */
3834
+ async handleRowCommand(e, selectedRows, entity, allActions) {
3835
+ const data = e.data;
3836
+ const commandName = e.name;
3837
+ const action = allActions.find((c) => {
3838
+ return (c.name == e.name &&
3839
+ ((selectedRows?.length
3840
+ ? c.scope == AXPEntityCommandScope.Selected
3841
+ : c.scope == AXPEntityCommandScope.Individual) ||
3842
+ c.scope == AXPEntityCommandScope.TypeLevel));
3843
+ });
3844
+ const command = commandName.split('&')[0];
3845
+ const options = await this.evaluateExpressions(action?.options, data);
3846
+ await this.workflow.execute(command, {
3847
+ entity: getEntityInfo(entity).source,
3848
+ entityInfo: {
3849
+ name: entity.name,
3850
+ module: entity.module,
3851
+ title: entity.title,
3852
+ parentKey: entity.parentKey,
3853
+ source: entity.source,
3854
+ },
3855
+ data: action?.scope == AXPEntityCommandScope.Selected ? selectedRows : data,
3856
+ options: options,
3857
+ metadata: action?.metadata,
3858
+ });
3859
+ console.log('Entity List - Row command:', e.name, e.data);
3860
+ }
3656
3861
  /**
3657
3862
  * Create default events
3658
3863
  */
@@ -3661,38 +3866,27 @@ class AXPEntityListTableService {
3661
3866
  onRowClick: (row) => {
3662
3867
  console.log('Entity List - Row clicked:', row);
3663
3868
  },
3664
- onRowDoubleClick: (row) => {
3665
- console.log('Entity List - Row double clicked:', row);
3869
+ onRowDoubleClick: (e) => {
3870
+ console.log('Entity List - Row double clicked:', e);
3871
+ const defaultAction = allActions.find((c) => {
3872
+ const commandName = c.name.split('&')[0];
3873
+ return (c.default || commandName === 'open-entity') && !c.hidden;
3874
+ });
3875
+ if (!defaultAction) {
3876
+ return;
3877
+ }
3878
+ const d = {
3879
+ component: e.component,
3880
+ name: defaultAction.name,
3881
+ data: e.data,
3882
+ };
3883
+ this.handleRowCommand(d, undefined, entity, allActions);
3666
3884
  },
3667
3885
  onSelectionChange: (selectedRows) => {
3668
3886
  console.log('Entity List - Selection changed:', selectedRows);
3669
3887
  },
3670
3888
  onRowCommand: async (e, selectedRows) => {
3671
- const data = e.data;
3672
- const commandName = e.name;
3673
- const action = allActions.find((c) => {
3674
- return (c.name == e.name &&
3675
- ((selectedRows?.length
3676
- ? c.scope == AXPEntityCommandScope.Selected
3677
- : c.scope == AXPEntityCommandScope.Individual) ||
3678
- c.scope == AXPEntityCommandScope.TypeLevel));
3679
- });
3680
- const command = commandName.split('&')[0];
3681
- const options = await this.evaluateExpressions(action?.options, data);
3682
- await this.workflow.execute(command, {
3683
- entity: getEntityInfo(entity).source,
3684
- entityInfo: {
3685
- name: entity.name,
3686
- module: entity.module,
3687
- title: entity.title,
3688
- parentKey: entity.parentKey,
3689
- source: entity.source,
3690
- },
3691
- data: action?.scope == AXPEntityCommandScope.Selected ? selectedRows : data,
3692
- options: options,
3693
- metadata: action?.metadata,
3694
- });
3695
- console.log('Entity List - Row command:', e.name, e.data);
3889
+ await this.handleRowCommand(e, selectedRows, entity, allActions);
3696
3890
  },
3697
3891
  };
3698
3892
  }
@@ -3754,29 +3948,29 @@ class AXPEntityListToolbarService {
3754
3948
  * Create Column Definitions for Toolbar
3755
3949
  */
3756
3950
  createColumnDefinitions(entity, options) {
3757
- const { columns = [], properties } = entity;
3951
+ const { columns = [] } = entity;
3758
3952
  const excludeColumns = options?.excludeColumns || [];
3759
3953
  const includeColumns = options?.includeColumns || [];
3760
- const visibleProperties = properties.filter(({ schema }) => !schema?.hidden);
3954
+ const visibleProperties = getVisibleProperties(entity);
3761
3955
  const visiblePropNames = new Set(visibleProperties.map(({ name }) => name));
3762
- let filteredColumns = columns.filter(({ name }) => visiblePropNames.has(name));
3763
- // Apply include/exclude filters
3764
- if (includeColumns.length > 0) {
3765
- // If includeColumns is specified, only include those columns
3766
- filteredColumns = filteredColumns.filter((col) => includeColumns.includes(col.name));
3767
- }
3768
- if (excludeColumns.length > 0) {
3769
- // If excludeColumns is specified, exclude those columns
3770
- filteredColumns = filteredColumns.filter((col) => !excludeColumns.includes(col.name));
3771
- }
3772
- return filteredColumns.map((column) => {
3773
- const property = visibleProperties.find(({ name }) => name === column.name);
3774
- return {
3775
- name: column.name,
3776
- title: property?.title,
3777
- visible: column?.options?.visible ?? true,
3778
- };
3779
- });
3956
+ // Prefer explicit entity.columns if present; fallback to visible properties
3957
+ const baseColumns = columns.length > 0
3958
+ ? columns
3959
+ .filter(({ name }) => visiblePropNames.has(name))
3960
+ .map((column) => {
3961
+ const property = visibleProperties.find(({ name }) => name === column.name);
3962
+ return {
3963
+ name: column.name,
3964
+ title: property?.title,
3965
+ visible: column?.options?.visible ?? true,
3966
+ };
3967
+ })
3968
+ : visibleProperties.map((property) => ({
3969
+ name: property.name,
3970
+ title: property.title,
3971
+ visible: true,
3972
+ }));
3973
+ return applyIncludeExclude(baseColumns, includeColumns, excludeColumns);
3780
3974
  }
3781
3975
  /**
3782
3976
  * Create Sort Definitions for Toolbar
@@ -3805,7 +3999,6 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3805
3999
  this.entityListTableService = inject(AXPEntityListTableService);
3806
4000
  this.entityListToolbarService = inject(AXPEntityListToolbarService);
3807
4001
  this.layoutThemeService = inject(AXPLayoutThemeService);
3808
- this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
3809
4002
  this.isMounted = signal(false, ...(ngDevMode ? [{ debugName: "isMounted" }] : []));
3810
4003
  this.entity = signal(null, ...(ngDevMode ? [{ debugName: "entity" }] : []));
3811
4004
  this.listNode = signal(null, ...(ngDevMode ? [{ debugName: "listNode" }] : []));
@@ -3813,19 +4006,41 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3813
4006
  this.allWidgets = viewChildren(AXPWidgetRendererDirective, ...(ngDevMode ? [{ debugName: "allWidgets" }] : []));
3814
4007
  this.listWidget = linkedSignal(() => this.allWidgets().find((widget) => widget.node()?.type === AXPWidgetsCatalog.list));
3815
4008
  this.toolbarWidget = computed(() => this.allWidgets().find((widget) => widget.node()?.type === AXPWidgetsCatalog.listToolbar), ...(ngDevMode ? [{ debugName: "toolbarWidget" }] : []));
3816
- this.selectedItems = computed(() => this.getValue()?.table || [], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
4009
+ this.selectedItems = signal([], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
3817
4010
  this.toolbarNode = signal(null, ...(ngDevMode ? [{ debugName: "toolbarNode" }] : []));
3818
4011
  this.destroyed = new Subject();
3819
4012
  //options
3820
4013
  this.entitySource = computed(() => this.options()['entity'], ...(ngDevMode ? [{ debugName: "entitySource" }] : []));
3821
4014
  this.excludeColumns = computed(() => this.options()['excludeColumns'], ...(ngDevMode ? [{ debugName: "excludeColumns" }] : []));
3822
4015
  this.includeColumns = computed(() => this.options()['includeColumns'], ...(ngDevMode ? [{ debugName: "includeColumns" }] : []));
4016
+ this.externalActions = computed(() => this.options()['actions'], ...(ngDevMode ? [{ debugName: "externalActions" }] : []));
3823
4017
  this.showEntityActions = computed(() => this.options()['showEntityActions'] ?? true, ...(ngDevMode ? [{ debugName: "showEntityActions" }] : []));
3824
4018
  this.showToolbar = computed(() => this.options()['showToolbar'] ?? true, ...(ngDevMode ? [{ debugName: "showToolbar" }] : []));
3825
4019
  //actions
3826
4020
  this.allActions = computed(() => {
3827
- const list = this.entity()?.interfaces?.master?.list?.actions ?? [];
3828
- return list.map((tr) => new AXPEntityCommandTriggerViewModel(this.entity(), tr)) ?? [];
4021
+ const originalList = this.entity()?.interfaces?.master?.list?.actions ?? [];
4022
+ const externalActionList = this.externalActions() ?? [];
4023
+ const usedOverrideActions = new Set();
4024
+ const mergedActions = originalList.map((originalAction) => {
4025
+ const originalCommandName = typeof originalAction.command === 'string' ? originalAction.command : originalAction.command?.name;
4026
+ const overrideAction = externalActionList.find((action) => {
4027
+ const actionCommandName = typeof action?.command === 'string' ? action.command : action?.command?.name;
4028
+ return actionCommandName === originalCommandName;
4029
+ });
4030
+ if (overrideAction) {
4031
+ const overrideKey = `${typeof overrideAction.command === 'string' ? overrideAction.command : overrideAction.command?.name}`;
4032
+ usedOverrideActions.add(overrideKey);
4033
+ return new AXPEntityCommandTriggerViewModel(this.entity(), overrideAction);
4034
+ }
4035
+ return new AXPEntityCommandTriggerViewModel(this.entity(), originalAction);
4036
+ });
4037
+ const additionalActions = externalActionList
4038
+ .filter((action) => {
4039
+ const actionKey = `${typeof action?.command === 'string' ? action.command : action?.command?.name}`;
4040
+ return !usedOverrideActions.has(actionKey);
4041
+ })
4042
+ .map((action) => new AXPEntityCommandTriggerViewModel(this.entity(), action));
4043
+ return [...additionalActions, ...mergedActions];
3829
4044
  }, ...(ngDevMode ? [{ debugName: "allActions" }] : []));
3830
4045
  this.primaryActions = computed(() => {
3831
4046
  const actions = this.allActions()
@@ -3913,7 +4128,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3913
4128
  parentKey: this.entity()?.parentKey,
3914
4129
  source: this.entity()?.source,
3915
4130
  },
3916
- data: action?.scope == AXPEntityCommandScope.Selected ? this.selectedItems() : data,
4131
+ data: action?.scope == AXPEntityCommandScope.Selected
4132
+ ? this.selectedItems()
4133
+ : action?.options?.['process']?.data || null,
3917
4134
  options: action?.options,
3918
4135
  metadata: action?.metadata,
3919
4136
  });
@@ -3979,10 +4196,30 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
3979
4196
  * TODO: Implement column change logic
3980
4197
  */
3981
4198
  handleColumnChanges(changeTracker) {
3982
- if (changeTracker.isColumnsChanged) {
3983
- // TODO: Implement column change handling
3984
- console.log('Column changes detected - implementation needed');
4199
+ if (!changeTracker.isColumnsChanged) {
4200
+ return;
4201
+ }
4202
+ const listInstance = this.listWidget()?.instance;
4203
+ const toolbarState = this.getValue()?.toolbar;
4204
+ if (!listInstance || !toolbarState?.columns) {
4205
+ return;
3985
4206
  }
4207
+ // Current columns from list options
4208
+ const currentColumns = (listInstance.options()['columns'] || []);
4209
+ const columnByName = new Map(currentColumns.map((c) => [c.name, c]));
4210
+ // Build new ordered columns array based on toolbar order/visibility
4211
+ const updatedColumns = toolbarState.columns
4212
+ .map((q) => {
4213
+ const base = columnByName.get(q.name);
4214
+ if (!base) {
4215
+ return null;
4216
+ }
4217
+ // Preserve all existing column config but update visibility
4218
+ return { ...base, visible: q.visible };
4219
+ })
4220
+ .filter((c) => c != null);
4221
+ // Apply updated columns to the list widget and refresh
4222
+ listInstance.setOptions({ columns: updatedColumns });
3986
4223
  }
3987
4224
  async ngOnInit() {
3988
4225
  super.ngOnInit();
@@ -4000,7 +4237,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4000
4237
  excludeColumns: this.excludeColumns(),
4001
4238
  includeColumns: this.includeColumns(),
4002
4239
  };
4003
- const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options);
4240
+ const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options, this.allActions());
4004
4241
  const toolbarOptions = await this.entityListToolbarService.convertEntityToolbarOptions(resolvedEntity, options);
4005
4242
  this.listNode.set({
4006
4243
  type: AXPWidgetsCatalog.list,
@@ -4022,7 +4259,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4022
4259
  },
4023
4260
  });
4024
4261
  }
4025
- ngAfterViewInit() {
4262
+ async ngAfterViewInit() {
4026
4263
  this.workflow.events$
4027
4264
  .pipe(ofType(AXPRefreshEvent))
4028
4265
  .pipe(takeUntil(this.destroyed))
@@ -4031,6 +4268,15 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4031
4268
  this.listWidget()?.instance.call('refresh');
4032
4269
  }
4033
4270
  });
4271
+ const listWidget = (await this.layoutService.waitForWidget(`${this.entitySource()}-tab-list_table`, 500));
4272
+ if (listWidget?.api && typeof listWidget.api === 'function') {
4273
+ const onSelectionChange = listWidget.api()['onSelectionChange'];
4274
+ if (onSelectionChange) {
4275
+ onSelectionChange.pipe(takeUntil(this.destroyed)).subscribe((e) => {
4276
+ this.selectedItems.set(e);
4277
+ });
4278
+ }
4279
+ }
4034
4280
  }
4035
4281
  ngOnDestroy() {
4036
4282
  this.listWidget.set(undefined);
@@ -4038,7 +4284,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
4038
4284
  this.destroyed.complete();
4039
4285
  }
4040
4286
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: AXPEntityListWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
4041
- 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: `
4287
+ 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: `
4042
4288
  @if (showEntityActions()) {
4043
4289
  <div class="ax-flex ax-gap-2 ax-justify-end ax-mb-4">
4044
4290
  @for (action of primaryActions(); track $index) {
@@ -4769,6 +5015,7 @@ class AXPLookupWidgetSelectorViewModel {
4769
5015
  this.options = options;
4770
5016
  this.workflow = this.injector.get(AXPWorkflowService);
4771
5017
  this.filterOperatorMiddleware = this.injector.get(AXPFilterOperatorMiddlewareService);
5018
+ this.widgetResolver = this.injector.get(AXPWidgetRegistryService);
4772
5019
  this.dataSource = new AXDataSource({
4773
5020
  byKey: (key) => {
4774
5021
  const func = this.entityDef.queries.byKey.execute;
@@ -4798,15 +5045,33 @@ class AXPLookupWidgetSelectorViewModel {
4798
5045
  return this.inlineFiltersPlaceholders().length > 0;
4799
5046
  }, ...(ngDevMode ? [{ debugName: "hasInlineFilters" }] : []));
4800
5047
  this.columns = () => {
4801
- const listColumns = this.entityDef.columns ?? [];
4802
- const columns = listColumns?.map((c) => c.name) ?? [];
4803
- const props = this.entityDef.properties.filter((p) => p.schema.hidden != true);
4804
- const displayColumns = props.filter((p) => (this.options.columns?.length ?? 0) > 0
4805
- ? this.options.columns.some((c) => c == p.name)
4806
- : columns.some((c) => c == p.name));
4807
- //
4808
- return displayColumns.map((p) => {
4809
- return new AXPEntityListViewColumnViewModel(p, listColumns?.find((c) => c.name == p.name));
5048
+ const { columns = [], properties } = this.entityDef;
5049
+ const visibleProperties = properties.filter(({ schema }) => !schema?.hidden);
5050
+ const visiblePropNames = new Set(visibleProperties.map(({ name }) => name));
5051
+ return columns
5052
+ .filter(({ name, showAs }) => visiblePropNames.has(name) || showAs)
5053
+ .filter(({ name }) => ((this.options.columns?.length ?? 0) == 0) || this.options.columns?.includes(name))
5054
+ .map((column) => {
5055
+ if (column.showAs) {
5056
+ const widgetConfig = this.widgetResolver.resolve(column.showAs.type);
5057
+ const property = {
5058
+ ...widgetConfig,
5059
+ name: column.name,
5060
+ title: column.title ?? '',
5061
+ schema: {
5062
+ dataType: 'string',
5063
+ interface: {
5064
+ type: column.showAs.type,
5065
+ options: column.showAs.options,
5066
+ },
5067
+ },
5068
+ };
5069
+ return new AXPEntityListViewColumnViewModel(property, column);
5070
+ }
5071
+ else {
5072
+ const property = visibleProperties.find(({ name }) => name === column.name);
5073
+ return new AXPEntityListViewColumnViewModel(property, column);
5074
+ }
4810
5075
  });
4811
5076
  };
4812
5077
  this.inlineFilters = {
@@ -4915,13 +5180,24 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
4915
5180
  this.entity = computed(() => this.options()['entity'], ...(ngDevMode ? [{ debugName: "entity" }] : []));
4916
5181
  this.disabled = computed(() => this.options()['disabled'], ...(ngDevMode ? [{ debugName: "disabled" }] : []));
4917
5182
  this.columns = computed(() => this.options()['columns'] ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : []));
5183
+ this.textField = computed(() => this.options()['textField'] ?? '', ...(ngDevMode ? [{ debugName: "textField" }] : []));
4918
5184
  this.customFilter = computed(() => this.options()['filter'], ...(ngDevMode ? [{ debugName: "customFilter" }] : []));
4919
5185
  this.multiple = computed(() => (this.options()['multiple'] ?? false), ...(ngDevMode ? [{ debugName: "multiple" }] : []));
4920
5186
  this.look = computed(() => this.options()['look'] ?? 'lookup', ...(ngDevMode ? [{ debugName: "look" }] : []));
4921
5187
  this.allowClear = computed(() => (this.options()['allowClear'] ?? false), ...(ngDevMode ? [{ debugName: "allowClear" }] : []));
4922
- this.textField = computed(() => {
4923
- return (this.entityDef()?.formats.lookup ?? this.entityDef()?.properties.find((c) => c.name != 'id')?.name ?? 'title');
4924
- }, ...(ngDevMode ? [{ debugName: "textField" }] : []));
5188
+ this.defaultTextField = computed(() => {
5189
+ const textField = this.entityDef()?.formats.lookup ?? this.entityDef()?.properties.find((c) => c.name != 'id')?.name ?? 'title';
5190
+ return textField;
5191
+ }, ...(ngDevMode ? [{ debugName: "defaultTextField" }] : []));
5192
+ this.displayField = computed(() => {
5193
+ if (this.textField()) {
5194
+ return this.textField();
5195
+ }
5196
+ return this.defaultTextField();
5197
+ }, ...(ngDevMode ? [{ debugName: "displayField" }] : []));
5198
+ this.selectedItemsText = computed(() => {
5199
+ return this.selectedItems().map((item) => get(item, this.displayField())).join(', ');
5200
+ }, ...(ngDevMode ? [{ debugName: "selectedItemsText" }] : []));
4925
5201
  this.valueField = computed(() => this.entityDef()?.properties.find((c) => c.name == 'id')?.name ?? 'id', ...(ngDevMode ? [{ debugName: "valueField" }] : []));
4926
5202
  this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : []));
4927
5203
  this.searchTerm = signal(null, ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
@@ -5061,6 +5337,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5061
5337
  this.selectedItems.set(items);
5062
5338
  //
5063
5339
  const keys = items.map((item) => get(item, this.valueField()));
5340
+ const text = items.map((item) => get(item, this.displayField()));
5064
5341
  //
5065
5342
  // extract data from valueField and set context by expose path
5066
5343
  if (this.expose()) {
@@ -5116,7 +5393,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5116
5393
  <ax-select-box
5117
5394
  [dataSource]="vm()?.dataSource!"
5118
5395
  [ngModel]="selectedItems()"
5119
- [textField]="textField()"
5396
+ [textField]="displayField()"
5120
5397
  [valueField]="valueField()"
5121
5398
  [disabled]="disabled()"
5122
5399
  [multiple]="multiple()"
@@ -5130,9 +5407,10 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
5130
5407
  }
5131
5408
  </ax-select-box>
5132
5409
  } @else {
5410
+ {{selectedItemsText()}}
5133
5411
  <ax-tag-box
5134
5412
  [ngModel]="selectedItems()"
5135
- [textField]="textField()"
5413
+ [textField]="displayField()"
5136
5414
  [valueField]="valueField()"
5137
5415
  (onValueChanged)="handleValueChange($event)"
5138
5416
  [placeholder]="placeholder() | translate | async"
@@ -5182,7 +5460,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
5182
5460
  <ax-select-box
5183
5461
  [dataSource]="vm()?.dataSource!"
5184
5462
  [ngModel]="selectedItems()"
5185
- [textField]="textField()"
5463
+ [textField]="displayField()"
5186
5464
  [valueField]="valueField()"
5187
5465
  [disabled]="disabled()"
5188
5466
  [multiple]="multiple()"
@@ -5196,9 +5474,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
5196
5474
  }
5197
5475
  </ax-select-box>
5198
5476
  } @else {
5477
+ {{selectedItemsText()}}
5199
5478
  <ax-tag-box
5200
5479
  [ngModel]="selectedItems()"
5201
- [textField]="textField()"
5480
+ [textField]="displayField()"
5202
5481
  [valueField]="valueField()"
5203
5482
  (onValueChanged)="handleValueChange($event)"
5204
5483
  [placeholder]="placeholder() | translate | async"
@@ -6120,7 +6399,7 @@ class AXPShowDetailViewAction extends AXPWorkflowAction {
6120
6399
  const [module, entity] = context.getVariable('entity').split('.');
6121
6400
  const { id } = context.getVariable('data');
6122
6401
  const newPayload = {
6123
- commands: `/${this.sessionService.application?.name}/m/${module}/e/${entity}/${id}/new-view`,
6402
+ commands: `/${this.sessionService.application?.name}/m/${module}/e/${entity}/${id}/view`,
6124
6403
  };
6125
6404
  context.setVariable('payload', newPayload);
6126
6405
  this.navigation.execute(context);