@acorex/platform 21.0.0-next.39 → 21.0.0-next.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/acorex-platform-common.mjs +6 -2
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +8 -1
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain.mjs +3 -0
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +137 -34
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +25 -13
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +261 -58
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +1583 -632
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +169 -85
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs → acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs} +9 -4
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs +1053 -409
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs +120 -9
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs → acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs} +3 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs → acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs} +3 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs → acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs} +3 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +10 -10
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs → acorex-platform-themes-shared-settings.provider-D13QB3Hr.mjs} +2 -2
- package/fesm2022/acorex-platform-themes-shared-settings.provider-D13QB3Hr.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs +94 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs} +38 -16
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +183 -84
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +52 -11
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-common.d.ts +14 -10
- package/types/acorex-platform-core.d.ts +13 -2
- package/types/acorex-platform-domain.d.ts +28 -2
- package/types/acorex-platform-layout-builder.d.ts +61 -29
- package/types/acorex-platform-layout-designer.d.ts +88 -16
- package/types/acorex-platform-layout-entity.d.ts +190 -15
- package/types/acorex-platform-layout-widget-core.d.ts +81 -71
- package/types/acorex-platform-layout-widgets.d.ts +131 -54
- package/types/acorex-platform-runtime.d.ts +156 -61
- package/types/acorex-platform-workflow.d.ts +37 -2
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs +0 -65
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs.map +0 -1
|
@@ -2,24 +2,25 @@ import { AXToastService } from '@acorex/components/toast';
|
|
|
2
2
|
import * as i6 from '@acorex/core/translation';
|
|
3
3
|
import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
4
4
|
import * as i4$4 from '@acorex/platform/common';
|
|
5
|
-
import { AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService,
|
|
5
|
+
import { AXPEntityCommandScope, AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPCleanNestedFilters, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
|
|
6
6
|
import * as i0 from '@angular/core';
|
|
7
|
-
import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent,
|
|
7
|
+
import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, computed, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, NgModule, makeEnvironmentProviders } from '@angular/core';
|
|
8
8
|
import { Subject, takeUntil } from 'rxjs';
|
|
9
9
|
import { AXPLayoutBuilderService, LayoutBuilderModule } from '@acorex/platform/layout/builder';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
10
|
+
import * as i3 from '@acorex/platform/layout/widget-core';
|
|
11
|
+
import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, createSelectProperty, createStringProperty, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
|
|
12
|
+
import { AXPSystemActionType, AXPDeviceService, AXPExpressionEvaluatorService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
|
|
13
|
+
import { cloneDeep, merge, get, castArray, set, orderBy, omit, isNil, isEmpty, isEqual as isEqual$1 } from 'lodash-es';
|
|
14
|
+
import { transform, isEqual } from 'lodash';
|
|
12
15
|
import { AXPSessionService, AXPAuthGuard, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
|
|
13
16
|
import { Router, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
|
|
14
|
-
import { defineCommand, AXPCommandService, AXPQueryService, AXPQueryExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
|
|
17
|
+
import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY, AXPCommandService, AXPQueryService, AXPQueryExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
|
|
15
18
|
import * as i1 from '@acorex/components/button';
|
|
16
19
|
import { AXButtonModule } from '@acorex/components/button';
|
|
17
20
|
import * as i4 from '@acorex/components/loading';
|
|
18
21
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
19
22
|
import * as i2 from '@acorex/components/popover';
|
|
20
23
|
import { AXPopoverModule } from '@acorex/components/popover';
|
|
21
|
-
import * as i3 from '@acorex/platform/layout/widget-core';
|
|
22
|
-
import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, createSelectProperty, createStringProperty, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
|
|
23
24
|
import * as i5 from '@angular/common';
|
|
24
25
|
import { CommonModule, AsyncPipe } from '@angular/common';
|
|
25
26
|
import { AXPThemeLayoutBlockComponent, AXPPreloadFiltersComponent, AXPStateMessageComponent, AXPColumnItemListComponent, AXPDataSelectorService, AXPPageComponentRegistryService } from '@acorex/platform/layout/components';
|
|
@@ -69,7 +70,6 @@ import * as i5$3 from '@acorex/components/label';
|
|
|
69
70
|
import { AXLabelModule } from '@acorex/components/label';
|
|
70
71
|
import * as i7 from '@acorex/components/text-box';
|
|
71
72
|
import { AXTextBoxModule } from '@acorex/components/text-box';
|
|
72
|
-
import { transform, isEqual as isEqual$1 } from 'lodash';
|
|
73
73
|
|
|
74
74
|
function ensureListActions(ctx) {
|
|
75
75
|
ctx.interfaces.update((i) => {
|
|
@@ -119,6 +119,28 @@ const AXP_ENTITY_ACTION_PLUGIN = new InjectionToken('AXP_ENTITY_ACTION_PLUGIN');
|
|
|
119
119
|
function createModifierContext(entity) {
|
|
120
120
|
const ctx = {
|
|
121
121
|
entity,
|
|
122
|
+
plugins: {
|
|
123
|
+
list: () => entity.plugins ?? [],
|
|
124
|
+
add: (...items) => {
|
|
125
|
+
entity.plugins ??= [];
|
|
126
|
+
entity.plugins.push(...items);
|
|
127
|
+
return ctx;
|
|
128
|
+
},
|
|
129
|
+
remove: (predicate) => {
|
|
130
|
+
if (entity.plugins)
|
|
131
|
+
entity.plugins = entity.plugins.filter((p) => !predicate(p));
|
|
132
|
+
return ctx;
|
|
133
|
+
},
|
|
134
|
+
find: (name) => ({
|
|
135
|
+
get: () => entity.plugins?.find((p) => p.name === name),
|
|
136
|
+
update: (updater) => {
|
|
137
|
+
const index = entity.plugins?.findIndex((p) => p.name === name);
|
|
138
|
+
if (index !== undefined && index !== -1 && entity.plugins)
|
|
139
|
+
entity.plugins[index] = updater(entity.plugins[index]);
|
|
140
|
+
return ctx;
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
122
144
|
title: {
|
|
123
145
|
get: () => entity.title,
|
|
124
146
|
set: (newTitle) => {
|
|
@@ -781,6 +803,405 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
781
803
|
}]
|
|
782
804
|
}] });
|
|
783
805
|
|
|
806
|
+
// #region Master
|
|
807
|
+
function entityMasterCreateAction() {
|
|
808
|
+
return {
|
|
809
|
+
title: '@general:actions.create.title',
|
|
810
|
+
command: {
|
|
811
|
+
name: 'Entity:Create',
|
|
812
|
+
},
|
|
813
|
+
priority: 'primary',
|
|
814
|
+
type: AXPSystemActionType.Create,
|
|
815
|
+
scope: AXPEntityCommandScope.TypeLevel,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function entityMasterEditAction() {
|
|
819
|
+
return {
|
|
820
|
+
title: '@general:actions.edit.title',
|
|
821
|
+
command: 'Entity:Update',
|
|
822
|
+
priority: 'secondary',
|
|
823
|
+
type: AXPSystemActionType.Update,
|
|
824
|
+
scope: AXPEntityCommandScope.Individual,
|
|
825
|
+
default: true,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function entityMasterBulkDeleteAction() {
|
|
829
|
+
return {
|
|
830
|
+
title: '@general:actions.delete-items.title',
|
|
831
|
+
command: 'delete-entity',
|
|
832
|
+
priority: 'primary',
|
|
833
|
+
type: AXPSystemActionType.Delete,
|
|
834
|
+
scope: AXPEntityCommandScope.Selected,
|
|
835
|
+
order: 100,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function entityMasterViewAction() {
|
|
839
|
+
return {
|
|
840
|
+
title: '@general:actions.view.title',
|
|
841
|
+
command: 'open-entity',
|
|
842
|
+
priority: 'secondary',
|
|
843
|
+
type: AXPSystemActionType.View,
|
|
844
|
+
scope: AXPEntityCommandScope.Individual,
|
|
845
|
+
default: true,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function entityMasterDeleteAction() {
|
|
849
|
+
return {
|
|
850
|
+
title: '@general:actions.delete.title',
|
|
851
|
+
command: 'delete-entity',
|
|
852
|
+
priority: 'secondary',
|
|
853
|
+
type: AXPSystemActionType.Delete,
|
|
854
|
+
scope: AXPEntityCommandScope.Individual,
|
|
855
|
+
order: 100,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function entityMasterCrudActions(options) {
|
|
859
|
+
const opts = {
|
|
860
|
+
create: true,
|
|
861
|
+
delete: true,
|
|
862
|
+
view: true,
|
|
863
|
+
edit: false,
|
|
864
|
+
...options,
|
|
865
|
+
};
|
|
866
|
+
const actions = [];
|
|
867
|
+
if (opts.create) {
|
|
868
|
+
actions.push(entityMasterCreateAction());
|
|
869
|
+
}
|
|
870
|
+
if (opts.delete) {
|
|
871
|
+
actions.push(entityMasterBulkDeleteAction());
|
|
872
|
+
actions.push(entityMasterDeleteAction());
|
|
873
|
+
}
|
|
874
|
+
if (opts.view) {
|
|
875
|
+
actions.push(entityMasterViewAction());
|
|
876
|
+
}
|
|
877
|
+
if (opts.edit) {
|
|
878
|
+
actions.push(entityMasterEditAction());
|
|
879
|
+
}
|
|
880
|
+
return actions;
|
|
881
|
+
}
|
|
882
|
+
function entityMasterRecordActions() {
|
|
883
|
+
return [entityMasterDeleteAction()];
|
|
884
|
+
}
|
|
885
|
+
// #endregion
|
|
886
|
+
// #region Details
|
|
887
|
+
function entityDetailsCreateActions(parentId) {
|
|
888
|
+
return {
|
|
889
|
+
title: '@general:actions.create.title',
|
|
890
|
+
command: {
|
|
891
|
+
name: 'Entity:Create',
|
|
892
|
+
options: {
|
|
893
|
+
process: {
|
|
894
|
+
redirect: false,
|
|
895
|
+
canCreateNewOne: true,
|
|
896
|
+
data: {
|
|
897
|
+
[parentId]: '{{context.eval("id")}}',
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
priority: 'primary',
|
|
903
|
+
type: AXPSystemActionType.Create,
|
|
904
|
+
scope: AXPEntityCommandScope.TypeLevel,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Type-level Create with empty `process.data`; the FK is supplied from `relatedEntity.persistence.foreignKeyField`
|
|
909
|
+
* when the related list is built (see `mergeForeignKeyFieldIntoCreateActions`).
|
|
910
|
+
*/
|
|
911
|
+
function entityDetailsCreateActionsDeferredParent() {
|
|
912
|
+
return {
|
|
913
|
+
title: '@general:actions.create.title',
|
|
914
|
+
command: {
|
|
915
|
+
name: 'Entity:Create',
|
|
916
|
+
options: {
|
|
917
|
+
process: {
|
|
918
|
+
redirect: false,
|
|
919
|
+
canCreateNewOne: true,
|
|
920
|
+
data: {},
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
priority: 'primary',
|
|
925
|
+
type: AXPSystemActionType.Create,
|
|
926
|
+
scope: AXPEntityCommandScope.TypeLevel,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Ensures each type-level `Entity:Create` action includes `process.data[foreignKeyField]` bound to the parent row id.
|
|
931
|
+
*/
|
|
932
|
+
function mergeForeignKeyFieldIntoCreateActions(foreignKeyField, actions) {
|
|
933
|
+
if (!foreignKeyField || !actions?.length) {
|
|
934
|
+
return actions ?? [];
|
|
935
|
+
}
|
|
936
|
+
return actions.map((a) => {
|
|
937
|
+
const cmd = a.command;
|
|
938
|
+
if (typeof cmd !== 'object' ||
|
|
939
|
+
!cmd ||
|
|
940
|
+
cmd.name !== 'Entity:Create' ||
|
|
941
|
+
a.scope !== AXPEntityCommandScope.TypeLevel) {
|
|
942
|
+
return a;
|
|
943
|
+
}
|
|
944
|
+
const opts = (cmd.options ?? {});
|
|
945
|
+
const proc = (opts['process'] ?? {});
|
|
946
|
+
const data = { ...(proc['data'] ?? {}) };
|
|
947
|
+
if (data[foreignKeyField] === undefined || data[foreignKeyField] === null || data[foreignKeyField] === '') {
|
|
948
|
+
data[foreignKeyField] = '{{ context.eval("id") }}';
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
...a,
|
|
952
|
+
command: {
|
|
953
|
+
...cmd,
|
|
954
|
+
options: { ...opts, process: { ...proc, data } },
|
|
955
|
+
},
|
|
956
|
+
};
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
/** Property names hidden on nested Create from a related list (`excludeProperties` plus `foreignKeyField` when set). */
|
|
960
|
+
function collectNestedCreateHiddenProperties(relatedEntity) {
|
|
961
|
+
const fk = relatedEntity.persistence?.foreignKeyField;
|
|
962
|
+
const merged = [...(relatedEntity.excludeProperties ?? [])];
|
|
963
|
+
if (fk) {
|
|
964
|
+
merged.push(fk);
|
|
965
|
+
}
|
|
966
|
+
const unique = [...new Set(merged)];
|
|
967
|
+
return unique.length ? unique : undefined;
|
|
968
|
+
}
|
|
969
|
+
function entityDetailsSimpleCondition(fk) {
|
|
970
|
+
return {
|
|
971
|
+
name: fk,
|
|
972
|
+
operator: { type: 'equal' },
|
|
973
|
+
value: '{{context.eval("id")}}',
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function entityDetailsReferenceCondition(type) {
|
|
977
|
+
return [
|
|
978
|
+
{
|
|
979
|
+
name: 'reference.id',
|
|
980
|
+
operator: { type: 'equal' },
|
|
981
|
+
value: '{{context.eval("id")}}',
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: 'reference.type',
|
|
985
|
+
operator: { type: 'equal' },
|
|
986
|
+
value: type,
|
|
987
|
+
},
|
|
988
|
+
];
|
|
989
|
+
}
|
|
990
|
+
function entityDetailsEditAction() {
|
|
991
|
+
return {
|
|
992
|
+
title: '@general:actions.edit.title',
|
|
993
|
+
// command: 'quick-modify-entity',
|
|
994
|
+
command: 'Entity:Update',
|
|
995
|
+
priority: 'secondary',
|
|
996
|
+
type: AXPSystemActionType.Update,
|
|
997
|
+
default: true,
|
|
998
|
+
scope: AXPEntityCommandScope.Individual,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function entityDetailsNewEditAction() {
|
|
1002
|
+
return {
|
|
1003
|
+
title: 'New Edit',
|
|
1004
|
+
command: {
|
|
1005
|
+
name: 'Entity:Update',
|
|
1006
|
+
},
|
|
1007
|
+
priority: 'secondary',
|
|
1008
|
+
type: AXPSystemActionType.Update,
|
|
1009
|
+
default: true,
|
|
1010
|
+
scope: AXPEntityCommandScope.Individual,
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function entityOverrideDetailsViewAction() {
|
|
1014
|
+
return {
|
|
1015
|
+
title: '@general:actions.view.title',
|
|
1016
|
+
command: 'open-entity',
|
|
1017
|
+
priority: 'secondary',
|
|
1018
|
+
hidden: true,
|
|
1019
|
+
type: AXPSystemActionType.View,
|
|
1020
|
+
scope: AXPEntityCommandScope.Individual,
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function entityDetailsCrudActions(parentId, options) {
|
|
1024
|
+
const opts = {
|
|
1025
|
+
create: true,
|
|
1026
|
+
delete: true,
|
|
1027
|
+
view: true,
|
|
1028
|
+
edit: true,
|
|
1029
|
+
...options,
|
|
1030
|
+
};
|
|
1031
|
+
const actions = [];
|
|
1032
|
+
if (opts.create) {
|
|
1033
|
+
if (parentId) {
|
|
1034
|
+
actions.push(entityDetailsCreateActions(parentId));
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
actions.push(entityDetailsCreateActionsDeferredParent());
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (opts.edit) {
|
|
1041
|
+
actions.push(entityDetailsEditAction());
|
|
1042
|
+
}
|
|
1043
|
+
if (opts.view) {
|
|
1044
|
+
actions.push(entityOverrideDetailsViewAction());
|
|
1045
|
+
}
|
|
1046
|
+
return actions;
|
|
1047
|
+
}
|
|
1048
|
+
function entityDetailsReferenceCreateActions(type) {
|
|
1049
|
+
return [
|
|
1050
|
+
{
|
|
1051
|
+
title: '@general:actions.create.title',
|
|
1052
|
+
command: {
|
|
1053
|
+
name: 'Entity:Create',
|
|
1054
|
+
options: {
|
|
1055
|
+
process: {
|
|
1056
|
+
redirect: false,
|
|
1057
|
+
canCreateNewOne: true,
|
|
1058
|
+
data: {
|
|
1059
|
+
reference: {
|
|
1060
|
+
id: '{{context.eval("id")}}',
|
|
1061
|
+
type: type,
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
priority: 'primary',
|
|
1068
|
+
type: AXPSystemActionType.Create,
|
|
1069
|
+
scope: AXPEntityCommandScope.TypeLevel,
|
|
1070
|
+
},
|
|
1071
|
+
entityDetailsEditAction(),
|
|
1072
|
+
entityOverrideDetailsViewAction(),
|
|
1073
|
+
];
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Computes a diff between two plain objects with array-aware semantics.
|
|
1077
|
+
* - For arrays of objects with an id field, computes added/removed by id.
|
|
1078
|
+
* - For arrays of primitives or objects without id, uses deep equality.
|
|
1079
|
+
* - For scalars/objects, reports oldValue/newValue when changed.
|
|
1080
|
+
*/
|
|
1081
|
+
function detectEntityChanges(oldObj, newObj) {
|
|
1082
|
+
return transform(newObj, (result, value, key) => {
|
|
1083
|
+
if (!isEqual(value, oldObj[key])) {
|
|
1084
|
+
const oldValue = oldObj[key];
|
|
1085
|
+
if (Array.isArray(value) || Array.isArray(oldValue)) {
|
|
1086
|
+
const oldArray = Array.isArray(oldValue) ? oldValue : [];
|
|
1087
|
+
const newArray = Array.isArray(value) ? value : [];
|
|
1088
|
+
const hasId = newArray.length > 0 && typeof newArray[0] === 'object' && newArray[0] !== null && 'id' in newArray[0];
|
|
1089
|
+
if (hasId) {
|
|
1090
|
+
const added = newArray.filter((item) => !oldArray.some((oldItem) => oldItem.id === item.id));
|
|
1091
|
+
const removed = oldArray.filter((item) => !newArray.some((newItem) => newItem.id === item.id));
|
|
1092
|
+
result[key] = { oldValue, newValue: value, added, removed };
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
const added = newArray.filter((item) => !oldArray.some((oldItem) => isEqual(item, oldItem)));
|
|
1096
|
+
const removed = oldArray.filter((item) => !newArray.some((newItem) => isEqual(item, newItem)));
|
|
1097
|
+
result[key] = { oldValue, newValue: value, added, removed };
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
result[key] = { oldValue, newValue: value };
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}, {});
|
|
1105
|
+
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Maps entity property `description` (or any i18n key / multi-language payload) to `form-field`
|
|
1110
|
+
* widget options. Returns `undefined` when there is nothing to show.
|
|
1111
|
+
*/
|
|
1112
|
+
function hintFormFieldOptionsFromDescription(description) {
|
|
1113
|
+
if (description == null) {
|
|
1114
|
+
return undefined;
|
|
1115
|
+
}
|
|
1116
|
+
if (typeof description === 'string' && description.trim() === '') {
|
|
1117
|
+
return undefined;
|
|
1118
|
+
}
|
|
1119
|
+
return { hint: description, hintDisplayMode: 'icon' };
|
|
1120
|
+
}
|
|
1121
|
+
//#endregion
|
|
1122
|
+
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region ---- Helpers ----
|
|
1125
|
+
function isRelatedEntityStringColumns(columns) {
|
|
1126
|
+
return columns.length === 0 || typeof columns[0] === 'string';
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Property names from `AXPRelatedEntity.columns` (no expression evaluation).
|
|
1130
|
+
* Empty array means caller can treat as “no name filter” (show all allowed by entity).
|
|
1131
|
+
*/
|
|
1132
|
+
function getRelatedEntityColumnNames(columns) {
|
|
1133
|
+
if (!columns?.length) {
|
|
1134
|
+
return [];
|
|
1135
|
+
}
|
|
1136
|
+
if (isRelatedEntityStringColumns(columns)) {
|
|
1137
|
+
return [...columns];
|
|
1138
|
+
}
|
|
1139
|
+
return columns.map((c) => c.name);
|
|
1140
|
+
}
|
|
1141
|
+
function isTruthyVisible(value) {
|
|
1142
|
+
if (value === true) {
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
if (value === false || value === null || value === undefined) {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof value === 'string') {
|
|
1149
|
+
const t = value.trim().toLowerCase();
|
|
1150
|
+
return t !== '' && t !== 'false' && t !== '0';
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof value === 'number') {
|
|
1153
|
+
return value !== 0 && !Number.isNaN(value);
|
|
1154
|
+
}
|
|
1155
|
+
return Boolean(value);
|
|
1156
|
+
}
|
|
1157
|
+
//#endregion
|
|
1158
|
+
//#region ---- Public API ----
|
|
1159
|
+
/**
|
|
1160
|
+
* Resolves `AXPRelatedEntity.columns` for `entity-list`:
|
|
1161
|
+
* - `string[]` → `includeColumns` only (legacy).
|
|
1162
|
+
* - `AXPEntityTableColumn[]` → evaluates `options.visible` when it is a string (parent detail context),
|
|
1163
|
+
* drops hidden / invisible columns, and returns `relatedTableColumns` + matching `includeColumns`.
|
|
1164
|
+
*/
|
|
1165
|
+
async function resolveRelatedEntityColumns(columns, expressionEvaluator, scope) {
|
|
1166
|
+
if (!columns?.length) {
|
|
1167
|
+
return {};
|
|
1168
|
+
}
|
|
1169
|
+
if (isRelatedEntityStringColumns(columns)) {
|
|
1170
|
+
return { includeColumns: getRelatedEntityColumnNames(columns) };
|
|
1171
|
+
}
|
|
1172
|
+
const relatedTableColumns = [];
|
|
1173
|
+
for (const raw of columns) {
|
|
1174
|
+
const col = cloneDeep(raw);
|
|
1175
|
+
if (col.hidden === true) {
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
let visible = col.options?.visible;
|
|
1179
|
+
if (visible === undefined) {
|
|
1180
|
+
visible = true;
|
|
1181
|
+
}
|
|
1182
|
+
else if (typeof visible === 'string') {
|
|
1183
|
+
const evaluated = await expressionEvaluator.evaluate(visible, scope);
|
|
1184
|
+
visible = isTruthyVisible(evaluated);
|
|
1185
|
+
}
|
|
1186
|
+
if (visible === false) {
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
col.options = { ...col.options, visible: true };
|
|
1190
|
+
relatedTableColumns.push(col);
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
relatedTableColumns,
|
|
1194
|
+
includeColumns: relatedTableColumns.map((c) => c.name),
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
//#endregion
|
|
1198
|
+
|
|
1199
|
+
/** Registered widget name for entity create/update step wizard (dialog footer uses `widget:${name}.next`). */
|
|
1200
|
+
const ENTITY_FORM_STEP_WIZARD_NAME = 'entityFormStepWizard';
|
|
1201
|
+
/** Footer command: validate, persist main entity, merge context, advance wizard. */
|
|
1202
|
+
const ENTITY_FORM_ACTION_FIRST_STEP_CONTINUE = 'entity-form-first-step-continue';
|
|
1203
|
+
/** Footer command: close dialog (data already saved per step). */
|
|
1204
|
+
const ENTITY_FORM_ACTION_DONE = 'entity-form-done';
|
|
784
1205
|
//#endregion
|
|
785
1206
|
class AXPEntityFormBuilderService {
|
|
786
1207
|
constructor() {
|
|
@@ -788,11 +1209,12 @@ class AXPEntityFormBuilderService {
|
|
|
788
1209
|
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
789
1210
|
this.layoutBuilder = inject(AXPLayoutBuilderService);
|
|
790
1211
|
this.deviceService = inject(AXPDeviceService);
|
|
1212
|
+
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
791
1213
|
}
|
|
792
1214
|
//#endregion
|
|
793
1215
|
//#region ---- Public API ----
|
|
794
1216
|
entity(fullName) {
|
|
795
|
-
return new InterfaceSelector(this.entityRegistry, this.layoutBuilder, this.deviceService, fullName, this);
|
|
1217
|
+
return new InterfaceSelector(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, fullName, this);
|
|
796
1218
|
}
|
|
797
1219
|
/**
|
|
798
1220
|
* Fetches a record by ID for the specified entity.
|
|
@@ -826,15 +1248,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
826
1248
|
}] });
|
|
827
1249
|
//#region ---- Builders ----
|
|
828
1250
|
class InterfaceSelector {
|
|
829
|
-
constructor(entityRegistry, layoutBuilder, deviceService, fullName, formBuilderService) {
|
|
1251
|
+
constructor(entityRegistry, layoutBuilder, deviceService, expressionEvaluator, fullName, formBuilderService) {
|
|
830
1252
|
this.entityRegistry = entityRegistry;
|
|
831
1253
|
this.layoutBuilder = layoutBuilder;
|
|
832
1254
|
this.deviceService = deviceService;
|
|
1255
|
+
this.expressionEvaluator = expressionEvaluator;
|
|
833
1256
|
this.fullName = fullName;
|
|
834
1257
|
this.formBuilderService = formBuilderService;
|
|
835
1258
|
}
|
|
836
1259
|
create(initialData) {
|
|
837
|
-
const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.fullName, 'create', undefined, this.formBuilderService);
|
|
1260
|
+
const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, this.fullName, 'create', undefined, this.formBuilderService);
|
|
838
1261
|
if (initialData) {
|
|
839
1262
|
filter.context(initialData);
|
|
840
1263
|
}
|
|
@@ -850,7 +1273,7 @@ class InterfaceSelector {
|
|
|
850
1273
|
initialData = data;
|
|
851
1274
|
recordId = data['id'] || data['_id'];
|
|
852
1275
|
}
|
|
853
|
-
const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.fullName, 'update', recordId, this.formBuilderService);
|
|
1276
|
+
const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, this.fullName, 'update', recordId, this.formBuilderService);
|
|
854
1277
|
if (Object.keys(initialData).length > 0) {
|
|
855
1278
|
filter.context(initialData);
|
|
856
1279
|
}
|
|
@@ -866,7 +1289,7 @@ class InterfaceSelector {
|
|
|
866
1289
|
initialData = data;
|
|
867
1290
|
recordId = data['id'] || data['_id'];
|
|
868
1291
|
}
|
|
869
|
-
const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.fullName, 'single', recordId, this.formBuilderService);
|
|
1292
|
+
const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, this.fullName, 'single', recordId, this.formBuilderService);
|
|
870
1293
|
if (Object.keys(initialData).length > 0) {
|
|
871
1294
|
filter.context(initialData);
|
|
872
1295
|
}
|
|
@@ -874,10 +1297,11 @@ class InterfaceSelector {
|
|
|
874
1297
|
}
|
|
875
1298
|
}
|
|
876
1299
|
class PropertyFilter {
|
|
877
|
-
constructor(entityRegistry, layoutBuilder, deviceService, fullName, kind, recordId, formBuilderService) {
|
|
1300
|
+
constructor(entityRegistry, layoutBuilder, deviceService, expressionEvaluator, fullName, kind, recordId, formBuilderService) {
|
|
878
1301
|
this.entityRegistry = entityRegistry;
|
|
879
1302
|
this.layoutBuilder = layoutBuilder;
|
|
880
1303
|
this.deviceService = deviceService;
|
|
1304
|
+
this.expressionEvaluator = expressionEvaluator;
|
|
881
1305
|
this.fullName = fullName;
|
|
882
1306
|
this.kind = kind;
|
|
883
1307
|
this.formBuilderService = formBuilderService;
|
|
@@ -934,11 +1358,10 @@ class PropertyFilter {
|
|
|
934
1358
|
return this;
|
|
935
1359
|
}
|
|
936
1360
|
async build() {
|
|
937
|
-
const dialog = await this.buildDialog();
|
|
1361
|
+
const dialog = await this.buildDialog({});
|
|
938
1362
|
return dialog.build();
|
|
939
1363
|
}
|
|
940
1364
|
async show() {
|
|
941
|
-
const dialog = await this.buildDialog();
|
|
942
1365
|
// Context: always load by key when we have a record id (authoritative full row), then merge any
|
|
943
1366
|
// caller/list row context. Skipping fetch when initialContext was non-empty left dialogs without
|
|
944
1367
|
// fields not present on the passed row (e.g. Entity:View single() with partial list payload).
|
|
@@ -963,14 +1386,17 @@ class PropertyFilter {
|
|
|
963
1386
|
}
|
|
964
1387
|
}
|
|
965
1388
|
const effectiveContext = merge({}, this.initialContext, baseContext);
|
|
1389
|
+
const dialog = await this.buildDialog(effectiveContext);
|
|
966
1390
|
dialog.setContext(effectiveContext);
|
|
967
1391
|
return await dialog.show();
|
|
968
1392
|
}
|
|
969
1393
|
/**
|
|
970
1394
|
* Builds the dialog node structure without showing it.
|
|
971
1395
|
* This method is shared by both build() and show() methods.
|
|
1396
|
+
*
|
|
1397
|
+
* @param modelForRelatedEval Merged root model used to evaluate related list filters (show() passes full row).
|
|
972
1398
|
*/
|
|
973
|
-
async buildDialog() {
|
|
1399
|
+
async buildDialog(modelForRelatedEval) {
|
|
974
1400
|
const { moduleName, entityName } = parseEntityFullName(this.fullName);
|
|
975
1401
|
const entity = await this.entityRegistry.resolve(moduleName, entityName);
|
|
976
1402
|
// Select the appropriate interface based on kind
|
|
@@ -988,7 +1414,9 @@ class PropertyFilter {
|
|
|
988
1414
|
// Collect all groups for title lookup (main + merged)
|
|
989
1415
|
const allGroups = [...(entity.groups ?? [])];
|
|
990
1416
|
// Process merge-detail related entities
|
|
991
|
-
const mergeDetailEntities = (entity.relatedEntities ?? []).filter((re) => !re.hidden &&
|
|
1417
|
+
const mergeDetailEntities = (entity.relatedEntities ?? []).filter((re) => !re.hidden &&
|
|
1418
|
+
re.layout?.type === 'merge-detail' &&
|
|
1419
|
+
isRelatedEntityIncludedOnMasterForm(re, this.kind, 'merge-detail'));
|
|
992
1420
|
// Build merged properties map by section
|
|
993
1421
|
const mergedPropsBySection = new Map();
|
|
994
1422
|
// Initialize with main entity properties
|
|
@@ -1161,104 +1589,271 @@ class PropertyFilter {
|
|
|
1161
1589
|
return sectionProps.length > 0 || extraFields.length > 0;
|
|
1162
1590
|
});
|
|
1163
1591
|
const sectionScaleFactors = normalizeLayoutFillRowGaps(renderedSections, (s) => s.layout, (s) => s.id);
|
|
1592
|
+
const filterEvalRoot = merge({}, this.initialContext, modelForRelatedEval);
|
|
1593
|
+
const listRelatedEntities = collectListRelatedEntitiesForFormWizard(entity, this.kind);
|
|
1594
|
+
const useListWizard = listRelatedEntities.length > 0 && (this.kind === 'create' || this.kind === 'update');
|
|
1595
|
+
const listWizardSteps = [];
|
|
1596
|
+
for (const re of listRelatedEntities) {
|
|
1597
|
+
const [rm, en] = re.entity.split('.');
|
|
1598
|
+
const relDef = await this.entityRegistry.resolve(rm, en);
|
|
1599
|
+
listWizardSteps.push({
|
|
1600
|
+
stepId: buildEntityFormRelatedStepId(re),
|
|
1601
|
+
title: (re.title ?? relDef?.title ?? re.entity),
|
|
1602
|
+
rel: re,
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
const prebuiltRelatedListConfigs = useListWizard
|
|
1606
|
+
? await Promise.all(listWizardSteps.map((s) => this.buildRelatedEntityListWidgetOptions(s.rel, filterEvalRoot)))
|
|
1607
|
+
: [];
|
|
1608
|
+
const mainFormGridArgs = {
|
|
1609
|
+
entity,
|
|
1610
|
+
finalSections,
|
|
1611
|
+
mergedPropsBySection,
|
|
1612
|
+
allGroups,
|
|
1613
|
+
extraFieldsByGroup: this.extraFieldsByGroup,
|
|
1614
|
+
sectionScaleFactors,
|
|
1615
|
+
};
|
|
1164
1616
|
const dialog = this.layoutBuilder.create().dialog((d) => {
|
|
1165
1617
|
d.setTitle(title);
|
|
1166
|
-
d.setSize(this.externalSize ?? calculatedSize);
|
|
1618
|
+
d.setSize(this.externalSize ?? (useListWizard ? 'lg' : calculatedSize));
|
|
1167
1619
|
d.setCloseButton(true);
|
|
1168
1620
|
d.content((layout) => {
|
|
1169
|
-
// Default mode: 'view' for single, 'edit' for create/update
|
|
1170
1621
|
const defaultMode = this.kind === 'single' ? 'view' : 'edit';
|
|
1171
1622
|
layout.mode(this.externalMode ?? defaultMode);
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
}
|
|
1184
|
-
const sectionLayout = section.layout ?? undefined;
|
|
1185
|
-
grid.item(sectionLayout, (fs) => {
|
|
1186
|
-
const sectionVisible = section.layout?.visible;
|
|
1187
|
-
if (sectionVisible !== undefined && sectionVisible !== null) {
|
|
1188
|
-
fs.visible(sectionVisible);
|
|
1189
|
-
}
|
|
1190
|
-
fs.setLook('fieldset');
|
|
1191
|
-
fs.setTitle((getGroupTitleFromList(allGroups, groupId) || groupId));
|
|
1192
|
-
fs.setCols(12);
|
|
1193
|
-
// Sort properties by order within section
|
|
1194
|
-
const orderedProps = [...sectionProps].sort((a, b) => {
|
|
1195
|
-
const aOrder = a.__order ?? Infinity;
|
|
1196
|
-
const bOrder = b.__order ?? Infinity;
|
|
1197
|
-
return aOrder - bOrder;
|
|
1623
|
+
if (useListWizard) {
|
|
1624
|
+
layout.stepWizard((w) => {
|
|
1625
|
+
w.name(ENTITY_FORM_STEP_WIZARD_NAME);
|
|
1626
|
+
w.setLook('circular');
|
|
1627
|
+
w.setShowActions(false);
|
|
1628
|
+
const mainStepTitle = (entity.formats?.individual ||
|
|
1629
|
+
entity.title ||
|
|
1630
|
+
`${entity.module}.${entity.name}`);
|
|
1631
|
+
w.step('main', mainStepTitle, (step) => {
|
|
1632
|
+
step.content((inner) => {
|
|
1633
|
+
inner.grid((grid) => this.fillMainEntityFormGrid(grid, mainFormGridArgs));
|
|
1198
1634
|
});
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
field.defaultValue(prop.schema.defaultValue);
|
|
1215
|
-
}
|
|
1216
|
-
const fieldLayout = toFieldLayout(mergedProp.__layout);
|
|
1217
|
-
if (fieldLayout) {
|
|
1218
|
-
field.layout(fieldLayout);
|
|
1219
|
-
}
|
|
1220
|
-
if (fieldLayout?.label?.visible === false) {
|
|
1221
|
-
field.setShowLabel(false);
|
|
1222
|
-
}
|
|
1223
|
-
const widgetType = prop.schema?.interface?.type || '';
|
|
1224
|
-
const widgetOptions = buildWidgetOptions(prop);
|
|
1225
|
-
const extendedProperties = buildWidgetExtendedProperties(prop);
|
|
1226
|
-
const finalExtendedProps = dataPath
|
|
1227
|
-
? prefixExpressions(extendedProperties, dataPath)
|
|
1228
|
-
: extendedProperties;
|
|
1229
|
-
field.customWidget(widgetType, { ...widgetOptions, ...finalExtendedProps });
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
for (const extra of extraFields) {
|
|
1233
|
-
const label = extra.path?.split('.').slice(-1)[0] || extra.path;
|
|
1234
|
-
fs.formField(label, (f) => {
|
|
1235
|
-
const legacy = toCompatFormFieldBuilder(f);
|
|
1236
|
-
legacy.path(extra.path);
|
|
1237
|
-
extra.delegate?.(legacy);
|
|
1635
|
+
});
|
|
1636
|
+
listWizardSteps.forEach((step, idx) => {
|
|
1637
|
+
const built = prebuiltRelatedListConfigs[idx];
|
|
1638
|
+
w.step(step.stepId, step.title, (st) => {
|
|
1639
|
+
st.content((inner) => {
|
|
1640
|
+
// Step content uses root LayoutBuilder (no customWidget); wrap in flex like other roots.
|
|
1641
|
+
inner.flex((flex) => {
|
|
1642
|
+
flex.customWidget(AXPWidgetsCatalog.entityList, (iw) => {
|
|
1643
|
+
iw.name(built.name);
|
|
1644
|
+
if (built.defaultValue) {
|
|
1645
|
+
iw.defaultValue(built.defaultValue);
|
|
1646
|
+
}
|
|
1647
|
+
iw.options(built.options);
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
1238
1650
|
});
|
|
1239
|
-
}
|
|
1651
|
+
});
|
|
1240
1652
|
});
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
layout.grid((grid) => this.fillMainEntityFormGrid(grid, mainFormGridArgs));
|
|
1657
|
+
}
|
|
1243
1658
|
});
|
|
1244
|
-
|
|
1245
|
-
if (this.externalActionsDelegate) {
|
|
1659
|
+
if (this.externalActionsDelegate && !useListWizard) {
|
|
1246
1660
|
d.setActions(this.externalActionsDelegate);
|
|
1247
1661
|
}
|
|
1662
|
+
else if (useListWizard) {
|
|
1663
|
+
d.setActions((ab) => configureEntityFormWizardFooterActions(ab, ENTITY_FORM_STEP_WIZARD_NAME, this.kind === 'create'
|
|
1664
|
+
? '{{ ((typeof context.id === "string" && context.id.length > 0) || (typeof context.id === "number" && !isNaN(context.id)) || (typeof context._id === "string" && context._id.length > 0) || (typeof context._id === "number" && !isNaN(context._id))) ? "@general:entity-form.update-and-continue.title" : "@general:entity-form.create-and-continue.title" }}'
|
|
1665
|
+
: '@general:entity-form.save-and-continue.title'));
|
|
1666
|
+
}
|
|
1248
1667
|
else if (this.kind === 'single') {
|
|
1249
|
-
|
|
1250
|
-
d.setActions((a) => a.cancel());
|
|
1668
|
+
d.setActions((a) => a.submit('@general:actions.close.title'));
|
|
1251
1669
|
}
|
|
1252
1670
|
else {
|
|
1253
|
-
|
|
1254
|
-
|
|
1671
|
+
d.setActions((a) => a.submit(this.kind === 'create' ? '@general:actions.create.title' : '@general:actions.apply.title'));
|
|
1672
|
+
}
|
|
1673
|
+
if (useListWizard && this.onActionHandler) {
|
|
1674
|
+
d.onAction(this.createWizardOnActionWrapper(this.onActionHandler));
|
|
1255
1675
|
}
|
|
1256
|
-
if (this.onActionHandler) {
|
|
1676
|
+
else if (this.onActionHandler) {
|
|
1257
1677
|
d.onAction(this.onActionHandler);
|
|
1258
1678
|
}
|
|
1679
|
+
else if (this.kind === 'single') {
|
|
1680
|
+
d.onAction(async () => ({ success: true, skipValidate: true }));
|
|
1681
|
+
}
|
|
1259
1682
|
});
|
|
1260
1683
|
return dialog;
|
|
1261
1684
|
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Renders main entity fieldsets and fields into a grid container (step 1 or non-wizard form).
|
|
1687
|
+
*/
|
|
1688
|
+
fillMainEntityFormGrid(grid, args) {
|
|
1689
|
+
grid.setColumns(12);
|
|
1690
|
+
grid.setGap('1rem');
|
|
1691
|
+
for (const section of args.finalSections) {
|
|
1692
|
+
const groupId = section.id;
|
|
1693
|
+
const sectionProps = args.mergedPropsBySection.get(groupId) ?? [];
|
|
1694
|
+
const extraFields = args.extraFieldsByGroup.get(groupId) ?? [];
|
|
1695
|
+
if (sectionProps.length === 0 && extraFields.length === 0) {
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
const sectionLayout = section.layout ?? undefined;
|
|
1699
|
+
grid.item(sectionLayout, (fs) => {
|
|
1700
|
+
const sectionVisible = section.layout?.visible;
|
|
1701
|
+
if (sectionVisible !== undefined && sectionVisible !== null) {
|
|
1702
|
+
fs.visible(sectionVisible);
|
|
1703
|
+
}
|
|
1704
|
+
fs.setLook('fieldset');
|
|
1705
|
+
fs.setTitle((getGroupTitleFromList(args.allGroups, groupId) || groupId));
|
|
1706
|
+
fs.setCols(12);
|
|
1707
|
+
const sectionLabelVisible = section.layout?.label?.visible ??
|
|
1708
|
+
args.entity.interfaces?.master?.single?.sections?.find((s) => s?.id === groupId)?.layout?.label?.visible;
|
|
1709
|
+
if (sectionLabelVisible === false) {
|
|
1710
|
+
const legendHidden = { showTitle: false, showHeader: false };
|
|
1711
|
+
fs.setOptions(legendHidden);
|
|
1712
|
+
}
|
|
1713
|
+
const orderedProps = [...sectionProps].sort((a, b) => {
|
|
1714
|
+
const aOrder = a.__order ?? Infinity;
|
|
1715
|
+
const bOrder = b.__order ?? Infinity;
|
|
1716
|
+
return aOrder - bOrder;
|
|
1717
|
+
});
|
|
1718
|
+
applySectionScaleToProperties(orderedProps, args.sectionScaleFactors.get(groupId));
|
|
1719
|
+
normalizeLayoutFillRowGaps(orderedProps, (p) => p.__layout);
|
|
1720
|
+
for (const prop of orderedProps) {
|
|
1721
|
+
const mergedProp = prop;
|
|
1722
|
+
const dataPath = mergedProp.__dataPath;
|
|
1723
|
+
const fieldPath = dataPath ? `${dataPath}.${prop.name}` : prop.name;
|
|
1724
|
+
fs.formField(prop.title, (field) => {
|
|
1725
|
+
field.path(fieldPath);
|
|
1726
|
+
if (prop.schema?.visible !== undefined) {
|
|
1727
|
+
field.visible(prop.schema?.visible);
|
|
1728
|
+
}
|
|
1729
|
+
if (prop.schema?.readonly !== undefined) {
|
|
1730
|
+
field.readonly(prop.schema.readonly);
|
|
1731
|
+
}
|
|
1732
|
+
if (prop.schema?.defaultValue !== undefined) {
|
|
1733
|
+
field.defaultValue(prop.schema.defaultValue);
|
|
1734
|
+
}
|
|
1735
|
+
const fieldLayout = toFieldLayout(mergedProp.__layout);
|
|
1736
|
+
if (fieldLayout) {
|
|
1737
|
+
field.layout(fieldLayout);
|
|
1738
|
+
}
|
|
1739
|
+
if (fieldLayout?.label?.visible === false) {
|
|
1740
|
+
field.setShowLabel(false);
|
|
1741
|
+
}
|
|
1742
|
+
const hintOpts = hintFormFieldOptionsFromDescription(prop.description);
|
|
1743
|
+
if (hintOpts) {
|
|
1744
|
+
field.setOptions(hintOpts);
|
|
1745
|
+
}
|
|
1746
|
+
const widgetType = prop.schema?.interface?.type || '';
|
|
1747
|
+
const widgetOptions = buildWidgetOptions(prop);
|
|
1748
|
+
const extendedProperties = buildWidgetExtendedProperties(prop);
|
|
1749
|
+
const finalExtendedProps = dataPath ? prefixExpressions(extendedProperties, dataPath) : extendedProperties;
|
|
1750
|
+
field.customWidget(widgetType, { ...widgetOptions, ...finalExtendedProps });
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
for (const extra of extraFields) {
|
|
1754
|
+
const label = extra.path?.split('.').slice(-1)[0] || extra.path;
|
|
1755
|
+
fs.formField(label, (f) => {
|
|
1756
|
+
const legacy = toCompatFormFieldBuilder(f);
|
|
1757
|
+
legacy.path(extra.path);
|
|
1758
|
+
extra.delegate?.(legacy);
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
async buildRelatedEntityListWidgetOptions(relatedEntity, filterEvalRoot) {
|
|
1765
|
+
const evaluateExpressions = async (actionData) => {
|
|
1766
|
+
const scope = {
|
|
1767
|
+
context: {
|
|
1768
|
+
eval: (path) => get(filterEvalRoot, path),
|
|
1769
|
+
},
|
|
1770
|
+
};
|
|
1771
|
+
return await this.expressionEvaluator.evaluate(actionData, scope);
|
|
1772
|
+
};
|
|
1773
|
+
const syncRelatedFromRoot = this.kind === 'create';
|
|
1774
|
+
const foreignKeyField = relatedEntity.persistence?.foreignKeyField;
|
|
1775
|
+
const filters = syncRelatedFromRoot
|
|
1776
|
+
? []
|
|
1777
|
+
: await Promise.all(relatedEntity.conditions?.map(async (c) => {
|
|
1778
|
+
const value = await evaluateExpressions(c.value);
|
|
1779
|
+
return {
|
|
1780
|
+
field: c.name,
|
|
1781
|
+
operator: c.operator,
|
|
1782
|
+
value,
|
|
1783
|
+
hidden: true,
|
|
1784
|
+
};
|
|
1785
|
+
}) ?? []);
|
|
1786
|
+
const rawOrEvaluatedActions = syncRelatedFromRoot
|
|
1787
|
+
? (relatedEntity.actions ?? [])
|
|
1788
|
+
: await this.evaluateRelatedEntityActionsForFormWizard(relatedEntity.actions, filterEvalRoot);
|
|
1789
|
+
const evaluatedActions = mergeForeignKeyFieldIntoCreateActions(foreignKeyField, rawOrEvaluatedActions);
|
|
1790
|
+
const columnScope = {
|
|
1791
|
+
context: {
|
|
1792
|
+
eval: (path) => get(filterEvalRoot, path),
|
|
1793
|
+
},
|
|
1794
|
+
};
|
|
1795
|
+
const { includeColumns, relatedTableColumns } = await resolveRelatedEntityColumns(relatedEntity.columns, this.expressionEvaluator, columnScope);
|
|
1796
|
+
const nestedCreateHiddenProperties = collectNestedCreateHiddenProperties(relatedEntity);
|
|
1797
|
+
return {
|
|
1798
|
+
name: `${relatedEntity.entity}-entity-form-step`,
|
|
1799
|
+
defaultValue: {
|
|
1800
|
+
toolbar: {
|
|
1801
|
+
filters,
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
options: {
|
|
1805
|
+
entity: relatedEntity.entity,
|
|
1806
|
+
showEntityActions: true,
|
|
1807
|
+
showToolbar: false,
|
|
1808
|
+
actions: evaluatedActions,
|
|
1809
|
+
maxHeight: '400px',
|
|
1810
|
+
includeColumns,
|
|
1811
|
+
relatedTableColumns,
|
|
1812
|
+
customFilterDefinitions: relatedEntity.customFilterDefinitions,
|
|
1813
|
+
...(foreignKeyField ? { foreignKeyField } : {}),
|
|
1814
|
+
...(nestedCreateHiddenProperties?.length ? { nestedCreateHiddenProperties } : {}),
|
|
1815
|
+
...(syncRelatedFromRoot && relatedEntity.conditions?.length
|
|
1816
|
+
? {
|
|
1817
|
+
relatedFilterConditionSpecs: cloneDeep(relatedEntity.conditions),
|
|
1818
|
+
syncRelatedListFiltersFromDialogContext: true,
|
|
1819
|
+
}
|
|
1820
|
+
: {}),
|
|
1821
|
+
},
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
async evaluateRelatedEntityActionsForFormWizard(actions, filterEvalRoot) {
|
|
1825
|
+
const list = actions ?? [];
|
|
1826
|
+
const scope = {
|
|
1827
|
+
context: {
|
|
1828
|
+
eval: (path) => get(filterEvalRoot, path),
|
|
1829
|
+
},
|
|
1830
|
+
};
|
|
1831
|
+
return Promise.all(list.map(async (action) => {
|
|
1832
|
+
if (action.scope === AXPEntityCommandScope.Individual) {
|
|
1833
|
+
return action;
|
|
1834
|
+
}
|
|
1835
|
+
return (await this.expressionEvaluator.evaluate(action, scope));
|
|
1836
|
+
}));
|
|
1837
|
+
}
|
|
1838
|
+
createWizardOnActionWrapper(userHandler) {
|
|
1839
|
+
return async (ref) => {
|
|
1840
|
+
const action = ref.action();
|
|
1841
|
+
if (action === ENTITY_FORM_ACTION_DONE) {
|
|
1842
|
+
return { success: true, data: ref.context(), skipValidate: true };
|
|
1843
|
+
}
|
|
1844
|
+
if (action === ENTITY_FORM_ACTION_FIRST_STEP_CONTINUE) {
|
|
1845
|
+
const out = (await userHandler(ref));
|
|
1846
|
+
if (out?.success === false) {
|
|
1847
|
+
return out;
|
|
1848
|
+
}
|
|
1849
|
+
const item = out?.data?.item ?? out?.data ?? {};
|
|
1850
|
+
ref.patchContext?.(merge({}, ref.context(), item));
|
|
1851
|
+
await ref.invokeWidget?.(ENTITY_FORM_STEP_WIZARD_NAME, 'next', { setLoading: ref.setLoading });
|
|
1852
|
+
return merge({}, out, { keepDialogOpen: true });
|
|
1853
|
+
}
|
|
1854
|
+
return await userHandler(ref);
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1262
1857
|
computeAllowedNames(allNames) {
|
|
1263
1858
|
if (this.includeList && this.includeList.size > 0) {
|
|
1264
1859
|
return new Set(allNames.filter((n) => this.includeList.has(n)));
|
|
@@ -1539,6 +2134,62 @@ function buildWidgetExtendedProperties(prop) {
|
|
|
1539
2134
|
}
|
|
1540
2135
|
return extended;
|
|
1541
2136
|
}
|
|
2137
|
+
function isRelatedEntityIncludedOnMasterForm(related, kind, placementKind) {
|
|
2138
|
+
if (kind !== 'create' && kind !== 'update') {
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
const defaultInclusive = placementKind === 'merge-detail';
|
|
2142
|
+
if (kind === 'create') {
|
|
2143
|
+
const flag = related.appearOn?.create;
|
|
2144
|
+
return defaultInclusive ? flag !== false : flag === true;
|
|
2145
|
+
}
|
|
2146
|
+
const flag = related.appearOn?.update;
|
|
2147
|
+
return defaultInclusive ? flag !== false : flag === true;
|
|
2148
|
+
}
|
|
2149
|
+
function collectListRelatedEntitiesForFormWizard(entity, kind) {
|
|
2150
|
+
return (entity.relatedEntities ?? [])
|
|
2151
|
+
.filter((re) => !re.hidden &&
|
|
2152
|
+
(re.layout?.type === 'tab-list' || re.layout?.type === 'page-list') &&
|
|
2153
|
+
isRelatedEntityIncludedOnMasterForm(re, kind, 'list'))
|
|
2154
|
+
.sort((a, b) => (a.layout?.order ?? 0) - (b.layout?.order ?? 0));
|
|
2155
|
+
}
|
|
2156
|
+
function buildEntityFormRelatedStepId(re) {
|
|
2157
|
+
const safe = re.entity.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
2158
|
+
return `related-${safe}-${re.layout?.order ?? 0}`;
|
|
2159
|
+
}
|
|
2160
|
+
function configureEntityFormWizardFooterActions(ab, wizardName, firstStepContinueTitleKey) {
|
|
2161
|
+
ab.custom({
|
|
2162
|
+
title: '@general:actions.previous.title',
|
|
2163
|
+
command: `widget:${wizardName}.previous`,
|
|
2164
|
+
icon: 'fa-regular fa-arrow-left',
|
|
2165
|
+
color: 'primary',
|
|
2166
|
+
position: 'suffix',
|
|
2167
|
+
disabled: '{{api.getStatus().isFirst}}',
|
|
2168
|
+
});
|
|
2169
|
+
ab.custom({
|
|
2170
|
+
title: firstStepContinueTitleKey,
|
|
2171
|
+
command: { name: ENTITY_FORM_ACTION_FIRST_STEP_CONTINUE },
|
|
2172
|
+
color: 'primary',
|
|
2173
|
+
position: 'suffix',
|
|
2174
|
+
hidden: '{{!api.getStatus().isFirst}}',
|
|
2175
|
+
predicateApiWidgetName: wizardName,
|
|
2176
|
+
});
|
|
2177
|
+
ab.custom({
|
|
2178
|
+
title: '@general:actions.next.title',
|
|
2179
|
+
command: `widget:${wizardName}.next`,
|
|
2180
|
+
color: 'primary',
|
|
2181
|
+
position: 'suffix',
|
|
2182
|
+
hidden: '{{api.getStatus().isFirst || api.getStatus().isLast}}',
|
|
2183
|
+
});
|
|
2184
|
+
ab.custom({
|
|
2185
|
+
title: '@general:entity-form.done.title',
|
|
2186
|
+
command: { name: ENTITY_FORM_ACTION_DONE },
|
|
2187
|
+
color: 'primary',
|
|
2188
|
+
position: 'suffix',
|
|
2189
|
+
hidden: '{{!api.getStatus().isLast}}',
|
|
2190
|
+
predicateApiWidgetName: wizardName,
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
1542
2193
|
function getGroupTitle(entity, groupId) {
|
|
1543
2194
|
const g = (entity.groups || []).find((x) => x.id === groupId);
|
|
1544
2195
|
return g?.title;
|
|
@@ -1727,6 +2378,11 @@ var openEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
|
1727
2378
|
AXPOpenEntityDetailsCommand: AXPOpenEntityDetailsCommand
|
|
1728
2379
|
});
|
|
1729
2380
|
|
|
2381
|
+
/** Matches persisted PK checks used for choose update vs create (strict types only). */
|
|
2382
|
+
function hasPersistedRootId(context) {
|
|
2383
|
+
const raw = context?.id ?? context?._id;
|
|
2384
|
+
return (typeof raw === 'string' && raw.length > 0) || (typeof raw === 'number' && !Number.isNaN(raw));
|
|
2385
|
+
}
|
|
1730
2386
|
class AXPCreateEntityCommand {
|
|
1731
2387
|
constructor() {
|
|
1732
2388
|
this.entityForm = inject(AXPEntityFormBuilderService);
|
|
@@ -1760,7 +2416,6 @@ class AXPCreateEntityCommand {
|
|
|
1760
2416
|
const entityRef = await this.entityService.resolve(moduleName, entityName);
|
|
1761
2417
|
let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(data);
|
|
1762
2418
|
chain.actions((actions) => {
|
|
1763
|
-
actions.cancel('@general:actions.cancel.title');
|
|
1764
2419
|
actions.submit('@general:actions.create.title');
|
|
1765
2420
|
});
|
|
1766
2421
|
if (excludeProperties && excludeProperties.length > 0) {
|
|
@@ -1785,25 +2440,31 @@ class AXPCreateEntityCommand {
|
|
|
1785
2440
|
}
|
|
1786
2441
|
const result = await chain
|
|
1787
2442
|
.onAction(async (dialogRef) => {
|
|
1788
|
-
if (dialogRef.action() === 'cancel') {
|
|
1789
|
-
return { success: false };
|
|
1790
|
-
}
|
|
1791
2443
|
const createFn = entityRef.commands?.create?.execute;
|
|
1792
|
-
|
|
1793
|
-
|
|
2444
|
+
const updateFn = entityRef.commands?.update?.execute;
|
|
2445
|
+
const context = dialogRef.context();
|
|
2446
|
+
console.log('context', context);
|
|
2447
|
+
const hasPersistedId = hasPersistedRootId(context);
|
|
2448
|
+
const persistFn = hasPersistedId ? updateFn : createFn;
|
|
2449
|
+
const missingPersistHandlerMsg = hasPersistedId
|
|
2450
|
+
? await this.translationService.translateAsync('@general:messages.entity.update-command-unavailable')
|
|
2451
|
+
: await this.translationService.translateAsync('@general:messages.entity.create-command-unavailable');
|
|
2452
|
+
const failedMsgKey = hasPersistedId
|
|
2453
|
+
? '@general:messages.entity.update-failed'
|
|
2454
|
+
: '@general:messages.entity.create-failed';
|
|
2455
|
+
if (!persistFn) {
|
|
1794
2456
|
if (enableOperationToasts) {
|
|
1795
2457
|
this.toastService.show({
|
|
1796
2458
|
color: 'danger',
|
|
1797
2459
|
title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
|
|
1798
|
-
content:
|
|
2460
|
+
content: missingPersistHandlerMsg,
|
|
1799
2461
|
});
|
|
1800
2462
|
}
|
|
1801
|
-
throw new Error(
|
|
2463
|
+
throw new Error(missingPersistHandlerMsg);
|
|
1802
2464
|
}
|
|
1803
2465
|
dialogRef.setLoading(true);
|
|
1804
2466
|
try {
|
|
1805
|
-
const
|
|
1806
|
-
const result = await createFn(context);
|
|
2467
|
+
const result = await persistFn(context);
|
|
1807
2468
|
if (result) {
|
|
1808
2469
|
return {
|
|
1809
2470
|
success: true,
|
|
@@ -1817,13 +2478,13 @@ class AXPCreateEntityCommand {
|
|
|
1817
2478
|
return {
|
|
1818
2479
|
success: false,
|
|
1819
2480
|
message: {
|
|
1820
|
-
text: await this.translationService.translateAsync(
|
|
2481
|
+
text: await this.translationService.translateAsync(failedMsgKey),
|
|
1821
2482
|
},
|
|
1822
2483
|
};
|
|
1823
2484
|
}
|
|
1824
2485
|
}
|
|
1825
2486
|
catch (e) {
|
|
1826
|
-
const errorMsg = e.message ?? (await this.translationService.translateAsync(
|
|
2487
|
+
const errorMsg = e.message ?? (await this.translationService.translateAsync(failedMsgKey));
|
|
1827
2488
|
if (enableOperationToasts) {
|
|
1828
2489
|
this.toastService.show({
|
|
1829
2490
|
color: 'danger',
|
|
@@ -1911,6 +2572,7 @@ const axpCreateEntityCommandDefinition = defineCommand({
|
|
|
1911
2572
|
summary: 'Dialog submit persists entity; navigation/toast behavior per entity form builder and settings.',
|
|
1912
2573
|
derivesFrom: 'AXPExecuteCommandResult<any> from entity form chain and AXPOpenEntityDetailsCommand',
|
|
1913
2574
|
},
|
|
2575
|
+
capabilities: ['ai'],
|
|
1914
2576
|
ai: {
|
|
1915
2577
|
shortDescription: 'Opens create dialog with pre-filled data; execution waits until the user submits or cancels, then returns—use for human-confirmed inserts into mock storage.',
|
|
1916
2578
|
usage: {
|
|
@@ -1925,6 +2587,7 @@ const axpCreateEntityCommandDefinition = defineCommand({
|
|
|
1925
2587
|
tags: ['entity', 'create', 'form', 'dialog', 'human-in-the-loop', 'mock', 'entity-storage', 'wait-user'],
|
|
1926
2588
|
toolInputDefaults: { ...axpCreateEntityAiToolInputDefaults },
|
|
1927
2589
|
},
|
|
2590
|
+
categories: [AXP_COMMAND_DEFINITION_CATEGORY_ENTITY],
|
|
1928
2591
|
});
|
|
1929
2592
|
|
|
1930
2593
|
class AXPUpdateEntityCommand {
|
|
@@ -1959,9 +2622,7 @@ class AXPUpdateEntityCommand {
|
|
|
1959
2622
|
};
|
|
1960
2623
|
}
|
|
1961
2624
|
const entityRef = await this.entityService.resolve(moduleName, entityName);
|
|
1962
|
-
let dialogRef;
|
|
1963
2625
|
try {
|
|
1964
|
-
// Validate data type
|
|
1965
2626
|
if (!data || (typeof data !== 'string' && typeof data !== 'object')) {
|
|
1966
2627
|
return {
|
|
1967
2628
|
success: false,
|
|
@@ -1970,10 +2631,8 @@ class AXPUpdateEntityCommand {
|
|
|
1970
2631
|
},
|
|
1971
2632
|
};
|
|
1972
2633
|
}
|
|
1973
|
-
// Pass data directly to update() - it handles string | object internally
|
|
1974
2634
|
let chain = this.entityForm.entity(`${moduleName}.${entityName}`).update(data);
|
|
1975
2635
|
chain.actions((actions) => {
|
|
1976
|
-
actions.cancel('@general:actions.cancel.title');
|
|
1977
2636
|
actions.submit('@general:actions.apply.title');
|
|
1978
2637
|
});
|
|
1979
2638
|
if (excludeProperties && excludeProperties.length > 0) {
|
|
@@ -1994,19 +2653,8 @@ class AXPUpdateEntityCommand {
|
|
|
1994
2653
|
if (finalSize) {
|
|
1995
2654
|
chain.size(finalSize);
|
|
1996
2655
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
dialogRef.close();
|
|
2000
|
-
return {
|
|
2001
|
-
success: false,
|
|
2002
|
-
// message: {
|
|
2003
|
-
// text: await this.translationService.translateAsync('@general:messages.generic.cancel.description'),
|
|
2004
|
-
// },
|
|
2005
|
-
};
|
|
2006
|
-
}
|
|
2007
|
-
else if (dialogRef.action() === 'submit') {
|
|
2008
|
-
dialogRef.setLoading(true);
|
|
2009
|
-
const context = dialogRef.context();
|
|
2656
|
+
return (await chain
|
|
2657
|
+
.onAction(async (dialogRef) => {
|
|
2010
2658
|
const updateFn = entityRef.commands?.update?.execute;
|
|
2011
2659
|
if (!updateFn) {
|
|
2012
2660
|
const msg = await this.translationService.translateAsync('@general:messages.entity.update-command-unavailable');
|
|
@@ -2017,35 +2665,44 @@ class AXPUpdateEntityCommand {
|
|
|
2017
2665
|
content: msg,
|
|
2018
2666
|
});
|
|
2019
2667
|
}
|
|
2020
|
-
|
|
2021
|
-
success: false,
|
|
2022
|
-
message: { text: msg },
|
|
2023
|
-
};
|
|
2668
|
+
throw new Error(msg);
|
|
2024
2669
|
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
dialogRef.
|
|
2670
|
+
dialogRef.setLoading(true);
|
|
2671
|
+
try {
|
|
2672
|
+
const context = dialogRef.context();
|
|
2673
|
+
const result = await updateFn(context);
|
|
2674
|
+
if (result) {
|
|
2675
|
+
return {
|
|
2676
|
+
success: true,
|
|
2677
|
+
data: result.data ?? result,
|
|
2678
|
+
message: {
|
|
2679
|
+
text: await this.translationService.translateAsync('@general:messages.generic.success.description'),
|
|
2680
|
+
},
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2028
2683
|
return {
|
|
2029
|
-
success:
|
|
2030
|
-
data: result,
|
|
2684
|
+
success: false,
|
|
2031
2685
|
message: {
|
|
2032
|
-
text: await this.translationService.translateAsync('@general:messages.
|
|
2686
|
+
text: await this.translationService.translateAsync('@general:messages.entity.command-no-result'),
|
|
2033
2687
|
},
|
|
2034
2688
|
};
|
|
2035
2689
|
}
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2690
|
+
catch (e) {
|
|
2691
|
+
const errorMsg = e.message ?? (await this.translationService.translateAsync('@general:messages.entity.update-failed'));
|
|
2692
|
+
if (enableOperationToasts) {
|
|
2693
|
+
this.toastService.show({
|
|
2694
|
+
color: 'danger',
|
|
2695
|
+
title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
|
|
2696
|
+
content: errorMsg,
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
throw e;
|
|
2700
|
+
}
|
|
2701
|
+
finally {
|
|
2702
|
+
dialogRef.setLoading(false);
|
|
2703
|
+
}
|
|
2704
|
+
})
|
|
2705
|
+
.show());
|
|
2049
2706
|
}
|
|
2050
2707
|
catch (error) {
|
|
2051
2708
|
const text = error instanceof Error
|
|
@@ -2063,11 +2720,6 @@ class AXPUpdateEntityCommand {
|
|
|
2063
2720
|
message: { text },
|
|
2064
2721
|
};
|
|
2065
2722
|
}
|
|
2066
|
-
finally {
|
|
2067
|
-
if (dialogRef) {
|
|
2068
|
-
dialogRef.setLoading(false);
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
2723
|
}
|
|
2072
2724
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2073
2725
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityCommand, providedIn: 'root' }); }
|
|
@@ -2193,6 +2845,18 @@ class AXPEntityDetailPopoverComponent {
|
|
|
2193
2845
|
this.entityDetails = signal(null, ...(ngDevMode ? [{ debugName: "entityDetails" }] : /* istanbul ignore next */ []));
|
|
2194
2846
|
this.isLoadingDetails = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingDetails" }] : /* istanbul ignore next */ []));
|
|
2195
2847
|
this.isDetailPopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDetailPopoverOpen" }] : /* istanbul ignore next */ []));
|
|
2848
|
+
/**
|
|
2849
|
+
* Stable list of property widgets for the template. Must be a signal (computed), not a method:
|
|
2850
|
+
* calling a method from the template rebuilds nodes every CD cycle and can make the widget renderer loop.
|
|
2851
|
+
*/
|
|
2852
|
+
this.entityPropertiesWithWidgets = computed(() => {
|
|
2853
|
+
const details = this.entityDetails();
|
|
2854
|
+
const data = details?.entityData;
|
|
2855
|
+
const entityDefinition = details?.entityDefinition;
|
|
2856
|
+
if (!data || !entityDefinition?.properties)
|
|
2857
|
+
return [];
|
|
2858
|
+
return this.buildEntityPropertiesWithWidgets(data, entityDefinition, this.textField(), this.valueField());
|
|
2859
|
+
}, ...(ngDevMode ? [{ debugName: "entityPropertiesWithWidgets" }] : /* istanbul ignore next */ []));
|
|
2196
2860
|
}
|
|
2197
2861
|
//#endregion
|
|
2198
2862
|
//#region ---- Public Methods ----
|
|
@@ -2347,16 +3011,12 @@ class AXPEntityDetailPopoverComponent {
|
|
|
2347
3011
|
}
|
|
2348
3012
|
return 0;
|
|
2349
3013
|
}
|
|
2350
|
-
|
|
2351
|
-
const data = this.entityDetails()?.entityData;
|
|
2352
|
-
const entityDefinition = this.entityDetails()?.entityDefinition;
|
|
2353
|
-
if (!data || !entityDefinition?.properties)
|
|
2354
|
-
return [];
|
|
3014
|
+
buildEntityPropertiesWithWidgets(data, entityDefinition, textFieldValue, valueFieldValue) {
|
|
2355
3015
|
// Use properties (not columns) for correct titles and schema; columns may have dataPath that doesn't match
|
|
2356
|
-
|
|
3016
|
+
return entityDefinition.properties
|
|
2357
3017
|
.filter((prop) => {
|
|
2358
3018
|
// Exclude technical fields
|
|
2359
|
-
if (prop.name === 'id' || prop.name ===
|
|
3019
|
+
if (prop.name === 'id' || prop.name === textFieldValue || prop.name === valueFieldValue) {
|
|
2360
3020
|
return false;
|
|
2361
3021
|
}
|
|
2362
3022
|
// Only include properties that have a meaningful value (using get for dotted paths)
|
|
@@ -2424,7 +3084,6 @@ class AXPEntityDetailPopoverComponent {
|
|
|
2424
3084
|
node: widgetNode,
|
|
2425
3085
|
};
|
|
2426
3086
|
});
|
|
2427
|
-
return importantProperties;
|
|
2428
3087
|
}
|
|
2429
3088
|
/**
|
|
2430
3089
|
* Resolves the data path for a property. For lookups with expose, returns the expanded path
|
|
@@ -2447,11 +3106,11 @@ class AXPEntityDetailPopoverComponent {
|
|
|
2447
3106
|
return prop.name;
|
|
2448
3107
|
}
|
|
2449
3108
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2450
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of
|
|
3109
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.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: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "repositionOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2451
3110
|
}
|
|
2452
3111
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
|
|
2453
3112
|
type: Component,
|
|
2454
|
-
args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXLoadingModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of
|
|
3113
|
+
args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXLoadingModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n" }]
|
|
2455
3114
|
}], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: true }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], breadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "breadcrumb", required: false }] }], detailPopover: [{ type: i0.ViewChild, args: ['detailPopover', { isSignal: true }] }] } });
|
|
2456
3115
|
|
|
2457
3116
|
class AXPEntityDetailPopoverService {
|
|
@@ -3265,82 +3924,6 @@ class AXPEntityListViewColumnViewModel {
|
|
|
3265
3924
|
}
|
|
3266
3925
|
}
|
|
3267
3926
|
|
|
3268
|
-
//#endregion
|
|
3269
|
-
//#region ---- Helpers ----
|
|
3270
|
-
function isRelatedEntityStringColumns(columns) {
|
|
3271
|
-
return columns.length === 0 || typeof columns[0] === 'string';
|
|
3272
|
-
}
|
|
3273
|
-
/**
|
|
3274
|
-
* Property names from `AXPRelatedEntity.columns` (no expression evaluation).
|
|
3275
|
-
* Empty array means caller can treat as “no name filter” (show all allowed by entity).
|
|
3276
|
-
*/
|
|
3277
|
-
function getRelatedEntityColumnNames(columns) {
|
|
3278
|
-
if (!columns?.length) {
|
|
3279
|
-
return [];
|
|
3280
|
-
}
|
|
3281
|
-
if (isRelatedEntityStringColumns(columns)) {
|
|
3282
|
-
return [...columns];
|
|
3283
|
-
}
|
|
3284
|
-
return columns.map((c) => c.name);
|
|
3285
|
-
}
|
|
3286
|
-
function isTruthyVisible(value) {
|
|
3287
|
-
if (value === true) {
|
|
3288
|
-
return true;
|
|
3289
|
-
}
|
|
3290
|
-
if (value === false || value === null || value === undefined) {
|
|
3291
|
-
return false;
|
|
3292
|
-
}
|
|
3293
|
-
if (typeof value === 'string') {
|
|
3294
|
-
const t = value.trim().toLowerCase();
|
|
3295
|
-
return t !== '' && t !== 'false' && t !== '0';
|
|
3296
|
-
}
|
|
3297
|
-
if (typeof value === 'number') {
|
|
3298
|
-
return value !== 0 && !Number.isNaN(value);
|
|
3299
|
-
}
|
|
3300
|
-
return Boolean(value);
|
|
3301
|
-
}
|
|
3302
|
-
//#endregion
|
|
3303
|
-
//#region ---- Public API ----
|
|
3304
|
-
/**
|
|
3305
|
-
* Resolves `AXPRelatedEntity.columns` for `entity-list`:
|
|
3306
|
-
* - `string[]` → `includeColumns` only (legacy).
|
|
3307
|
-
* - `AXPEntityTableColumn[]` → evaluates `options.visible` when it is a string (parent detail context),
|
|
3308
|
-
* drops hidden / invisible columns, and returns `relatedTableColumns` + matching `includeColumns`.
|
|
3309
|
-
*/
|
|
3310
|
-
async function resolveRelatedEntityColumns(columns, expressionEvaluator, scope) {
|
|
3311
|
-
if (!columns?.length) {
|
|
3312
|
-
return {};
|
|
3313
|
-
}
|
|
3314
|
-
if (isRelatedEntityStringColumns(columns)) {
|
|
3315
|
-
return { includeColumns: getRelatedEntityColumnNames(columns) };
|
|
3316
|
-
}
|
|
3317
|
-
const relatedTableColumns = [];
|
|
3318
|
-
for (const raw of columns) {
|
|
3319
|
-
const col = cloneDeep(raw);
|
|
3320
|
-
if (col.hidden === true) {
|
|
3321
|
-
continue;
|
|
3322
|
-
}
|
|
3323
|
-
let visible = col.options?.visible;
|
|
3324
|
-
if (visible === undefined) {
|
|
3325
|
-
visible = true;
|
|
3326
|
-
}
|
|
3327
|
-
else if (typeof visible === 'string') {
|
|
3328
|
-
const evaluated = await expressionEvaluator.evaluate(visible, scope);
|
|
3329
|
-
visible = isTruthyVisible(evaluated);
|
|
3330
|
-
}
|
|
3331
|
-
if (visible === false) {
|
|
3332
|
-
continue;
|
|
3333
|
-
}
|
|
3334
|
-
col.options = { ...col.options, visible: true };
|
|
3335
|
-
relatedTableColumns.push(col);
|
|
3336
|
-
}
|
|
3337
|
-
return {
|
|
3338
|
-
relatedTableColumns,
|
|
3339
|
-
includeColumns: relatedTableColumns.map((c) => c.name),
|
|
3340
|
-
};
|
|
3341
|
-
}
|
|
3342
|
-
//#endregion
|
|
3343
|
-
|
|
3344
3927
|
class AXPEntityDetailListViewModel {
|
|
3345
3928
|
constructor(injector, detailEntityConfig, parent) {
|
|
3346
3929
|
this.injector = injector;
|
|
@@ -4151,6 +4734,7 @@ class AXPEntityMasterListViewModel {
|
|
|
4151
4734
|
return this.sortedFields().filter((i) => i.dir).length;
|
|
4152
4735
|
};
|
|
4153
4736
|
this.sortedFields = signal([], ...(ngDevMode ? [{ debugName: "sortedFields" }] : /* istanbul ignore next */ []));
|
|
4737
|
+
//#endregion
|
|
4154
4738
|
this.evaluateExpressions = async (options, data) => {
|
|
4155
4739
|
const scope = this.createExpressionScope(data);
|
|
4156
4740
|
return await this.expressionEvaluator.evaluate(options, scope);
|
|
@@ -4240,7 +4824,6 @@ class AXPEntityMasterListViewModel {
|
|
|
4240
4824
|
width: c.width,
|
|
4241
4825
|
}));
|
|
4242
4826
|
set(newSettings, `list.views.${this.view().name}.columns`, updatedColumns);
|
|
4243
|
-
console.log(newSettings, `list.views.${this.view().name}.columns`);
|
|
4244
4827
|
return newSettings;
|
|
4245
4828
|
});
|
|
4246
4829
|
break;
|
|
@@ -4623,7 +5206,8 @@ class AXPEntityMasterListViewModel {
|
|
|
4623
5206
|
const command = resolvedTriggerName.split('&')[0];
|
|
4624
5207
|
const options = await this.evaluateExpressions(action?.options, data);
|
|
4625
5208
|
const baseData = action?.scope == AXPEntityCommandScope.Selected ? this.selectedItems() : data;
|
|
4626
|
-
|
|
5209
|
+
let commandData = this.mergeViewConditionsForCreate(command, baseData);
|
|
5210
|
+
commandData = this.mergeDefaultCategoryForCreate(command, commandData);
|
|
4627
5211
|
if (this.workflow.exists(command)) {
|
|
4628
5212
|
await this.workflow.execute(command, {
|
|
4629
5213
|
entity: getEntityInfo(this.entityDef).source,
|
|
@@ -4705,6 +5289,38 @@ class AXPEntityMasterListViewModel {
|
|
|
4705
5289
|
}
|
|
4706
5290
|
return baseData;
|
|
4707
5291
|
}
|
|
5292
|
+
//#region ---- Create command defaults from list view ----
|
|
5293
|
+
/**
|
|
5294
|
+
* Seeds create payloads with fixed list-view conditions (e.g. scope=T on the tenant dashboards view)
|
|
5295
|
+
* so new rows match the view context and downstream queries (such as home dashboard) can find them.
|
|
5296
|
+
*/
|
|
5297
|
+
mergeViewConditionsForCreate(command, baseData) {
|
|
5298
|
+
const isCreateCommand = command === 'Entity:Create' || command === 'create-entity';
|
|
5299
|
+
if (!isCreateCommand) {
|
|
5300
|
+
return baseData;
|
|
5301
|
+
}
|
|
5302
|
+
const conditions = this.view()?.conditions ?? [];
|
|
5303
|
+
if (conditions.length === 0) {
|
|
5304
|
+
return baseData;
|
|
5305
|
+
}
|
|
5306
|
+
const seed = {};
|
|
5307
|
+
for (const c of conditions) {
|
|
5308
|
+
if (!c?.name || c.operator?.type !== 'equal' || c.value === undefined) {
|
|
5309
|
+
continue;
|
|
5310
|
+
}
|
|
5311
|
+
seed[c.name] = c.value;
|
|
5312
|
+
}
|
|
5313
|
+
if (Object.keys(seed).length === 0) {
|
|
5314
|
+
return baseData;
|
|
5315
|
+
}
|
|
5316
|
+
if (baseData == null) {
|
|
5317
|
+
return seed;
|
|
5318
|
+
}
|
|
5319
|
+
if (typeof baseData === 'object' && !Array.isArray(baseData)) {
|
|
5320
|
+
return { ...seed, ...baseData };
|
|
5321
|
+
}
|
|
5322
|
+
return baseData;
|
|
5323
|
+
}
|
|
4708
5324
|
async execute(command) {
|
|
4709
5325
|
switch (command?.name) {
|
|
4710
5326
|
case 'navigate':
|
|
@@ -9858,7 +10474,7 @@ class AXPEntityListTableService {
|
|
|
9858
10474
|
animation: true,
|
|
9859
10475
|
},
|
|
9860
10476
|
// 🎪 Events
|
|
9861
|
-
...this.createDefaultEvents(entity, allActions),
|
|
10477
|
+
...this.createDefaultEvents(entity, allActions, options?.excludeProperties),
|
|
9862
10478
|
};
|
|
9863
10479
|
console.log('listOptions', listOptions);
|
|
9864
10480
|
return listOptions;
|
|
@@ -9948,7 +10564,7 @@ class AXPEntityListTableService {
|
|
|
9948
10564
|
/**
|
|
9949
10565
|
* Handle execution of a row command (shared by double-click and command handlers)
|
|
9950
10566
|
*/
|
|
9951
|
-
async handleRowCommand(e, selectedRows, entity, allActions) {
|
|
10567
|
+
async handleRowCommand(e, selectedRows, entity, allActions, relatedListExcludeProperties) {
|
|
9952
10568
|
const data = e.data;
|
|
9953
10569
|
const commandName = e.name;
|
|
9954
10570
|
const action = allActions.find((c) => {
|
|
@@ -9959,7 +10575,8 @@ class AXPEntityListTableService {
|
|
|
9959
10575
|
c.scope == AXPEntityCommandScope.TypeLevel));
|
|
9960
10576
|
});
|
|
9961
10577
|
const command = commandName.split('&')[0];
|
|
9962
|
-
const
|
|
10578
|
+
const evaluatedOptions = await this.evaluateExpressions(action?.options, data);
|
|
10579
|
+
const options = this.mergeRelatedListFormOptions(command, evaluatedOptions, relatedListExcludeProperties);
|
|
9963
10580
|
if (this.commandService.exists(command)) {
|
|
9964
10581
|
await this.commandService.execute(command, {
|
|
9965
10582
|
__context__: {
|
|
@@ -9995,10 +10612,25 @@ class AXPEntityListTableService {
|
|
|
9995
10612
|
});
|
|
9996
10613
|
}
|
|
9997
10614
|
}
|
|
10615
|
+
/**
|
|
10616
|
+
* When a related entity list declares `excludeProperties`, row commands bypass the details page
|
|
10617
|
+
* `execute()` merge — apply the same exclusions for embedded create/update commands.
|
|
10618
|
+
*/
|
|
10619
|
+
mergeRelatedListFormOptions(command, evaluatedOptions, relatedListExcludeProperties) {
|
|
10620
|
+
const exclusions = relatedListExcludeProperties?.filter(Boolean);
|
|
10621
|
+
if (!exclusions?.length ||
|
|
10622
|
+
(command !== 'Entity:Create' && command !== 'Entity:Update')) {
|
|
10623
|
+
return evaluatedOptions;
|
|
10624
|
+
}
|
|
10625
|
+
return {
|
|
10626
|
+
...evaluatedOptions,
|
|
10627
|
+
excludeProperties: exclusions,
|
|
10628
|
+
};
|
|
10629
|
+
}
|
|
9998
10630
|
/**
|
|
9999
10631
|
* Create default events
|
|
10000
10632
|
*/
|
|
10001
|
-
createDefaultEvents(entity, allActions) {
|
|
10633
|
+
createDefaultEvents(entity, allActions, relatedListExcludeProperties) {
|
|
10002
10634
|
return {
|
|
10003
10635
|
onRowClick: (row) => {
|
|
10004
10636
|
console.log('Entity List - Row clicked:', row);
|
|
@@ -10017,13 +10649,13 @@ class AXPEntityListTableService {
|
|
|
10017
10649
|
name: defaultAction.name,
|
|
10018
10650
|
data: e.data,
|
|
10019
10651
|
};
|
|
10020
|
-
this.handleRowCommand(d, undefined, entity, allActions);
|
|
10652
|
+
this.handleRowCommand(d, undefined, entity, allActions, relatedListExcludeProperties);
|
|
10021
10653
|
},
|
|
10022
10654
|
onSelectionChange: (selectedRows) => {
|
|
10023
10655
|
console.log('Entity List - Selection changed:', selectedRows);
|
|
10024
10656
|
},
|
|
10025
10657
|
onRowCommand: async (e, selectedRows) => {
|
|
10026
|
-
await this.handleRowCommand(e, selectedRows, entity, allActions);
|
|
10658
|
+
await this.handleRowCommand(e, selectedRows, entity, allActions, relatedListExcludeProperties);
|
|
10027
10659
|
},
|
|
10028
10660
|
};
|
|
10029
10661
|
}
|
|
@@ -10330,6 +10962,42 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10330
10962
|
}));
|
|
10331
10963
|
return actions;
|
|
10332
10964
|
}, ...(ngDevMode ? [{ debugName: "secondaryActions" }] : /* istanbul ignore next */ []));
|
|
10965
|
+
/** Keeps related-list filters in sync with dialog context for create wizards (main id appears after first-step save). */
|
|
10966
|
+
this.#relatedFilterSyncEffect = effect(() => {
|
|
10967
|
+
const opts = this.options();
|
|
10968
|
+
if (!opts['syncRelatedListFiltersFromDialogContext']) {
|
|
10969
|
+
return;
|
|
10970
|
+
}
|
|
10971
|
+
const specs = opts['relatedFilterConditionSpecs'];
|
|
10972
|
+
if (!specs?.length) {
|
|
10973
|
+
return;
|
|
10974
|
+
}
|
|
10975
|
+
this.contextService.data();
|
|
10976
|
+
if (!this.fullPath()) {
|
|
10977
|
+
return;
|
|
10978
|
+
}
|
|
10979
|
+
untracked(() => void this.applyRelatedFiltersFromContextAndDatasource(specs));
|
|
10980
|
+
}, ...(ngDevMode ? [{ debugName: "#relatedFilterSyncEffect" }] : /* istanbul ignore next */ []));
|
|
10981
|
+
/** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
|
|
10982
|
+
this.#patchDataListRefreshEffect = effect(() => {
|
|
10983
|
+
const inst = this.listWidget()?.instance;
|
|
10984
|
+
const opts = this.options();
|
|
10985
|
+
if (!inst?.refresh || inst.__axpEntityListRefreshPatched || !opts['syncRelatedListFiltersFromDialogContext']) {
|
|
10986
|
+
return;
|
|
10987
|
+
}
|
|
10988
|
+
inst.__axpEntityListRefreshPatched = true;
|
|
10989
|
+
const originalRefresh = inst.refresh.bind(inst);
|
|
10990
|
+
inst.refresh = () => {
|
|
10991
|
+
void (async () => {
|
|
10992
|
+
const o = this.options();
|
|
10993
|
+
const specs = o['relatedFilterConditionSpecs'];
|
|
10994
|
+
if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
|
|
10995
|
+
await this.applyRelatedFiltersFromContextAndDatasource(specs);
|
|
10996
|
+
}
|
|
10997
|
+
originalRefresh();
|
|
10998
|
+
})();
|
|
10999
|
+
};
|
|
11000
|
+
}, ...(ngDevMode ? [{ debugName: "#patchDataListRefreshEffect" }] : /* istanbul ignore next */ []));
|
|
10333
11001
|
//#region ---- Query Change Handler ----
|
|
10334
11002
|
this.queries = undefined;
|
|
10335
11003
|
this.#effect = effect(() => {
|
|
@@ -10380,7 +11048,32 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10380
11048
|
const commandData = action?.scope == AXPEntityCommandScope.Selected
|
|
10381
11049
|
? this.selectedItems()
|
|
10382
11050
|
: action?.options?.['process']?.data || null;
|
|
10383
|
-
|
|
11051
|
+
let options = await this.evaluateToolbarExpressions(action?.options, commandData);
|
|
11052
|
+
const listOpts = this.options();
|
|
11053
|
+
const foreignKeyFieldName = listOpts['foreignKeyField'];
|
|
11054
|
+
const createExcludes = listOpts['nestedCreateHiddenProperties'] ?? [];
|
|
11055
|
+
if (command === 'Entity:Create' && foreignKeyFieldName) {
|
|
11056
|
+
const root = this.contextService.snapshot();
|
|
11057
|
+
const idVal = get(root, 'id');
|
|
11058
|
+
options = merge({}, options);
|
|
11059
|
+
options['process'] = merge({}, options['process'], {
|
|
11060
|
+
data: merge({}, options['process']?.data ?? {}, {
|
|
11061
|
+
...(idVal !== undefined && idVal !== null && idVal !== '' ? { [foreignKeyFieldName]: idVal } : {}),
|
|
11062
|
+
}),
|
|
11063
|
+
});
|
|
11064
|
+
}
|
|
11065
|
+
if (command === 'Entity:Create' && createExcludes.length) {
|
|
11066
|
+
const existing = (options['excludeProperties'] ?? []).slice();
|
|
11067
|
+
options = merge({}, options);
|
|
11068
|
+
options['excludeProperties'] = [...new Set([...createExcludes, ...existing])];
|
|
11069
|
+
}
|
|
11070
|
+
const relatedExcludes = listOpts['excludeProperties'];
|
|
11071
|
+
const exclusionsFromList = relatedExcludes?.filter(Boolean) ?? [];
|
|
11072
|
+
if (exclusionsFromList.length && (command === 'Entity:Create' || command === 'Entity:Update')) {
|
|
11073
|
+
const existing = (options['excludeProperties'] ?? []).slice();
|
|
11074
|
+
options = merge({}, options);
|
|
11075
|
+
options['excludeProperties'] = [...new Set([...existing, ...exclusionsFromList])];
|
|
11076
|
+
}
|
|
10384
11077
|
if (this.commandService.exists(command)) {
|
|
10385
11078
|
await this.commandService.execute(command, {
|
|
10386
11079
|
__context__: {
|
|
@@ -10420,13 +11113,88 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10420
11113
|
if (!opts) {
|
|
10421
11114
|
return {};
|
|
10422
11115
|
}
|
|
11116
|
+
const root = this.contextService.snapshot();
|
|
10423
11117
|
const scope = {
|
|
10424
11118
|
context: {
|
|
10425
|
-
eval: (path) => get(expressionData, path),
|
|
11119
|
+
eval: (path) => get(root, path) ?? get(expressionData, path),
|
|
10426
11120
|
},
|
|
10427
11121
|
};
|
|
10428
11122
|
return (await this.expressionEvaluator.evaluate(opts, scope));
|
|
10429
11123
|
}
|
|
11124
|
+
/**
|
|
11125
|
+
* Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
|
|
11126
|
+
*/
|
|
11127
|
+
async applyRelatedFiltersFromContext(specs) {
|
|
11128
|
+
const root = this.contextService.snapshot();
|
|
11129
|
+
const scope = {
|
|
11130
|
+
context: {
|
|
11131
|
+
eval: (path) => get(root, path),
|
|
11132
|
+
},
|
|
11133
|
+
};
|
|
11134
|
+
const filters = await Promise.all(specs.map(async (c) => ({
|
|
11135
|
+
field: c.name,
|
|
11136
|
+
operator: c.operator,
|
|
11137
|
+
value: await this.expressionEvaluator.evaluate(c.value, scope),
|
|
11138
|
+
hidden: true,
|
|
11139
|
+
})));
|
|
11140
|
+
if (!this.fullPath()) {
|
|
11141
|
+
return;
|
|
11142
|
+
}
|
|
11143
|
+
const current = this.getValue();
|
|
11144
|
+
if (isEqual$1(current?.toolbar?.filters, filters)) {
|
|
11145
|
+
return;
|
|
11146
|
+
}
|
|
11147
|
+
this.setValue({
|
|
11148
|
+
...current,
|
|
11149
|
+
toolbar: { ...(current?.toolbar ?? {}), filters },
|
|
11150
|
+
});
|
|
11151
|
+
}
|
|
11152
|
+
/** Keeps related-list filters in sync with dialog context for create wizards (main id appears after first-step save). */
|
|
11153
|
+
#relatedFilterSyncEffect;
|
|
11154
|
+
/**
|
|
11155
|
+
* Pushes current toolbar filters to the embedded list data source when both exist.
|
|
11156
|
+
* Returns false while data-list is still mounting (see deferred listNode.set in ngOnInit).
|
|
11157
|
+
*/
|
|
11158
|
+
pushToolbarFiltersToDataSource() {
|
|
11159
|
+
const listInstance = this.listWidget()?.instance;
|
|
11160
|
+
const dataSource = listInstance?.options?.()?.['dataSource'];
|
|
11161
|
+
const toolbar = this.getValue()?.toolbar;
|
|
11162
|
+
if (!dataSource?.filter || !toolbar?.filters?.length) {
|
|
11163
|
+
return false;
|
|
11164
|
+
}
|
|
11165
|
+
dataSource.filter({ filters: toolbar.filters });
|
|
11166
|
+
return true;
|
|
11167
|
+
}
|
|
11168
|
+
/**
|
|
11169
|
+
* Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
|
|
11170
|
+
*/
|
|
11171
|
+
async applyRelatedFiltersFromContextAndDatasource(specs) {
|
|
11172
|
+
await this.applyRelatedFiltersFromContext(specs);
|
|
11173
|
+
if (this.pushToolbarFiltersToDataSource()) {
|
|
11174
|
+
return;
|
|
11175
|
+
}
|
|
11176
|
+
const opts = this.options();
|
|
11177
|
+
if (!opts['syncRelatedListFiltersFromDialogContext']) {
|
|
11178
|
+
return;
|
|
11179
|
+
}
|
|
11180
|
+
/** Data-list is created in ngOnInit via deferred listNode.set; retry briefly until instance exposes dataSource. */
|
|
11181
|
+
const maxAttempts = 40;
|
|
11182
|
+
const delayMs = 50;
|
|
11183
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
11184
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
11185
|
+
if (this.pushToolbarFiltersToDataSource()) {
|
|
11186
|
+
return;
|
|
11187
|
+
}
|
|
11188
|
+
}
|
|
11189
|
+
}
|
|
11190
|
+
/**
|
|
11191
|
+
* Refreshes the embedded data list (toolbar / workflow). In wizard mode, `refresh` is patched to re-apply scoped filters first.
|
|
11192
|
+
*/
|
|
11193
|
+
refreshGridWithParentScopedFilters() {
|
|
11194
|
+
this.listWidget()?.instance?.call('refresh');
|
|
11195
|
+
}
|
|
11196
|
+
/** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
|
|
11197
|
+
#patchDataListRefreshEffect;
|
|
10430
11198
|
#effect;
|
|
10431
11199
|
/**
|
|
10432
11200
|
* Validates that all required dependencies are available
|
|
@@ -10529,6 +11297,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10529
11297
|
includeColumns: this.includeColumns(),
|
|
10530
11298
|
relatedTableColumns: this.relatedTableColumns(),
|
|
10531
11299
|
customFilterDefinitions: this.customFilterDefinitions(),
|
|
11300
|
+
excludeProperties: this.options()['excludeProperties'],
|
|
10532
11301
|
};
|
|
10533
11302
|
const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options, this.allActions());
|
|
10534
11303
|
const toolbarOptions = await this.entityListToolbarService.convertEntityToolbarOptions(resolvedEntity, options);
|
|
@@ -10552,6 +11321,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10552
11321
|
columns: this.getValue()?.toolbar?.columns,
|
|
10553
11322
|
},
|
|
10554
11323
|
});
|
|
11324
|
+
queueMicrotask(() => {
|
|
11325
|
+
void this.pushToolbarFiltersToDataSource();
|
|
11326
|
+
});
|
|
10555
11327
|
}, 100);
|
|
10556
11328
|
}
|
|
10557
11329
|
async ngAfterViewInit() {
|
|
@@ -10560,7 +11332,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10560
11332
|
.pipe(takeUntil(this.destroyed))
|
|
10561
11333
|
.subscribe((event) => {
|
|
10562
11334
|
if (event.payload.entity == this.entitySource()) {
|
|
10563
|
-
this.
|
|
11335
|
+
this.refreshGridWithParentScopedFilters();
|
|
10564
11336
|
}
|
|
10565
11337
|
});
|
|
10566
11338
|
this.eventService
|
|
@@ -10568,7 +11340,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10568
11340
|
.pipe(takeUntil(this.destroyed))
|
|
10569
11341
|
.subscribe((e) => {
|
|
10570
11342
|
if (e.data.name == `${this.entity()?.module}.${this.entity()?.name}`) {
|
|
10571
|
-
this.
|
|
11343
|
+
this.refreshGridWithParentScopedFilters();
|
|
10572
11344
|
}
|
|
10573
11345
|
});
|
|
10574
11346
|
const listWidget = (await this.layoutService.waitForWidget(`${this.entitySource()}-tab-list_table`, 500));
|
|
@@ -10846,7 +11618,7 @@ const AXPEntityListWidget = {
|
|
|
10846
11618
|
type: 'view',
|
|
10847
11619
|
categories: [],
|
|
10848
11620
|
groups: [AXPWidgetGroupEnum.EntityWidget],
|
|
10849
|
-
icon: 'fa-
|
|
11621
|
+
icon: 'fa-light fa-square',
|
|
10850
11622
|
properties: [AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY],
|
|
10851
11623
|
components: {
|
|
10852
11624
|
view: {
|
|
@@ -12145,7 +12917,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
12145
12917
|
const prevValue = this.previousValue();
|
|
12146
12918
|
const isInitialized = this.initialized();
|
|
12147
12919
|
// Check if value has actually changed
|
|
12148
|
-
const valueChanged = !isEqual(currentValue, prevValue);
|
|
12920
|
+
const valueChanged = !isEqual$1(currentValue, prevValue);
|
|
12149
12921
|
// Determine if we should update:
|
|
12150
12922
|
// 1. First initialization with a value
|
|
12151
12923
|
// 2. Value becomes empty after initialization
|
|
@@ -12296,67 +13068,53 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
12296
13068
|
}
|
|
12297
13069
|
}
|
|
12298
13070
|
/**
|
|
12299
|
-
*
|
|
13071
|
+
* Writes expose targets into context using per-path updates.
|
|
13072
|
+
* Avoids `contextService.patch()` with nested objects: patch shallow-merges top-level keys only,
|
|
13073
|
+
* so e.g. `{ person: { educationLevel: { id, title } } }` would replace the entire `person`
|
|
13074
|
+
* object and drop sibling fields like `person.educationLevelId`, causing a value/effect loop.
|
|
12300
13075
|
*/
|
|
12301
13076
|
expoesItems() {
|
|
12302
13077
|
const exposeValue = castArray(this.expose());
|
|
12303
|
-
const itemToExpose = {};
|
|
12304
13078
|
const items = this.selectedItems();
|
|
12305
13079
|
const isEmpty = !items || items.length === 0;
|
|
12306
|
-
// If items are empty, group expose configs by parent path and set parent to null
|
|
12307
13080
|
if (isEmpty) {
|
|
12308
13081
|
const parentPaths = new Set();
|
|
12309
13082
|
exposeValue.forEach((i) => {
|
|
12310
13083
|
if (typeof i === 'string') {
|
|
12311
|
-
// For string expose, the path itself is the target
|
|
12312
13084
|
const pathParts = i.split('.');
|
|
12313
13085
|
if (pathParts.length > 1) {
|
|
12314
|
-
|
|
12315
|
-
const parentPath = pathParts.slice(0, -1).join('.');
|
|
12316
|
-
parentPaths.add(parentPath);
|
|
13086
|
+
parentPaths.add(pathParts.slice(0, -1).join('.'));
|
|
12317
13087
|
}
|
|
12318
13088
|
else {
|
|
12319
|
-
|
|
12320
|
-
setSmart(itemToExpose, i, null);
|
|
13089
|
+
this.contextService.update(i, null);
|
|
12321
13090
|
}
|
|
12322
13091
|
}
|
|
12323
13092
|
else {
|
|
12324
|
-
// For object expose, extract parent path from target
|
|
12325
13093
|
const pathParts = i.target.split('.');
|
|
12326
13094
|
if (pathParts.length > 1) {
|
|
12327
|
-
|
|
12328
|
-
const parentPath = pathParts.slice(0, -1).join('.');
|
|
12329
|
-
parentPaths.add(parentPath);
|
|
13095
|
+
parentPaths.add(pathParts.slice(0, -1).join('.'));
|
|
12330
13096
|
}
|
|
12331
13097
|
else {
|
|
12332
|
-
|
|
12333
|
-
setSmart(itemToExpose, i.target, null);
|
|
13098
|
+
this.contextService.update(i.target, null);
|
|
12334
13099
|
}
|
|
12335
13100
|
}
|
|
12336
13101
|
});
|
|
12337
|
-
// Set all parent paths to null
|
|
12338
13102
|
parentPaths.forEach((parentPath) => {
|
|
12339
|
-
|
|
12340
|
-
});
|
|
12341
|
-
}
|
|
12342
|
-
else {
|
|
12343
|
-
// Normal processing when items exist
|
|
12344
|
-
exposeValue.forEach((i) => {
|
|
12345
|
-
if (typeof i == 'string') {
|
|
12346
|
-
const values = items.map((item) => set({}, i, get(item, i)));
|
|
12347
|
-
setSmart(itemToExpose, i, this.singleOrMultiple(values));
|
|
12348
|
-
}
|
|
12349
|
-
else {
|
|
12350
|
-
// extract data from item by source path and set context by target path
|
|
12351
|
-
const values = this.multiple()
|
|
12352
|
-
? items.map((item) => set({}, i.source, get(item, i.source)))
|
|
12353
|
-
: items.map((item) => get(item, i.source));
|
|
12354
|
-
setSmart(itemToExpose, i.target, this.singleOrMultiple(values));
|
|
12355
|
-
}
|
|
13103
|
+
this.contextService.update(parentPath, null);
|
|
12356
13104
|
});
|
|
13105
|
+
return;
|
|
12357
13106
|
}
|
|
12358
|
-
|
|
12359
|
-
|
|
13107
|
+
exposeValue.forEach((i) => {
|
|
13108
|
+
if (typeof i === 'string') {
|
|
13109
|
+
const values = items.map((item) => set({}, i, get(item, i)));
|
|
13110
|
+
this.contextService.update(i, this.singleOrMultiple(values));
|
|
13111
|
+
}
|
|
13112
|
+
else {
|
|
13113
|
+
const values = this.multiple()
|
|
13114
|
+
? items.map((item) => set({}, i.source, get(item, i.source)))
|
|
13115
|
+
: items.map((item) => get(item, i.source));
|
|
13116
|
+
this.contextService.update(i.target, this.singleOrMultiple(values));
|
|
13117
|
+
}
|
|
12360
13118
|
});
|
|
12361
13119
|
}
|
|
12362
13120
|
singleOrMultiple(values) {
|
|
@@ -12517,36 +13275,47 @@ var lookupWidgetEdit_component = /*#__PURE__*/Object.freeze({
|
|
|
12517
13275
|
|
|
12518
13276
|
class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
12519
13277
|
constructor() {
|
|
12520
|
-
super(
|
|
12521
|
-
//#region ----
|
|
13278
|
+
super();
|
|
13279
|
+
//#region ---- Dependencies ----
|
|
12522
13280
|
this.entityDetailPopoverService = inject(AXPEntityDetailPopoverService);
|
|
12523
|
-
this.
|
|
13281
|
+
this.translation = inject(AXTranslationService);
|
|
12524
13282
|
this.formatService = inject(AXFormatService);
|
|
13283
|
+
this.entityService = inject(AXPEntityService);
|
|
13284
|
+
this.queryExecutor = inject(AXPQueryExecutor);
|
|
12525
13285
|
//#endregion
|
|
12526
|
-
//#region ----
|
|
13286
|
+
//#region ---- View Children ----
|
|
12527
13287
|
this.moreButton = viewChild('moreButton', ...(ngDevMode ? [{ debugName: "moreButton" }] : /* istanbul ignore next */ []));
|
|
13288
|
+
this.lazyTrigger = viewChild('lazyTrigger', ...(ngDevMode ? [{ debugName: "lazyTrigger" }] : /* istanbul ignore next */ []));
|
|
12528
13289
|
this.morePopover = viewChild('morePopover', ...(ngDevMode ? [{ debugName: "morePopover" }] : /* istanbul ignore next */ []));
|
|
12529
13290
|
//#endregion
|
|
12530
|
-
//#region ----
|
|
13291
|
+
//#region ---- Properties ----
|
|
12531
13292
|
this.host = inject(ElementRef);
|
|
12532
13293
|
this.valueField = this.options['valueField'] ?? 'id';
|
|
12533
13294
|
this.textField = this.options['textField'] ?? 'title';
|
|
12534
|
-
this.entity = this.options['entity'] ?? '
|
|
13295
|
+
this.entity = this.options['entity'] ?? '';
|
|
12535
13296
|
this.columnName = this.options['columnName'] ?? 'title';
|
|
12536
13297
|
this.maxVisible = this.options['maxVisible'] ?? 2;
|
|
12537
13298
|
this.displayFormat = computed(() => {
|
|
12538
13299
|
const template = this.options['displayFormat'];
|
|
12539
13300
|
return template ? template.replace(/\{/g, '{{').replace(/\}/g, '}}') : undefined;
|
|
12540
13301
|
}, ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
|
|
12541
|
-
this.displayField = computed(() => {
|
|
12542
|
-
|
|
12543
|
-
|
|
13302
|
+
this.displayField = computed(() => this.textField ?? 'title', ...(ngDevMode ? [{ debugName: "displayField" }] : /* istanbul ignore next */ []));
|
|
13303
|
+
this.columnResolve = computed(() => this.options['columnResolve'], ...(ngDevMode ? [{ debugName: "columnResolve" }] : /* istanbul ignore next */ []));
|
|
13304
|
+
this.resolveStrategy = computed(() => {
|
|
13305
|
+
return this.columnResolve()?.strategy ?? 'hydrated';
|
|
13306
|
+
}, ...(ngDevMode ? [{ debugName: "resolveStrategy" }] : /* istanbul ignore next */ []));
|
|
13307
|
+
this.isHydratedStrategy = computed(() => this.resolveStrategy() === 'hydrated', ...(ngDevMode ? [{ debugName: "isHydratedStrategy" }] : /* istanbul ignore next */ []));
|
|
12544
13308
|
//#endregion
|
|
12545
|
-
//#region ----
|
|
13309
|
+
//#region ---- Signals ----
|
|
12546
13310
|
this.isMorePopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isMorePopoverOpen" }] : /* istanbul ignore next */ []));
|
|
12547
13311
|
this.selectedItemIndex = signal(-1, ...(ngDevMode ? [{ debugName: "selectedItemIndex" }] : /* istanbul ignore next */ []));
|
|
13312
|
+
this.resolvedPopoverItems = signal([], ...(ngDevMode ? [{ debugName: "resolvedPopoverItems" }] : /* istanbul ignore next */ []));
|
|
13313
|
+
this.resolveStatus = signal('idle', ...(ngDevMode ? [{ debugName: "resolveStatus" }] : /* istanbul ignore next */ []));
|
|
13314
|
+
this.resolveError = signal(null, ...(ngDevMode ? [{ debugName: "resolveError" }] : /* istanbul ignore next */ []));
|
|
13315
|
+
this.summaryLabel = signal('', ...(ngDevMode ? [{ debugName: "summaryLabel" }] : /* istanbul ignore next */ []));
|
|
13316
|
+
this.popoverHeader = signal('', ...(ngDevMode ? [{ debugName: "popoverHeader" }] : /* istanbul ignore next */ []));
|
|
12548
13317
|
//#endregion
|
|
12549
|
-
//#region ----
|
|
13318
|
+
//#region ---- Computed ----
|
|
12550
13319
|
this.displayItems = computed(() => isNil(this.rawValue)
|
|
12551
13320
|
? []
|
|
12552
13321
|
: castArray(this.rawValue)
|
|
@@ -12557,34 +13326,127 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
12557
13326
|
const items = this.allItems();
|
|
12558
13327
|
return items.slice(0, this.maxVisible);
|
|
12559
13328
|
}, ...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
|
|
12560
|
-
this.hasMoreItems = computed(() => {
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
13329
|
+
this.hasMoreItems = computed(() => this.allItems().length > this.maxVisible, ...(ngDevMode ? [{ debugName: "hasMoreItems" }] : /* istanbul ignore next */ []));
|
|
13330
|
+
this.idsFromRow = computed(() => {
|
|
13331
|
+
const cr = this.columnResolve();
|
|
13332
|
+
const path = cr?.idsPath;
|
|
13333
|
+
const source = path ? get(this.rowData, path) : this.rawValue;
|
|
13334
|
+
if (isNil(source)) {
|
|
13335
|
+
return [];
|
|
13336
|
+
}
|
|
13337
|
+
return castArray(source)
|
|
13338
|
+
.map((x) => (typeof x === 'object' && x != null ? get(x, this.valueField) : x))
|
|
13339
|
+
.filter((id) => id != null && id !== '');
|
|
13340
|
+
}, ...(ngDevMode ? [{ debugName: "idsFromRow" }] : /* istanbul ignore next */ []));
|
|
13341
|
+
this.displayCount = computed(() => {
|
|
13342
|
+
const cr = this.columnResolve();
|
|
13343
|
+
const countPath = cr?.countFieldPath;
|
|
13344
|
+
if (countPath) {
|
|
13345
|
+
const v = get(this.rowData, countPath);
|
|
13346
|
+
if (typeof v === 'number' && !Number.isNaN(v)) {
|
|
13347
|
+
return v;
|
|
13348
|
+
}
|
|
13349
|
+
if (typeof v === 'string' && v !== '') {
|
|
13350
|
+
const n = Number(v);
|
|
13351
|
+
return Number.isNaN(n) ? 0 : n;
|
|
13352
|
+
}
|
|
13353
|
+
return 0;
|
|
13354
|
+
}
|
|
13355
|
+
if (this.resolveStrategy() === 'idsWithCount') {
|
|
13356
|
+
return this.idsFromRow().length;
|
|
13357
|
+
}
|
|
13358
|
+
if (this.resolveStrategy() === 'countOnly') {
|
|
13359
|
+
return 0;
|
|
13360
|
+
}
|
|
13361
|
+
return 0;
|
|
13362
|
+
}, ...(ngDevMode ? [{ debugName: "displayCount" }] : /* istanbul ignore next */ []));
|
|
13363
|
+
this.popoverListItems = computed(() => {
|
|
13364
|
+
if (this.isHydratedStrategy()) {
|
|
13365
|
+
return this.allItems();
|
|
13366
|
+
}
|
|
13367
|
+
return this.resolvedPopoverItems();
|
|
13368
|
+
}, ...(ngDevMode ? [{ debugName: "popoverListItems" }] : /* istanbul ignore next */ []));
|
|
13369
|
+
//#endregion
|
|
13370
|
+
//#region ---- Constructor effects ----
|
|
13371
|
+
/** Avoid resetting lazy cache on every CD when `rowData` is a new object reference for the same row. */
|
|
13372
|
+
this.previousLazyRowId = undefined;
|
|
13373
|
+
effect(() => {
|
|
13374
|
+
const id = this.rowData?.['id'];
|
|
13375
|
+
const key = id != null && id !== '' ? String(id) : undefined;
|
|
13376
|
+
if (key === this.previousLazyRowId) {
|
|
13377
|
+
return;
|
|
13378
|
+
}
|
|
13379
|
+
this.previousLazyRowId = key;
|
|
13380
|
+
this.resetLazyState();
|
|
13381
|
+
});
|
|
13382
|
+
effect(() => {
|
|
13383
|
+
if (!this.isHydratedStrategy()) {
|
|
13384
|
+
const count = this.displayCount();
|
|
13385
|
+
void this.refreshSummaryLabel(count);
|
|
13386
|
+
}
|
|
13387
|
+
});
|
|
13388
|
+
effect(() => {
|
|
13389
|
+
const hydrated = this.isHydratedStrategy();
|
|
13390
|
+
const count = hydrated
|
|
13391
|
+
? this.allItems().length
|
|
13392
|
+
: this.resolveStatus() === 'ready'
|
|
13393
|
+
? this.resolvedPopoverItems().length
|
|
13394
|
+
: this.displayCount();
|
|
13395
|
+
void this.refreshPopoverHeader(count);
|
|
13396
|
+
});
|
|
12566
13397
|
}
|
|
12567
13398
|
//#endregion
|
|
12568
|
-
//#region ----
|
|
13399
|
+
//#region ---- Public methods ----
|
|
12569
13400
|
showMoreItems() {
|
|
12570
13401
|
this.entityDetailPopoverService.hide();
|
|
12571
|
-
this.
|
|
13402
|
+
this.openPopoverFromRef(this.moreButton());
|
|
13403
|
+
}
|
|
13404
|
+
openLazyPopover() {
|
|
13405
|
+
this.entityDetailPopoverService.hide();
|
|
13406
|
+
this.openPopoverFromRef(this.lazyTrigger());
|
|
13407
|
+
if (!this.isHydratedStrategy()) {
|
|
13408
|
+
void this.ensureLazyItemsLoaded();
|
|
13409
|
+
}
|
|
12572
13410
|
}
|
|
12573
|
-
|
|
12574
|
-
this.
|
|
13411
|
+
onPopoverOpenChange(event) {
|
|
13412
|
+
const open = this.coercePopoverOpenEvent(event);
|
|
13413
|
+
this.isMorePopoverOpen.set(open);
|
|
13414
|
+
if (open && !this.isHydratedStrategy()) {
|
|
13415
|
+
void this.ensureLazyItemsLoaded();
|
|
13416
|
+
}
|
|
12575
13417
|
}
|
|
12576
13418
|
async showItemDetail(item, index) {
|
|
12577
13419
|
if (this.options['disableDetailPopover']) {
|
|
12578
13420
|
return;
|
|
12579
13421
|
}
|
|
12580
|
-
const columnData = this.rowData[this.columnName];
|
|
12581
|
-
const id = Array.isArray(columnData) ? columnData[index] : columnData;
|
|
12582
13422
|
this.selectedItemIndex.set(index);
|
|
12583
13423
|
this.closeMorePopover();
|
|
12584
|
-
//
|
|
13424
|
+
// Prefer the row's FK / stored value (columnName) first. List columns often use options.dataPath so
|
|
13425
|
+
// rawValue (and thus `item`) is only display text (e.g. manager.person.fullName) while managerId holds the real id.
|
|
13426
|
+
const columnData = this.rowData?.[this.columnName];
|
|
13427
|
+
let id;
|
|
13428
|
+
if (Array.isArray(columnData)) {
|
|
13429
|
+
const cell = columnData[index];
|
|
13430
|
+
id =
|
|
13431
|
+
typeof cell === 'object' && cell != null
|
|
13432
|
+
? get(cell, this.valueField)
|
|
13433
|
+
: cell;
|
|
13434
|
+
}
|
|
13435
|
+
else if (!isNil(columnData) && columnData !== '') {
|
|
13436
|
+
id =
|
|
13437
|
+
typeof columnData === 'object' && columnData != null
|
|
13438
|
+
? get(columnData, this.valueField)
|
|
13439
|
+
: columnData;
|
|
13440
|
+
}
|
|
13441
|
+
if (isNil(id) || id === '') {
|
|
13442
|
+
id = get(item, this.valueField);
|
|
13443
|
+
}
|
|
13444
|
+
if (isNil(id) || id === '') {
|
|
13445
|
+
return;
|
|
13446
|
+
}
|
|
12585
13447
|
await this.entityDetailPopoverService.show(this.host, {
|
|
12586
13448
|
entity: this.entity,
|
|
12587
|
-
id
|
|
13449
|
+
id,
|
|
12588
13450
|
textField: this.textField,
|
|
12589
13451
|
valueField: this.valueField,
|
|
12590
13452
|
item,
|
|
@@ -12596,36 +13458,25 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
12596
13458
|
}
|
|
12597
13459
|
const items = this.allItems();
|
|
12598
13460
|
if (index < items.length) {
|
|
12599
|
-
|
|
12600
|
-
this.showItemDetail(item, index);
|
|
13461
|
+
this.showItemDetail(items[index], index);
|
|
12601
13462
|
}
|
|
12602
13463
|
}
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
if (this.morePopover() && this.moreButton()) {
|
|
12607
|
-
this.morePopover().target = this.moreButton().nativeElement;
|
|
12608
|
-
this.morePopover().open();
|
|
12609
|
-
this.isMorePopoverOpen.set(true);
|
|
13464
|
+
handleResolvedItemClick(index) {
|
|
13465
|
+
if (this.options['disableDetailPopover']) {
|
|
13466
|
+
return;
|
|
12610
13467
|
}
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
this.morePopover().close();
|
|
12615
|
-
this.isMorePopoverOpen.set(false);
|
|
13468
|
+
const items = this.resolvedPopoverItems();
|
|
13469
|
+
if (index < items.length) {
|
|
13470
|
+
this.showItemDetail(items[index], index);
|
|
12616
13471
|
}
|
|
12617
13472
|
}
|
|
12618
|
-
|
|
12619
|
-
if (
|
|
12620
|
-
|
|
13473
|
+
handlePopoverItemClick(index) {
|
|
13474
|
+
if (this.isHydratedStrategy()) {
|
|
13475
|
+
this.handleItemClick(index);
|
|
12621
13476
|
}
|
|
12622
|
-
|
|
12623
|
-
|
|
13477
|
+
else {
|
|
13478
|
+
this.handleResolvedItemClick(index);
|
|
12624
13479
|
}
|
|
12625
|
-
return {
|
|
12626
|
-
[this.valueField]: item,
|
|
12627
|
-
[this.textField]: item,
|
|
12628
|
-
};
|
|
12629
13480
|
}
|
|
12630
13481
|
getDisplayRaw(item) {
|
|
12631
13482
|
if (isNil(item)) {
|
|
@@ -12643,6 +13494,178 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
12643
13494
|
}
|
|
12644
13495
|
return resolved ?? '';
|
|
12645
13496
|
}
|
|
13497
|
+
//#endregion
|
|
13498
|
+
//#region ---- Private methods ----
|
|
13499
|
+
async refreshSummaryLabel(count) {
|
|
13500
|
+
const text = await this.translation.translateAsync('@general:widgets.lookup.column.items-count', {
|
|
13501
|
+
params: { count: String(count) },
|
|
13502
|
+
});
|
|
13503
|
+
this.summaryLabel.set(text);
|
|
13504
|
+
}
|
|
13505
|
+
async refreshPopoverHeader(count) {
|
|
13506
|
+
const text = await this.translation.translateAsync('@general:widgets.lookup.column.popover-title', {
|
|
13507
|
+
params: { count: String(count) },
|
|
13508
|
+
});
|
|
13509
|
+
this.popoverHeader.set(text);
|
|
13510
|
+
}
|
|
13511
|
+
resetLazyState() {
|
|
13512
|
+
this.resolvedPopoverItems.set([]);
|
|
13513
|
+
this.resolveStatus.set('idle');
|
|
13514
|
+
this.resolveError.set(null);
|
|
13515
|
+
}
|
|
13516
|
+
openPopoverFromRef(ref) {
|
|
13517
|
+
const popover = this.morePopover();
|
|
13518
|
+
if (popover && ref) {
|
|
13519
|
+
popover.target = ref.nativeElement;
|
|
13520
|
+
popover.open();
|
|
13521
|
+
this.isMorePopoverOpen.set(true);
|
|
13522
|
+
}
|
|
13523
|
+
}
|
|
13524
|
+
closeMorePopover() {
|
|
13525
|
+
const popover = this.morePopover();
|
|
13526
|
+
if (popover) {
|
|
13527
|
+
popover.close();
|
|
13528
|
+
this.isMorePopoverOpen.set(false);
|
|
13529
|
+
}
|
|
13530
|
+
}
|
|
13531
|
+
async ensureLazyItemsLoaded() {
|
|
13532
|
+
if (this.resolveStatus() === 'loading') {
|
|
13533
|
+
return;
|
|
13534
|
+
}
|
|
13535
|
+
const strategy = this.resolveStrategy();
|
|
13536
|
+
if (strategy === 'idsWithCount') {
|
|
13537
|
+
await this.loadByIds();
|
|
13538
|
+
return;
|
|
13539
|
+
}
|
|
13540
|
+
if (strategy === 'countOnly') {
|
|
13541
|
+
await this.loadByNamedQuery();
|
|
13542
|
+
}
|
|
13543
|
+
}
|
|
13544
|
+
/**
|
|
13545
|
+
* ax-popover may emit a boolean, CustomEvent with detail, or a DOM Event depending on version.
|
|
13546
|
+
*/
|
|
13547
|
+
coercePopoverOpenEvent(event) {
|
|
13548
|
+
if (typeof event === 'boolean') {
|
|
13549
|
+
return event;
|
|
13550
|
+
}
|
|
13551
|
+
if (event && typeof event === 'object') {
|
|
13552
|
+
const o = event;
|
|
13553
|
+
if ('detail' in o) {
|
|
13554
|
+
return Boolean(o['detail']);
|
|
13555
|
+
}
|
|
13556
|
+
if ('open' in o) {
|
|
13557
|
+
return Boolean(o['open']);
|
|
13558
|
+
}
|
|
13559
|
+
}
|
|
13560
|
+
return false;
|
|
13561
|
+
}
|
|
13562
|
+
async loadByIds() {
|
|
13563
|
+
const ids = this.idsFromRow();
|
|
13564
|
+
if (!ids.length) {
|
|
13565
|
+
this.resolvedPopoverItems.set([]);
|
|
13566
|
+
this.resolveStatus.set('ready');
|
|
13567
|
+
return;
|
|
13568
|
+
}
|
|
13569
|
+
if (!this.entity?.includes('.')) {
|
|
13570
|
+
const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
|
|
13571
|
+
this.resolveError.set(msg);
|
|
13572
|
+
this.resolveStatus.set('error');
|
|
13573
|
+
return;
|
|
13574
|
+
}
|
|
13575
|
+
this.resolveStatus.set('loading');
|
|
13576
|
+
this.resolveError.set(null);
|
|
13577
|
+
try {
|
|
13578
|
+
const accessor = this.entityService.withEntity(this.entity).data();
|
|
13579
|
+
const results = await Promise.all(ids.map((id) => accessor.byKey(id)));
|
|
13580
|
+
this.resolvedPopoverItems.set(results.filter((item) => item != null));
|
|
13581
|
+
this.resolveStatus.set('ready');
|
|
13582
|
+
}
|
|
13583
|
+
catch {
|
|
13584
|
+
const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
|
|
13585
|
+
this.resolveError.set(msg);
|
|
13586
|
+
this.resolveStatus.set('error');
|
|
13587
|
+
this.resolvedPopoverItems.set([]);
|
|
13588
|
+
}
|
|
13589
|
+
}
|
|
13590
|
+
async loadByNamedQuery() {
|
|
13591
|
+
const cr = this.columnResolve();
|
|
13592
|
+
const key = cr?.queryKey;
|
|
13593
|
+
if (!key?.trim()) {
|
|
13594
|
+
const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
|
|
13595
|
+
this.resolveError.set(msg);
|
|
13596
|
+
this.resolveStatus.set('error');
|
|
13597
|
+
return;
|
|
13598
|
+
}
|
|
13599
|
+
this.resolveStatus.set('loading');
|
|
13600
|
+
this.resolveError.set(null);
|
|
13601
|
+
try {
|
|
13602
|
+
const input = this.buildNamedQueryInput();
|
|
13603
|
+
const result = await this.queryExecutor.fetch(key, input);
|
|
13604
|
+
const items = this.extractItemsFromQueryResult(result);
|
|
13605
|
+
this.resolvedPopoverItems.set(items);
|
|
13606
|
+
this.resolveStatus.set('ready');
|
|
13607
|
+
}
|
|
13608
|
+
catch {
|
|
13609
|
+
const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
|
|
13610
|
+
this.resolveError.set(msg);
|
|
13611
|
+
this.resolveStatus.set('error');
|
|
13612
|
+
this.resolvedPopoverItems.set([]);
|
|
13613
|
+
}
|
|
13614
|
+
}
|
|
13615
|
+
buildNamedQueryInput() {
|
|
13616
|
+
const map = this.parseQueryParamsMap();
|
|
13617
|
+
const row = this.rowData ?? {};
|
|
13618
|
+
if (!map) {
|
|
13619
|
+
return {};
|
|
13620
|
+
}
|
|
13621
|
+
const out = {};
|
|
13622
|
+
for (const [param, path] of Object.entries(map)) {
|
|
13623
|
+
out[param] = get(row, path);
|
|
13624
|
+
}
|
|
13625
|
+
return out;
|
|
13626
|
+
}
|
|
13627
|
+
parseQueryParamsMap() {
|
|
13628
|
+
const raw = this.columnResolve()?.queryParams;
|
|
13629
|
+
if (raw == null) {
|
|
13630
|
+
return null;
|
|
13631
|
+
}
|
|
13632
|
+
if (typeof raw === 'string') {
|
|
13633
|
+
try {
|
|
13634
|
+
const parsed = JSON.parse(raw);
|
|
13635
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
13636
|
+
? parsed
|
|
13637
|
+
: null;
|
|
13638
|
+
}
|
|
13639
|
+
catch {
|
|
13640
|
+
return null;
|
|
13641
|
+
}
|
|
13642
|
+
}
|
|
13643
|
+
return raw;
|
|
13644
|
+
}
|
|
13645
|
+
extractItemsFromQueryResult(result) {
|
|
13646
|
+
if (result == null) {
|
|
13647
|
+
return [];
|
|
13648
|
+
}
|
|
13649
|
+
if (Array.isArray(result)) {
|
|
13650
|
+
return result;
|
|
13651
|
+
}
|
|
13652
|
+
const cr = this.columnResolve();
|
|
13653
|
+
const path = cr?.queryResultItemsPath ?? 'items';
|
|
13654
|
+
const nested = get(result, path);
|
|
13655
|
+
return Array.isArray(nested) ? nested : [];
|
|
13656
|
+
}
|
|
13657
|
+
extractItem(item) {
|
|
13658
|
+
if (isNil(item)) {
|
|
13659
|
+
return null;
|
|
13660
|
+
}
|
|
13661
|
+
if (typeof item === 'object') {
|
|
13662
|
+
return item;
|
|
13663
|
+
}
|
|
13664
|
+
return {
|
|
13665
|
+
[this.valueField]: item,
|
|
13666
|
+
[this.textField]: item,
|
|
13667
|
+
};
|
|
13668
|
+
}
|
|
12646
13669
|
resolveDisplayValue(item) {
|
|
12647
13670
|
if (isNil(item)) {
|
|
12648
13671
|
return '';
|
|
@@ -12657,18 +13680,18 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
12657
13680
|
if (!isNil(rawDisplayValue)) {
|
|
12658
13681
|
return this.resolveDisplayValue(rawDisplayValue);
|
|
12659
13682
|
}
|
|
12660
|
-
if (this.
|
|
12661
|
-
return this.
|
|
13683
|
+
if (this.translation.isValidMultiLanguageObject(item)) {
|
|
13684
|
+
return this.translation.resolve(item);
|
|
12662
13685
|
}
|
|
12663
13686
|
return '';
|
|
12664
13687
|
}
|
|
12665
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps:
|
|
12666
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-flex ax-
|
|
13688
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13689
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "lazyTrigger", first: true, predicate: ["lazyTrigger"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handleItemClick($index)\"\n >\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) {\n <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\"\n #moreButton\n >\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span\n class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\"\n #lazyTrigger\n >\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handlePopoverItemClick($index)\"\n >\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "repositionOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12667
13690
|
}
|
|
12668
13691
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, decorators: [{
|
|
12669
13692
|
type: Component,
|
|
12670
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
12671
|
-
}], propDecorators: { moreButton: [{ type: i0.ViewChild, args: ['moreButton', { isSignal: true }] }], morePopover: [{ type: i0.ViewChild, args: ['morePopover', { isSignal: true }] }] } });
|
|
13693
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXLoadingModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handleItemClick($index)\"\n >\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) {\n <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\"\n #moreButton\n >\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span\n class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\"\n #lazyTrigger\n >\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handlePopoverItemClick($index)\"\n >\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>\n" }]
|
|
13694
|
+
}], ctorParameters: () => [], propDecorators: { moreButton: [{ type: i0.ViewChild, args: ['moreButton', { isSignal: true }] }], lazyTrigger: [{ type: i0.ViewChild, args: ['lazyTrigger', { isSignal: true }] }], morePopover: [{ type: i0.ViewChild, args: ['morePopover', { isSignal: true }] }] } });
|
|
12672
13695
|
|
|
12673
13696
|
var lookupWidgetColumn_component = /*#__PURE__*/Object.freeze({
|
|
12674
13697
|
__proto__: null,
|
|
@@ -12730,6 +13753,100 @@ const AXPLookupWidget = {
|
|
|
12730
13753
|
},
|
|
12731
13754
|
visible: true,
|
|
12732
13755
|
},
|
|
13756
|
+
{
|
|
13757
|
+
name: 'columnResolveStrategy',
|
|
13758
|
+
title: 'Column resolve strategy',
|
|
13759
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
13760
|
+
schema: {
|
|
13761
|
+
dataType: 'string',
|
|
13762
|
+
defaultValue: 'hydrated',
|
|
13763
|
+
interface: {
|
|
13764
|
+
name: 'columnResolveStrategy',
|
|
13765
|
+
path: 'options.columnResolve.strategy',
|
|
13766
|
+
type: AXPWidgetsCatalog.select,
|
|
13767
|
+
valueTransforms: objectKeyValueTransforms('id'),
|
|
13768
|
+
options: {
|
|
13769
|
+
dataSource: [
|
|
13770
|
+
{ id: 'hydrated', title: 'Hydrated (titles on row)' },
|
|
13771
|
+
{ id: 'idsWithCount', title: 'Ids + count on row (load titles on open)' },
|
|
13772
|
+
{ id: 'countOnly', title: 'Count on row (named query on open)' },
|
|
13773
|
+
],
|
|
13774
|
+
},
|
|
13775
|
+
},
|
|
13776
|
+
},
|
|
13777
|
+
visible: true,
|
|
13778
|
+
},
|
|
13779
|
+
{
|
|
13780
|
+
name: 'columnResolveCountFieldPath',
|
|
13781
|
+
title: 'Column resolve — count field path',
|
|
13782
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
13783
|
+
schema: {
|
|
13784
|
+
dataType: 'string',
|
|
13785
|
+
interface: {
|
|
13786
|
+
name: 'columnResolveCountFieldPath',
|
|
13787
|
+
path: 'options.columnResolve.countFieldPath',
|
|
13788
|
+
type: AXPWidgetsCatalog.text,
|
|
13789
|
+
},
|
|
13790
|
+
},
|
|
13791
|
+
visible: true,
|
|
13792
|
+
},
|
|
13793
|
+
{
|
|
13794
|
+
name: 'columnResolveIdsPath',
|
|
13795
|
+
title: 'Column resolve — ids path',
|
|
13796
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
13797
|
+
schema: {
|
|
13798
|
+
dataType: 'string',
|
|
13799
|
+
interface: {
|
|
13800
|
+
name: 'columnResolveIdsPath',
|
|
13801
|
+
path: 'options.columnResolve.idsPath',
|
|
13802
|
+
type: AXPWidgetsCatalog.text,
|
|
13803
|
+
},
|
|
13804
|
+
},
|
|
13805
|
+
visible: true,
|
|
13806
|
+
},
|
|
13807
|
+
{
|
|
13808
|
+
name: 'columnResolveQueryKey',
|
|
13809
|
+
title: 'Column resolve — query key (countOnly)',
|
|
13810
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
13811
|
+
schema: {
|
|
13812
|
+
dataType: 'string',
|
|
13813
|
+
interface: {
|
|
13814
|
+
name: 'columnResolveQueryKey',
|
|
13815
|
+
path: 'options.columnResolve.queryKey',
|
|
13816
|
+
type: AXPWidgetsCatalog.text,
|
|
13817
|
+
},
|
|
13818
|
+
},
|
|
13819
|
+
visible: true,
|
|
13820
|
+
},
|
|
13821
|
+
{
|
|
13822
|
+
name: 'columnResolveQueryParams',
|
|
13823
|
+
title: 'Column resolve — query params (JSON: param → row path)',
|
|
13824
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
13825
|
+
schema: {
|
|
13826
|
+
dataType: 'string',
|
|
13827
|
+
interface: {
|
|
13828
|
+
name: 'columnResolveQueryParams',
|
|
13829
|
+
path: 'options.columnResolve.queryParams',
|
|
13830
|
+
type: AXPWidgetsCatalog.text,
|
|
13831
|
+
},
|
|
13832
|
+
},
|
|
13833
|
+
visible: true,
|
|
13834
|
+
},
|
|
13835
|
+
{
|
|
13836
|
+
name: 'columnResolveQueryResultItemsPath',
|
|
13837
|
+
title: 'Column resolve — query result items path',
|
|
13838
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
13839
|
+
schema: {
|
|
13840
|
+
dataType: 'string',
|
|
13841
|
+
defaultValue: 'items',
|
|
13842
|
+
interface: {
|
|
13843
|
+
name: 'columnResolveQueryResultItemsPath',
|
|
13844
|
+
path: 'options.columnResolve.queryResultItemsPath',
|
|
13845
|
+
type: AXPWidgetsCatalog.text,
|
|
13846
|
+
},
|
|
13847
|
+
},
|
|
13848
|
+
visible: true,
|
|
13849
|
+
},
|
|
12733
13850
|
],
|
|
12734
13851
|
components: {
|
|
12735
13852
|
view: {
|
|
@@ -12747,6 +13864,8 @@ const AXPLookupWidget = {
|
|
|
12747
13864
|
},
|
|
12748
13865
|
};
|
|
12749
13866
|
|
|
13867
|
+
//#endregion
|
|
13868
|
+
|
|
12750
13869
|
/**
|
|
12751
13870
|
* Source type
|
|
12752
13871
|
*/
|
|
@@ -15336,13 +16455,18 @@ class AXPDetailsViewBadgeStatusService {
|
|
|
15336
16455
|
if (!entityDefinition) {
|
|
15337
16456
|
return null;
|
|
15338
16457
|
}
|
|
15339
|
-
const
|
|
15340
|
-
if (
|
|
16458
|
+
const statusPlugins = (entityDefinition.plugins ?? []).filter((p) => p.name === 'status');
|
|
16459
|
+
if (statusPlugins.length === 0) {
|
|
15341
16460
|
return null;
|
|
15342
16461
|
}
|
|
15343
|
-
const
|
|
16462
|
+
const explicitPrimary = statusPlugins.find((p) => p.options?.isPrimary === true);
|
|
16463
|
+
const primaryPlugin = explicitPrimary ??
|
|
16464
|
+
statusPlugins.find((p) => p.options?.isPrimary !== false) ??
|
|
16465
|
+
statusPlugins[0];
|
|
16466
|
+
const statusOptions = primaryPlugin.options;
|
|
15344
16467
|
const statusField = statusOptions.field ?? 'statusId';
|
|
15345
16468
|
const statusDefinitionKey = statusOptions.definition;
|
|
16469
|
+
const pluginReadonly = statusOptions.readonly === true;
|
|
15346
16470
|
let definitionKey = typeof statusDefinitionKey === 'string' ? statusDefinitionKey : undefined;
|
|
15347
16471
|
if (!definitionKey) {
|
|
15348
16472
|
const statusProperty = entityDefinition.properties?.find((p) => p.name === statusField);
|
|
@@ -15363,7 +16487,7 @@ class AXPDetailsViewBadgeStatusService {
|
|
|
15363
16487
|
definitionKey,
|
|
15364
16488
|
value: String(value),
|
|
15365
16489
|
dataPath: statusField,
|
|
15366
|
-
readonly: currentPage?.isReadonly ?? false,
|
|
16490
|
+
readonly: pluginReadonly || (currentPage?.isReadonly ?? false),
|
|
15367
16491
|
};
|
|
15368
16492
|
}
|
|
15369
16493
|
catch (error) {
|
|
@@ -15546,34 +16670,35 @@ class AXPBaseRelatedEntityConverter {
|
|
|
15546
16670
|
groups,
|
|
15547
16671
|
};
|
|
15548
16672
|
}
|
|
15549
|
-
|
|
15550
|
-
return
|
|
16673
|
+
createGridLayoutStructure(singleInterface, helpers, includeProperties) {
|
|
16674
|
+
return this.createGridLayoutStructureInternal(singleInterface, helpers, includeProperties);
|
|
15551
16675
|
}
|
|
15552
|
-
//#region ----
|
|
15553
|
-
|
|
16676
|
+
//#region ---- Property visibility (layout) ----
|
|
16677
|
+
/**
|
|
16678
|
+
* Chooses which properties appear in the related-entity grid layout.
|
|
16679
|
+
* Boolean `schema.visible === false` excludes a property. String templates are kept in the layout
|
|
16680
|
+
* and passed on `form-field.options.visible`; `AXPWidgetRendererDirective` uses
|
|
16681
|
+
* `AXPExpressionEvaluatorService` in `updateVisibility()` to evaluate them when context updates.
|
|
16682
|
+
*/
|
|
16683
|
+
getVisiblePropertiesByGroupId(sectionId, helpers, includeProperties) {
|
|
15554
16684
|
let properties = helpers.getPropertyByGroupId(sectionId) ?? [];
|
|
15555
16685
|
// Filter by includeProperties if provided
|
|
15556
16686
|
if (includeProperties && includeProperties.length > 0) {
|
|
15557
16687
|
const includeSet = new Set(includeProperties);
|
|
15558
16688
|
properties = properties.filter((p) => includeSet.has(p.name));
|
|
15559
16689
|
}
|
|
15560
|
-
const evaluated =
|
|
15561
|
-
|
|
15562
|
-
if (typeof visible === 'string'
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
visible = result.visible;
|
|
15566
|
-
}
|
|
15567
|
-
catch {
|
|
15568
|
-
visible = true;
|
|
15569
|
-
}
|
|
16690
|
+
const evaluated = properties.map((property) => {
|
|
16691
|
+
const visible = property?.schema?.visible;
|
|
16692
|
+
if (typeof visible === 'string') {
|
|
16693
|
+
// Include in layout; runtime evaluation via `AXPExpressionEvaluatorService` (widget renderer).
|
|
16694
|
+
return { property, visible: true };
|
|
15570
16695
|
}
|
|
15571
16696
|
return { property, visible: visible ?? true };
|
|
15572
|
-
})
|
|
16697
|
+
});
|
|
15573
16698
|
return evaluated.filter((x) => x.visible !== false).map((x) => x.property);
|
|
15574
16699
|
}
|
|
15575
|
-
|
|
15576
|
-
const visibleProperties =
|
|
16700
|
+
createPropertyGrid(sectionId, helpers, includeProperties) {
|
|
16701
|
+
const visibleProperties = this.getVisiblePropertiesByGroupId(sectionId, helpers, includeProperties);
|
|
15577
16702
|
return {
|
|
15578
16703
|
type: 'grid-layout',
|
|
15579
16704
|
mode: 'edit',
|
|
@@ -15588,6 +16713,15 @@ class AXPBaseRelatedEntityConverter {
|
|
|
15588
16713
|
},
|
|
15589
16714
|
children: visibleProperties.map((p) => {
|
|
15590
16715
|
const layout = helpers.getPropertyLayout(p.name);
|
|
16716
|
+
const sv = p.schema?.visible;
|
|
16717
|
+
let formFieldVisible;
|
|
16718
|
+
if (typeof sv === 'string') {
|
|
16719
|
+
// Evaluated by `AXPExpressionEvaluatorService` in `AXPWidgetRendererDirective.updateVisibility()`.
|
|
16720
|
+
formFieldVisible = sv;
|
|
16721
|
+
}
|
|
16722
|
+
else if (sv === false) {
|
|
16723
|
+
formFieldVisible = false;
|
|
16724
|
+
}
|
|
15591
16725
|
return {
|
|
15592
16726
|
type: 'grid-item-layout',
|
|
15593
16727
|
name: p.name,
|
|
@@ -15602,6 +16736,8 @@ class AXPBaseRelatedEntityConverter {
|
|
|
15602
16736
|
options: {
|
|
15603
16737
|
label: p.title,
|
|
15604
16738
|
showLabel: layout?.label?.visible ?? true,
|
|
16739
|
+
...(formFieldVisible !== undefined ? { visible: formFieldVisible } : {}),
|
|
16740
|
+
...(hintFormFieldOptionsFromDescription(p.description) ?? {}),
|
|
15605
16741
|
},
|
|
15606
16742
|
children: [
|
|
15607
16743
|
{
|
|
@@ -15621,13 +16757,15 @@ class AXPBaseRelatedEntityConverter {
|
|
|
15621
16757
|
}),
|
|
15622
16758
|
};
|
|
15623
16759
|
}
|
|
15624
|
-
|
|
16760
|
+
createGridLayoutStructureInternal(singleInterface, helpers, includeProperties) {
|
|
15625
16761
|
// Filter out empty sections (sections with no visible properties)
|
|
15626
|
-
const sectionsWithProperties =
|
|
15627
|
-
const visibleProperties =
|
|
16762
|
+
const sectionsWithProperties = (singleInterface?.sections ?? []).map((s) => {
|
|
16763
|
+
const visibleProperties = this.getVisiblePropertiesByGroupId(s.id, helpers, includeProperties);
|
|
15628
16764
|
return { section: s, hasProperties: visibleProperties.length > 0 };
|
|
15629
|
-
})
|
|
15630
|
-
const validSections = sectionsWithProperties
|
|
16765
|
+
});
|
|
16766
|
+
const validSections = sectionsWithProperties
|
|
16767
|
+
.filter((item) => item.hasProperties)
|
|
16768
|
+
.map((item) => item.section);
|
|
15631
16769
|
return {
|
|
15632
16770
|
type: 'grid-layout',
|
|
15633
16771
|
options: {
|
|
@@ -15639,7 +16777,7 @@ class AXPBaseRelatedEntityConverter {
|
|
|
15639
16777
|
},
|
|
15640
16778
|
},
|
|
15641
16779
|
},
|
|
15642
|
-
children:
|
|
16780
|
+
children: validSections.map((s) => ({
|
|
15643
16781
|
type: 'grid-item-layout',
|
|
15644
16782
|
name: s.id,
|
|
15645
16783
|
options: {
|
|
@@ -15653,11 +16791,12 @@ class AXPBaseRelatedEntityConverter {
|
|
|
15653
16791
|
options: {
|
|
15654
16792
|
title: helpers.getGroupById(s.id)?.title ?? '',
|
|
15655
16793
|
collapsible: true,
|
|
16794
|
+
showTitle: s.layout?.label?.visible ?? true,
|
|
15656
16795
|
},
|
|
15657
|
-
children: [
|
|
16796
|
+
children: [this.createPropertyGrid(s.id, helpers, includeProperties)],
|
|
15658
16797
|
},
|
|
15659
16798
|
],
|
|
15660
|
-
}))
|
|
16799
|
+
})),
|
|
15661
16800
|
};
|
|
15662
16801
|
}
|
|
15663
16802
|
}
|
|
@@ -15860,7 +16999,7 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
|
|
|
15860
16999
|
execute: this.createExecuteFunction(entityDef, actions, evaluateExpressions, context),
|
|
15861
17000
|
actions: await this.buildEvaluatedActions(actions, evaluateExpressions),
|
|
15862
17001
|
// tabs: [...tabDetailTabs, ...tabListTabs],
|
|
15863
|
-
content: [
|
|
17002
|
+
content: [this.createGridLayoutStructure(helpers.singleInterface, helpers, relatedEntity.properties)],
|
|
15864
17003
|
};
|
|
15865
17004
|
}
|
|
15866
17005
|
//#region ---- Utility Methods ----
|
|
@@ -16178,16 +17317,34 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
16178
17317
|
evaluatedOptions = action.options;
|
|
16179
17318
|
}
|
|
16180
17319
|
}
|
|
16181
|
-
|
|
17320
|
+
let actionData = action.scope == AXPEntityCommandScope.Selected
|
|
16182
17321
|
? executeContext
|
|
16183
17322
|
: evaluatedOptions?.['process']?.data || null;
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16190
|
-
|
|
17323
|
+
const foreignKeyField = relatedEntity.persistence?.foreignKeyField;
|
|
17324
|
+
if (commandName === 'Entity:Create' && foreignKeyField && context.context) {
|
|
17325
|
+
const parentId = get(context.context, 'id');
|
|
17326
|
+
actionData = {
|
|
17327
|
+
...(typeof actionData === 'object' && actionData !== null ? actionData : {}),
|
|
17328
|
+
...(parentId !== undefined && parentId !== null && parentId !== ''
|
|
17329
|
+
? { [foreignKeyField]: parentId }
|
|
17330
|
+
: {}),
|
|
17331
|
+
};
|
|
17332
|
+
}
|
|
17333
|
+
const createExcludes = [
|
|
17334
|
+
...new Set([
|
|
17335
|
+
...(relatedEntity.excludeProperties ?? []),
|
|
17336
|
+
...(commandName === 'Entity:Create' && foreignKeyField ? [foreignKeyField] : []),
|
|
17337
|
+
]),
|
|
17338
|
+
];
|
|
17339
|
+
const excludeForCreate = commandName === 'Entity:Create' && createExcludes.length > 0;
|
|
17340
|
+
const excludeForUpdate = commandName === 'Entity:Update' &&
|
|
17341
|
+
relatedEntity.excludeProperties &&
|
|
17342
|
+
relatedEntity.excludeProperties.length > 0;
|
|
17343
|
+
const mergedOptions = excludeForCreate
|
|
17344
|
+
? { ...evaluatedOptions, excludeProperties: createExcludes }
|
|
17345
|
+
: excludeForUpdate
|
|
17346
|
+
? { ...evaluatedOptions, excludeProperties: relatedEntity.excludeProperties }
|
|
17347
|
+
: evaluatedOptions;
|
|
16191
17348
|
if (context.commandService.exists(commandName)) {
|
|
16192
17349
|
// check options for evaluation
|
|
16193
17350
|
await context.commandService.execute(commandName, {
|
|
@@ -16252,6 +17409,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
16252
17409
|
includeColumns,
|
|
16253
17410
|
relatedTableColumns,
|
|
16254
17411
|
customFilterDefinitions: relatedEntity.customFilterDefinitions,
|
|
17412
|
+
excludeProperties: relatedEntity.excludeProperties,
|
|
16255
17413
|
},
|
|
16256
17414
|
},
|
|
16257
17415
|
],
|
|
@@ -16310,7 +17468,7 @@ class AXPTabDetailsConverter extends AXPBaseRelatedEntityConverter {
|
|
|
16310
17468
|
title: relatedEntity.title ?? entityDef?.title ?? '',
|
|
16311
17469
|
icon: relatedEntity.icon || entityDef.icon,
|
|
16312
17470
|
content: [
|
|
16313
|
-
|
|
17471
|
+
this.createGridLayoutStructure(helpers.singleInterface, helpers, relatedEntity.properties),
|
|
16314
17472
|
],
|
|
16315
17473
|
};
|
|
16316
17474
|
}
|
|
@@ -16370,6 +17528,7 @@ class AXPTabListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
16370
17528
|
includeColumns,
|
|
16371
17529
|
relatedTableColumns,
|
|
16372
17530
|
customFilterDefinitions: relatedEntity.customFilterDefinitions,
|
|
17531
|
+
excludeProperties: relatedEntity.excludeProperties,
|
|
16373
17532
|
},
|
|
16374
17533
|
},
|
|
16375
17534
|
],
|
|
@@ -16866,20 +18025,18 @@ class AXPMainEntityContentBuilder {
|
|
|
16866
18025
|
const visibleInRelated = isFromRelated ? !!p.__layout : false;
|
|
16867
18026
|
return visibleInMain || visibleInRelated;
|
|
16868
18027
|
});
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
}
|
|
18028
|
+
// String `schema.visible` is not evaluated here (no `dependencies.expressionEvaluator` call).
|
|
18029
|
+
// The template is placed on `form-field.options.visible` below; at render time
|
|
18030
|
+
// `AXPWidgetRendererDirective` injects `AXPExpressionEvaluatorService` and evaluates it in
|
|
18031
|
+
// `updateVisibility()` (and on context changes via `hasVisibilityDependency`). Evaluating once
|
|
18032
|
+
// at build time would freeze visibility and break detail/edit when data changes.
|
|
18033
|
+
const visibilityEvaluated = inView.map((p) => {
|
|
18034
|
+
const v = p.schema?.visible;
|
|
18035
|
+
if (typeof v === 'string') {
|
|
18036
|
+
return { property: p, resolvedVisible: true };
|
|
16879
18037
|
}
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
}));
|
|
18038
|
+
return { property: p, resolvedVisible: v !== false };
|
|
18039
|
+
});
|
|
16883
18040
|
const visibleProperties = visibilityEvaluated
|
|
16884
18041
|
.filter((x) => x.resolvedVisible)
|
|
16885
18042
|
.map((x) => x.property);
|
|
@@ -16903,6 +18060,7 @@ class AXPMainEntityContentBuilder {
|
|
|
16903
18060
|
collapsible: true,
|
|
16904
18061
|
isOpen: !s.collapsed,
|
|
16905
18062
|
look: 'card',
|
|
18063
|
+
showTitle: s.layout?.label?.visible !== false,
|
|
16906
18064
|
},
|
|
16907
18065
|
children: [
|
|
16908
18066
|
{
|
|
@@ -16927,8 +18085,9 @@ class AXPMainEntityContentBuilder {
|
|
|
16927
18085
|
const hasOwnDisabled = p.schema.interface?.options?.disabled !== undefined;
|
|
16928
18086
|
let formFieldVisible;
|
|
16929
18087
|
const sv = p.schema?.visible;
|
|
18088
|
+
// Template strings are evaluated by `AXPExpressionEvaluatorService` in the widget renderer, not here.
|
|
16930
18089
|
if (typeof sv === 'string') {
|
|
16931
|
-
formFieldVisible =
|
|
18090
|
+
formFieldVisible = sv;
|
|
16932
18091
|
}
|
|
16933
18092
|
else if (sv === false) {
|
|
16934
18093
|
formFieldVisible = false;
|
|
@@ -16945,6 +18104,7 @@ class AXPMainEntityContentBuilder {
|
|
|
16945
18104
|
rowStart: layout?.positions?.lg?.rowStart,
|
|
16946
18105
|
rowEnd: layout?.positions?.lg?.rowEnd,
|
|
16947
18106
|
...(formFieldVisible !== undefined ? { visible: formFieldVisible } : {}),
|
|
18107
|
+
...(hintFormFieldOptionsFromDescription(p.description) ?? {}),
|
|
16948
18108
|
},
|
|
16949
18109
|
children: [
|
|
16950
18110
|
{
|
|
@@ -17942,6 +19102,7 @@ const AXPCrudModifier = {
|
|
|
17942
19102
|
if (!command?.create) {
|
|
17943
19103
|
command.create = {
|
|
17944
19104
|
execute: async (data) => {
|
|
19105
|
+
console.log('create', ctx.module.get() + '.' + ctx.name.get(), data);
|
|
17945
19106
|
const res = await dataService.insertOne(data);
|
|
17946
19107
|
return { id: res };
|
|
17947
19108
|
},
|
|
@@ -17957,6 +19118,7 @@ const AXPCrudModifier = {
|
|
|
17957
19118
|
if (!command?.update) {
|
|
17958
19119
|
command.update = {
|
|
17959
19120
|
execute: async (data) => {
|
|
19121
|
+
console.log('update', ctx.module.get() + '.' + ctx.name.get(), data);
|
|
17960
19122
|
return await dataService.updateOne(data.id, data);
|
|
17961
19123
|
},
|
|
17962
19124
|
};
|
|
@@ -17971,7 +19133,6 @@ const AXPCrudModifier = {
|
|
|
17971
19133
|
queries.byKey = {
|
|
17972
19134
|
execute: async (id) => {
|
|
17973
19135
|
const data = await dataService.getOne(id);
|
|
17974
|
-
// debugger;
|
|
17975
19136
|
return data;
|
|
17976
19137
|
},
|
|
17977
19138
|
type: AXPEntityQueryType.Single,
|
|
@@ -17980,6 +19141,7 @@ const AXPCrudModifier = {
|
|
|
17980
19141
|
if (!queries?.list) {
|
|
17981
19142
|
queries.list = {
|
|
17982
19143
|
execute: async (e) => {
|
|
19144
|
+
console.log('query', ctx.module.get() + '.' + ctx.name.get(), e);
|
|
17983
19145
|
return await dataService.query(e);
|
|
17984
19146
|
},
|
|
17985
19147
|
type: AXPEntityQueryType.List,
|
|
@@ -18575,10 +19737,34 @@ class AXPShowListViewAction extends AXPWorkflowAction {
|
|
|
18575
19737
|
this.sessionService = inject(AXPSessionService);
|
|
18576
19738
|
}
|
|
18577
19739
|
async execute(context) {
|
|
18578
|
-
const
|
|
19740
|
+
const entity = context.getVariable('entity');
|
|
19741
|
+
const [moduleName, entityName] = entity.split('.');
|
|
18579
19742
|
const newPayload = {
|
|
18580
19743
|
commands: `/${this.sessionService.application?.name}/m/${moduleName}/e/${entityName}/list`,
|
|
18581
19744
|
};
|
|
19745
|
+
const conditions = context.getVariable('conditions');
|
|
19746
|
+
if (Array.isArray(conditions) && conditions.length > 0) {
|
|
19747
|
+
const filterQueries = conditions
|
|
19748
|
+
.map((c) => {
|
|
19749
|
+
const field = c.name ?? c.field;
|
|
19750
|
+
if (!field || c.value === undefined || c.value === null || c.value === '') {
|
|
19751
|
+
return null;
|
|
19752
|
+
}
|
|
19753
|
+
return {
|
|
19754
|
+
field,
|
|
19755
|
+
operator: c.operator ?? { type: 'equal' },
|
|
19756
|
+
value: c.value,
|
|
19757
|
+
};
|
|
19758
|
+
})
|
|
19759
|
+
.filter((entry) => entry !== null);
|
|
19760
|
+
if (filterQueries.length > 0) {
|
|
19761
|
+
newPayload.extras = {
|
|
19762
|
+
queryParams: {
|
|
19763
|
+
filters: JSON.stringify(filterQueries),
|
|
19764
|
+
},
|
|
19765
|
+
};
|
|
19766
|
+
}
|
|
19767
|
+
}
|
|
18582
19768
|
context.setVariable('payload', newPayload);
|
|
18583
19769
|
this.navigation.execute(context);
|
|
18584
19770
|
}
|
|
@@ -19145,244 +20331,9 @@ var getEntityDetails_query = /*#__PURE__*/Object.freeze({
|
|
|
19145
20331
|
AXPGetEntityDetailsQuery: AXPGetEntityDetailsQuery
|
|
19146
20332
|
});
|
|
19147
20333
|
|
|
19148
|
-
// #region Master
|
|
19149
|
-
function entityMasterCreateAction() {
|
|
19150
|
-
return {
|
|
19151
|
-
title: '@general:actions.create.title',
|
|
19152
|
-
command: {
|
|
19153
|
-
name: 'Entity:Create',
|
|
19154
|
-
},
|
|
19155
|
-
priority: 'primary',
|
|
19156
|
-
type: AXPSystemActionType.Create,
|
|
19157
|
-
scope: AXPEntityCommandScope.TypeLevel,
|
|
19158
|
-
};
|
|
19159
|
-
}
|
|
19160
|
-
function entityMasterEditAction() {
|
|
19161
|
-
return {
|
|
19162
|
-
title: '@general:actions.edit.title',
|
|
19163
|
-
command: 'Entity:Update',
|
|
19164
|
-
priority: 'secondary',
|
|
19165
|
-
type: AXPSystemActionType.Update,
|
|
19166
|
-
scope: AXPEntityCommandScope.Individual,
|
|
19167
|
-
default: true,
|
|
19168
|
-
};
|
|
19169
|
-
}
|
|
19170
|
-
function entityMasterBulkDeleteAction() {
|
|
19171
|
-
return {
|
|
19172
|
-
title: '@general:actions.delete-items.title',
|
|
19173
|
-
command: 'delete-entity',
|
|
19174
|
-
priority: 'primary',
|
|
19175
|
-
type: AXPSystemActionType.Delete,
|
|
19176
|
-
scope: AXPEntityCommandScope.Selected,
|
|
19177
|
-
order: 100,
|
|
19178
|
-
};
|
|
19179
|
-
}
|
|
19180
|
-
function entityMasterViewAction() {
|
|
19181
|
-
return {
|
|
19182
|
-
title: '@general:actions.view.title',
|
|
19183
|
-
command: 'open-entity',
|
|
19184
|
-
priority: 'secondary',
|
|
19185
|
-
type: AXPSystemActionType.View,
|
|
19186
|
-
scope: AXPEntityCommandScope.Individual,
|
|
19187
|
-
default: true,
|
|
19188
|
-
};
|
|
19189
|
-
}
|
|
19190
|
-
function entityMasterDeleteAction() {
|
|
19191
|
-
return {
|
|
19192
|
-
title: '@general:actions.delete.title',
|
|
19193
|
-
command: 'delete-entity',
|
|
19194
|
-
priority: 'secondary',
|
|
19195
|
-
type: AXPSystemActionType.Delete,
|
|
19196
|
-
scope: AXPEntityCommandScope.Individual,
|
|
19197
|
-
order: 100,
|
|
19198
|
-
};
|
|
19199
|
-
}
|
|
19200
|
-
function entityMasterCrudActions(options) {
|
|
19201
|
-
const opts = {
|
|
19202
|
-
create: true,
|
|
19203
|
-
delete: true,
|
|
19204
|
-
view: true,
|
|
19205
|
-
edit: false,
|
|
19206
|
-
...options,
|
|
19207
|
-
};
|
|
19208
|
-
const actions = [];
|
|
19209
|
-
if (opts.create) {
|
|
19210
|
-
actions.push(entityMasterCreateAction());
|
|
19211
|
-
}
|
|
19212
|
-
if (opts.delete) {
|
|
19213
|
-
actions.push(entityMasterBulkDeleteAction());
|
|
19214
|
-
actions.push(entityMasterDeleteAction());
|
|
19215
|
-
}
|
|
19216
|
-
if (opts.view) {
|
|
19217
|
-
actions.push(entityMasterViewAction());
|
|
19218
|
-
}
|
|
19219
|
-
if (opts.edit) {
|
|
19220
|
-
actions.push(entityMasterEditAction());
|
|
19221
|
-
}
|
|
19222
|
-
return actions;
|
|
19223
|
-
}
|
|
19224
|
-
function entityMasterRecordActions() {
|
|
19225
|
-
return [entityMasterDeleteAction()];
|
|
19226
|
-
}
|
|
19227
|
-
// #endregion
|
|
19228
|
-
// #region Details
|
|
19229
|
-
function entityDetailsCreateActions(parentId) {
|
|
19230
|
-
return {
|
|
19231
|
-
title: '@general:actions.create.title',
|
|
19232
|
-
command: {
|
|
19233
|
-
name: 'Entity:Create',
|
|
19234
|
-
options: {
|
|
19235
|
-
process: {
|
|
19236
|
-
redirect: false,
|
|
19237
|
-
canCreateNewOne: true,
|
|
19238
|
-
data: {
|
|
19239
|
-
[parentId]: '{{context.eval("id")}}',
|
|
19240
|
-
},
|
|
19241
|
-
},
|
|
19242
|
-
},
|
|
19243
|
-
},
|
|
19244
|
-
priority: 'primary',
|
|
19245
|
-
type: AXPSystemActionType.Create,
|
|
19246
|
-
scope: AXPEntityCommandScope.TypeLevel,
|
|
19247
|
-
};
|
|
19248
|
-
}
|
|
19249
|
-
function entityDetailsSimpleCondition(fk) {
|
|
19250
|
-
return {
|
|
19251
|
-
name: fk,
|
|
19252
|
-
operator: { type: 'equal' },
|
|
19253
|
-
value: '{{context.eval("id")}}',
|
|
19254
|
-
};
|
|
19255
|
-
}
|
|
19256
|
-
function entityDetailsReferenceCondition(type) {
|
|
19257
|
-
return [
|
|
19258
|
-
{
|
|
19259
|
-
name: 'reference.id',
|
|
19260
|
-
operator: { type: 'equal' },
|
|
19261
|
-
value: '{{context.eval("id")}}',
|
|
19262
|
-
},
|
|
19263
|
-
{
|
|
19264
|
-
name: 'reference.type',
|
|
19265
|
-
operator: { type: 'equal' },
|
|
19266
|
-
value: type,
|
|
19267
|
-
},
|
|
19268
|
-
];
|
|
19269
|
-
}
|
|
19270
|
-
function entityDetailsEditAction() {
|
|
19271
|
-
return {
|
|
19272
|
-
title: '@general:actions.edit.title',
|
|
19273
|
-
// command: 'quick-modify-entity',
|
|
19274
|
-
command: 'Entity:Update',
|
|
19275
|
-
priority: 'secondary',
|
|
19276
|
-
type: AXPSystemActionType.Update,
|
|
19277
|
-
default: true,
|
|
19278
|
-
scope: AXPEntityCommandScope.Individual,
|
|
19279
|
-
};
|
|
19280
|
-
}
|
|
19281
|
-
function entityDetailsNewEditAction() {
|
|
19282
|
-
return {
|
|
19283
|
-
title: 'New Edit',
|
|
19284
|
-
command: {
|
|
19285
|
-
name: 'Entity:Update',
|
|
19286
|
-
},
|
|
19287
|
-
priority: 'secondary',
|
|
19288
|
-
type: AXPSystemActionType.Update,
|
|
19289
|
-
default: true,
|
|
19290
|
-
scope: AXPEntityCommandScope.Individual,
|
|
19291
|
-
};
|
|
19292
|
-
}
|
|
19293
|
-
function entityOverrideDetailsViewAction() {
|
|
19294
|
-
return {
|
|
19295
|
-
title: '@general:actions.view.title',
|
|
19296
|
-
command: 'open-entity',
|
|
19297
|
-
priority: 'secondary',
|
|
19298
|
-
hidden: true,
|
|
19299
|
-
type: AXPSystemActionType.View,
|
|
19300
|
-
scope: AXPEntityCommandScope.Individual,
|
|
19301
|
-
};
|
|
19302
|
-
}
|
|
19303
|
-
function entityDetailsCrudActions(parentId, options) {
|
|
19304
|
-
const opts = {
|
|
19305
|
-
create: true,
|
|
19306
|
-
delete: true,
|
|
19307
|
-
view: true,
|
|
19308
|
-
edit: true,
|
|
19309
|
-
...options,
|
|
19310
|
-
};
|
|
19311
|
-
const actions = [];
|
|
19312
|
-
if (opts.create) {
|
|
19313
|
-
actions.push(entityDetailsCreateActions(parentId));
|
|
19314
|
-
}
|
|
19315
|
-
if (opts.edit) {
|
|
19316
|
-
actions.push(entityDetailsEditAction());
|
|
19317
|
-
}
|
|
19318
|
-
if (opts.view) {
|
|
19319
|
-
actions.push(entityOverrideDetailsViewAction());
|
|
19320
|
-
}
|
|
19321
|
-
return actions;
|
|
19322
|
-
}
|
|
19323
|
-
function entityDetailsReferenceCreateActions(type) {
|
|
19324
|
-
return [
|
|
19325
|
-
{
|
|
19326
|
-
title: '@general:actions.create.title',
|
|
19327
|
-
command: {
|
|
19328
|
-
name: 'Entity:Create',
|
|
19329
|
-
options: {
|
|
19330
|
-
process: {
|
|
19331
|
-
redirect: false,
|
|
19332
|
-
canCreateNewOne: true,
|
|
19333
|
-
data: {
|
|
19334
|
-
reference: {
|
|
19335
|
-
id: '{{context.eval("id")}}',
|
|
19336
|
-
type: type,
|
|
19337
|
-
},
|
|
19338
|
-
},
|
|
19339
|
-
},
|
|
19340
|
-
},
|
|
19341
|
-
},
|
|
19342
|
-
priority: 'primary',
|
|
19343
|
-
type: AXPSystemActionType.Create,
|
|
19344
|
-
scope: AXPEntityCommandScope.TypeLevel,
|
|
19345
|
-
},
|
|
19346
|
-
entityDetailsEditAction(),
|
|
19347
|
-
entityOverrideDetailsViewAction(),
|
|
19348
|
-
];
|
|
19349
|
-
}
|
|
19350
|
-
/**
|
|
19351
|
-
* Computes a diff between two plain objects with array-aware semantics.
|
|
19352
|
-
* - For arrays of objects with an id field, computes added/removed by id.
|
|
19353
|
-
* - For arrays of primitives or objects without id, uses deep equality.
|
|
19354
|
-
* - For scalars/objects, reports oldValue/newValue when changed.
|
|
19355
|
-
*/
|
|
19356
|
-
function detectEntityChanges(oldObj, newObj) {
|
|
19357
|
-
return transform(newObj, (result, value, key) => {
|
|
19358
|
-
if (!isEqual$1(value, oldObj[key])) {
|
|
19359
|
-
const oldValue = oldObj[key];
|
|
19360
|
-
if (Array.isArray(value) || Array.isArray(oldValue)) {
|
|
19361
|
-
const oldArray = Array.isArray(oldValue) ? oldValue : [];
|
|
19362
|
-
const newArray = Array.isArray(value) ? value : [];
|
|
19363
|
-
const hasId = newArray.length > 0 && typeof newArray[0] === 'object' && newArray[0] !== null && 'id' in newArray[0];
|
|
19364
|
-
if (hasId) {
|
|
19365
|
-
const added = newArray.filter((item) => !oldArray.some((oldItem) => oldItem.id === item.id));
|
|
19366
|
-
const removed = oldArray.filter((item) => !newArray.some((newItem) => newItem.id === item.id));
|
|
19367
|
-
result[key] = { oldValue, newValue: value, added, removed };
|
|
19368
|
-
}
|
|
19369
|
-
else {
|
|
19370
|
-
const added = newArray.filter((item) => !oldArray.some((oldItem) => isEqual$1(item, oldItem)));
|
|
19371
|
-
const removed = oldArray.filter((item) => !newArray.some((newItem) => isEqual$1(item, newItem)));
|
|
19372
|
-
result[key] = { oldValue, newValue: value, added, removed };
|
|
19373
|
-
}
|
|
19374
|
-
}
|
|
19375
|
-
else {
|
|
19376
|
-
result[key] = { oldValue, newValue: value };
|
|
19377
|
-
}
|
|
19378
|
-
}
|
|
19379
|
-
}, {});
|
|
19380
|
-
}
|
|
19381
|
-
//#endregion
|
|
19382
|
-
|
|
19383
20334
|
/**
|
|
19384
20335
|
* Generated bundle index. Do not edit.
|
|
19385
20336
|
*/
|
|
19386
20337
|
|
|
19387
|
-
export { AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_ACTION_PLUGIN, AXP_ENTITY_CONFIG_TOKEN, AXP_ENTITY_DEFINITION_LOADER, AXP_ENTITY_MODIFIER, AXP_ENTITY_STORAGE_BACKEND, AXP_ENTITY_STORAGE_MIDDLEWARE, AXP_MULTI_SOURCE_DEFINITION_PROVIDER, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, EntityBuilder, EntityDataAccessor, actionExists, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, cloneLayoutArrays, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, getMasterInterfacePropertySortKey, isAXPMiddlewareAbortError, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, provideEntity, resolveEntityPluginDetailPageOrder, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider };
|
|
20338
|
+
export { AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_ACTION_PLUGIN, AXP_ENTITY_CONFIG_TOKEN, AXP_ENTITY_DEFINITION_LOADER, AXP_ENTITY_MODIFIER, AXP_ENTITY_STORAGE_BACKEND, AXP_ENTITY_STORAGE_MIDDLEWARE, AXP_MULTI_SOURCE_DEFINITION_PROVIDER, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, EntityBuilder, EntityDataAccessor, actionExists, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, cloneLayoutArrays, collectNestedCreateHiddenProperties, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, getMasterInterfacePropertySortKey, isAXPMiddlewareAbortError, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mergeForeignKeyFieldIntoCreateActions, provideEntity, resolveEntityPluginDetailPageOrder, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider };
|
|
19388
20339
|
//# sourceMappingURL=acorex-platform-layout-entity.mjs.map
|