@memberjunction/ng-skip-chat 2.58.0 → 2.60.0
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/dist/lib/dynamic-report/skip-react-component-host.d.ts +97 -0
- package/dist/lib/dynamic-report/skip-react-component-host.d.ts.map +1 -1
- package/dist/lib/dynamic-report/skip-react-component-host.js +1223 -1
- package/dist/lib/dynamic-report/skip-react-component-host.js.map +1 -1
- package/dist/lib/skip-chat/skip-chat.component.d.ts +22 -1
- package/dist/lib/skip-chat/skip-chat.component.d.ts.map +1 -1
- package/dist/lib/skip-chat/skip-chat.component.js +688 -32
- package/dist/lib/skip-chat/skip-chat.component.js.map +1 -1
- package/package.json +13 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LogError } from '@memberjunction/core';
|
|
2
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
2
3
|
/**
|
|
3
4
|
* CDN URLs for external dependencies
|
|
4
5
|
* These can be configured via environment variables in the future
|
|
@@ -21,6 +22,106 @@ const CDN_URLS = {
|
|
|
21
22
|
// Utilities
|
|
22
23
|
LODASH_JS: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js'
|
|
23
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Global component registry service for managing reusable React components
|
|
27
|
+
* Extends BaseSingleton to ensure a truly global singleton instance across
|
|
28
|
+
* the entire application, even if this code is loaded multiple times.
|
|
29
|
+
*/
|
|
30
|
+
export class GlobalComponentRegistry extends BaseSingleton {
|
|
31
|
+
components = new Map();
|
|
32
|
+
constructor() {
|
|
33
|
+
super(); // Call parent constructor to register in global store
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the singleton instance
|
|
37
|
+
*/
|
|
38
|
+
static get Instance() {
|
|
39
|
+
return super.getInstance();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Register a component with a simple key
|
|
43
|
+
*/
|
|
44
|
+
register(key, component) {
|
|
45
|
+
this.components.set(key, { component });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get a component by key
|
|
49
|
+
*/
|
|
50
|
+
get(key) {
|
|
51
|
+
const entry = this.components.get(key);
|
|
52
|
+
return entry?.component;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Register a component with metadata for versioning and context
|
|
56
|
+
*/
|
|
57
|
+
registerWithMetadata(name, context, version, component, description) {
|
|
58
|
+
const key = this.createKey(name, context, version);
|
|
59
|
+
this.components.set(key, {
|
|
60
|
+
component,
|
|
61
|
+
metadata: { context, version, description }
|
|
62
|
+
});
|
|
63
|
+
// Also register without version for backwards compatibility
|
|
64
|
+
const contextKey = `${name}_${context}`;
|
|
65
|
+
if (!this.components.has(contextKey)) {
|
|
66
|
+
this.register(contextKey, component);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a standardized key from component metadata
|
|
71
|
+
*/
|
|
72
|
+
createKey(name, context, version) {
|
|
73
|
+
return `${name}_${context}_${version}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get all registered component keys (useful for debugging)
|
|
77
|
+
*/
|
|
78
|
+
getRegisteredKeys() {
|
|
79
|
+
return Array.from(this.components.keys());
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Clear all registered components
|
|
83
|
+
*/
|
|
84
|
+
clear() {
|
|
85
|
+
this.components.clear();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a component is registered
|
|
89
|
+
*/
|
|
90
|
+
has(key) {
|
|
91
|
+
return this.components.has(key);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get component with fallback options
|
|
95
|
+
*/
|
|
96
|
+
getWithFallback(name, context, version) {
|
|
97
|
+
// Try exact match first
|
|
98
|
+
let key = this.createKey(name, context, version);
|
|
99
|
+
if (this.has(key)) {
|
|
100
|
+
return this.get(key);
|
|
101
|
+
}
|
|
102
|
+
// Try without version
|
|
103
|
+
key = `${name}_${context}`;
|
|
104
|
+
if (this.has(key)) {
|
|
105
|
+
return this.get(key);
|
|
106
|
+
}
|
|
107
|
+
// Try global version
|
|
108
|
+
key = `${name}_Global`;
|
|
109
|
+
if (this.has(key)) {
|
|
110
|
+
return this.get(key);
|
|
111
|
+
}
|
|
112
|
+
// Try just the name
|
|
113
|
+
if (this.has(name)) {
|
|
114
|
+
return this.get(name);
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Remove a component from the registry
|
|
120
|
+
*/
|
|
121
|
+
remove(key) {
|
|
122
|
+
this.components.delete(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
24
125
|
/**
|
|
25
126
|
* Default styles that match the Skip design system
|
|
26
127
|
*/
|
|
@@ -145,6 +246,30 @@ export class SkipReactComponentHost {
|
|
|
145
246
|
this.config = config;
|
|
146
247
|
this.loadReactLibraries();
|
|
147
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Create a plain JavaScript object containing only the components needed by the generated component
|
|
251
|
+
*/
|
|
252
|
+
createComponentsObject() {
|
|
253
|
+
if (!this.config.metadata?.requiredChildComponents) {
|
|
254
|
+
return {}; // No child components required
|
|
255
|
+
}
|
|
256
|
+
const registry = GlobalComponentRegistry.Instance;
|
|
257
|
+
const components = {};
|
|
258
|
+
console.log('Creating components object. Required:', this.config.metadata.requiredChildComponents);
|
|
259
|
+
console.log('Available components in registry:', registry.getRegisteredKeys());
|
|
260
|
+
for (const componentName of this.config.metadata.requiredChildComponents) {
|
|
261
|
+
// Try to resolve the component with metadata context
|
|
262
|
+
const component = registry.getWithFallback(componentName, this.config.metadata.componentContext, this.config.metadata.version);
|
|
263
|
+
if (component) {
|
|
264
|
+
components[componentName] = component;
|
|
265
|
+
console.log(`Found component "${componentName}"`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
console.warn(`Component "${componentName}" not found in registry. Tried contexts: ${this.config.metadata.componentContext}, Global`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return components;
|
|
272
|
+
}
|
|
148
273
|
/**
|
|
149
274
|
* Load React and ReactDOM dynamically
|
|
150
275
|
*/
|
|
@@ -313,6 +438,16 @@ export class SkipReactComponentHost {
|
|
|
313
438
|
this.loadBabel(),
|
|
314
439
|
this.loadCommonLibraries()
|
|
315
440
|
]);
|
|
441
|
+
// Register example components if needed (for testing)
|
|
442
|
+
if (this.config.metadata?.requiredChildComponents?.length) {
|
|
443
|
+
// Check if we need to register example components
|
|
444
|
+
const registry = GlobalComponentRegistry.Instance;
|
|
445
|
+
const hasComponents = this.config.metadata.requiredChildComponents.every(name => registry.getWithFallback(name, this.config.metadata.componentContext, this.config.metadata.version));
|
|
446
|
+
if (!hasComponents && typeof window.registerExampleComponents === 'function') {
|
|
447
|
+
// Try to register example components
|
|
448
|
+
window.registerExampleComponents(this.React, libraries.Chart);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
316
451
|
// Create utility functions
|
|
317
452
|
const createStateUpdater = this.createStateUpdaterFunction();
|
|
318
453
|
const createStandardEventHandler = this.createStandardEventHandlerFunction();
|
|
@@ -470,12 +605,14 @@ export class SkipReactComponentHost {
|
|
|
470
605
|
utilities: utilities,
|
|
471
606
|
userState: this.currentState,
|
|
472
607
|
callbacks: callbacks,
|
|
473
|
-
styles: styles
|
|
608
|
+
styles: styles,
|
|
609
|
+
components: this.createComponentsObject() // Add the filtered components object
|
|
474
610
|
};
|
|
475
611
|
// Debug: Log the data being passed to the component
|
|
476
612
|
console.log('=== SkipReactComponentHost: Rendering component ===');
|
|
477
613
|
console.log('Data:', componentProps.data);
|
|
478
614
|
console.log('User state:', componentProps.userState);
|
|
615
|
+
console.log('Components:', Object.keys(componentProps.components));
|
|
479
616
|
if (componentProps.data?.data_item_0) {
|
|
480
617
|
console.log('First entity:', componentProps.data.data_item_0[0]);
|
|
481
618
|
console.log('Entity count:', componentProps.data.data_item_0.length);
|
|
@@ -665,4 +802,1089 @@ export class SkipReactComponentHost {
|
|
|
665
802
|
};
|
|
666
803
|
}
|
|
667
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* Example child components for testing the component registry system
|
|
807
|
+
* These would normally be defined in separate files and imported
|
|
808
|
+
*/
|
|
809
|
+
// Example SearchBox component
|
|
810
|
+
export const createSearchBoxComponent = (React) => {
|
|
811
|
+
return function SearchBox({ data, config, state, onEvent, styles, statePath }) {
|
|
812
|
+
const [searchValue, setSearchValue] = React.useState(state?.searchValue || '');
|
|
813
|
+
const handleSearch = (value) => {
|
|
814
|
+
setSearchValue(value);
|
|
815
|
+
if (onEvent) {
|
|
816
|
+
onEvent({
|
|
817
|
+
type: 'stateChanged',
|
|
818
|
+
payload: {
|
|
819
|
+
statePath: statePath,
|
|
820
|
+
newState: { searchValue: value }
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
return React.createElement('div', {
|
|
826
|
+
style: {
|
|
827
|
+
padding: styles?.spacing?.md || '16px',
|
|
828
|
+
backgroundColor: styles?.colors?.surface || '#f8f9fa',
|
|
829
|
+
borderRadius: styles?.borders?.radius?.md || '8px',
|
|
830
|
+
marginBottom: styles?.spacing?.md || '16px'
|
|
831
|
+
}
|
|
832
|
+
}, [
|
|
833
|
+
React.createElement('input', {
|
|
834
|
+
key: 'search-input',
|
|
835
|
+
type: 'text',
|
|
836
|
+
value: searchValue,
|
|
837
|
+
onChange: (e) => handleSearch(e.target.value),
|
|
838
|
+
placeholder: config?.placeholder || 'Search...',
|
|
839
|
+
style: {
|
|
840
|
+
width: '100%',
|
|
841
|
+
padding: styles?.spacing?.sm || '8px',
|
|
842
|
+
border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
|
|
843
|
+
borderRadius: styles?.borders?.radius?.sm || '4px',
|
|
844
|
+
fontSize: styles?.typography?.fontSize?.md || '14px',
|
|
845
|
+
fontFamily: styles?.typography?.fontFamily || 'inherit'
|
|
846
|
+
}
|
|
847
|
+
})
|
|
848
|
+
]);
|
|
849
|
+
};
|
|
850
|
+
};
|
|
851
|
+
// Example OrderList component
|
|
852
|
+
export const createOrderListComponent = (React) => {
|
|
853
|
+
return function OrderList({ data, config, state, onEvent, styles, statePath }) {
|
|
854
|
+
const [sortBy, setSortBy] = React.useState(state?.sortBy || 'date');
|
|
855
|
+
const [currentPage, setCurrentPage] = React.useState(state?.currentPage || 1);
|
|
856
|
+
const orders = data || [];
|
|
857
|
+
const pageSize = config?.pageSize || 10;
|
|
858
|
+
const totalPages = Math.ceil(orders.length / pageSize);
|
|
859
|
+
const updateState = (newState) => {
|
|
860
|
+
if (onEvent) {
|
|
861
|
+
onEvent({
|
|
862
|
+
type: 'stateChanged',
|
|
863
|
+
payload: {
|
|
864
|
+
statePath: statePath,
|
|
865
|
+
newState: { ...state, ...newState }
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
const handleSort = (field) => {
|
|
871
|
+
setSortBy(field);
|
|
872
|
+
updateState({ sortBy: field });
|
|
873
|
+
};
|
|
874
|
+
const handlePageChange = (page) => {
|
|
875
|
+
setCurrentPage(page);
|
|
876
|
+
updateState({ currentPage: page });
|
|
877
|
+
};
|
|
878
|
+
// Simple pagination
|
|
879
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
880
|
+
const endIndex = startIndex + pageSize;
|
|
881
|
+
const displayedOrders = orders.slice(startIndex, endIndex);
|
|
882
|
+
return React.createElement('div', {
|
|
883
|
+
style: {
|
|
884
|
+
backgroundColor: styles?.colors?.background || '#ffffff',
|
|
885
|
+
border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
|
|
886
|
+
borderRadius: styles?.borders?.radius?.md || '8px',
|
|
887
|
+
padding: styles?.spacing?.lg || '24px'
|
|
888
|
+
}
|
|
889
|
+
}, [
|
|
890
|
+
// Header
|
|
891
|
+
React.createElement('div', {
|
|
892
|
+
key: 'header',
|
|
893
|
+
style: {
|
|
894
|
+
display: 'flex',
|
|
895
|
+
justifyContent: 'space-between',
|
|
896
|
+
alignItems: 'center',
|
|
897
|
+
marginBottom: styles?.spacing?.md || '16px'
|
|
898
|
+
}
|
|
899
|
+
}, [
|
|
900
|
+
React.createElement('h3', {
|
|
901
|
+
key: 'title',
|
|
902
|
+
style: {
|
|
903
|
+
margin: 0,
|
|
904
|
+
fontSize: styles?.typography?.fontSize?.lg || '16px',
|
|
905
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600'
|
|
906
|
+
}
|
|
907
|
+
}, 'Orders'),
|
|
908
|
+
config?.sortable && React.createElement('select', {
|
|
909
|
+
key: 'sort',
|
|
910
|
+
value: sortBy,
|
|
911
|
+
onChange: (e) => handleSort(e.target.value),
|
|
912
|
+
style: {
|
|
913
|
+
padding: styles?.spacing?.sm || '8px',
|
|
914
|
+
border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
|
|
915
|
+
borderRadius: styles?.borders?.radius?.sm || '4px'
|
|
916
|
+
}
|
|
917
|
+
}, [
|
|
918
|
+
React.createElement('option', { key: 'date', value: 'date' }, 'Sort by Date'),
|
|
919
|
+
React.createElement('option', { key: 'amount', value: 'amount' }, 'Sort by Amount'),
|
|
920
|
+
React.createElement('option', { key: 'status', value: 'status' }, 'Sort by Status')
|
|
921
|
+
])
|
|
922
|
+
]),
|
|
923
|
+
// List
|
|
924
|
+
React.createElement('div', {
|
|
925
|
+
key: 'list',
|
|
926
|
+
style: { marginBottom: styles?.spacing?.md || '16px' }
|
|
927
|
+
}, displayedOrders.length > 0 ? displayedOrders.map((order, index) => React.createElement('div', {
|
|
928
|
+
key: order.id || index,
|
|
929
|
+
style: {
|
|
930
|
+
padding: styles?.spacing?.md || '16px',
|
|
931
|
+
borderBottom: `1px solid ${styles?.colors?.borderLight || '#f1f5f9'}`,
|
|
932
|
+
cursor: 'pointer'
|
|
933
|
+
},
|
|
934
|
+
onClick: () => onEvent && onEvent({
|
|
935
|
+
type: 'navigate',
|
|
936
|
+
payload: { entityName: 'Orders', key: order.id }
|
|
937
|
+
})
|
|
938
|
+
}, [
|
|
939
|
+
React.createElement('div', {
|
|
940
|
+
key: 'order-number',
|
|
941
|
+
style: { fontWeight: styles?.typography?.fontWeight?.medium || '500' }
|
|
942
|
+
}, `Order #${order.orderNumber || order.id}`),
|
|
943
|
+
React.createElement('div', {
|
|
944
|
+
key: 'order-details',
|
|
945
|
+
style: {
|
|
946
|
+
fontSize: styles?.typography?.fontSize?.sm || '12px',
|
|
947
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
948
|
+
}
|
|
949
|
+
}, `$${order.amount || 0} - ${order.status || 'Pending'}`)
|
|
950
|
+
])) : React.createElement('div', {
|
|
951
|
+
style: {
|
|
952
|
+
textAlign: 'center',
|
|
953
|
+
padding: styles?.spacing?.xl || '32px',
|
|
954
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
955
|
+
}
|
|
956
|
+
}, 'No orders found')),
|
|
957
|
+
// Pagination
|
|
958
|
+
totalPages > 1 && React.createElement('div', {
|
|
959
|
+
key: 'pagination',
|
|
960
|
+
style: {
|
|
961
|
+
display: 'flex',
|
|
962
|
+
justifyContent: 'center',
|
|
963
|
+
gap: styles?.spacing?.sm || '8px'
|
|
964
|
+
}
|
|
965
|
+
}, Array.from({ length: totalPages }, (_, i) => i + 1).map(page => React.createElement('button', {
|
|
966
|
+
key: page,
|
|
967
|
+
onClick: () => handlePageChange(page),
|
|
968
|
+
style: {
|
|
969
|
+
padding: `${styles?.spacing?.xs || '4px'} ${styles?.spacing?.sm || '8px'}`,
|
|
970
|
+
border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
|
|
971
|
+
borderRadius: styles?.borders?.radius?.sm || '4px',
|
|
972
|
+
backgroundColor: page === currentPage ? styles?.colors?.primary || '#5B4FE9' : 'transparent',
|
|
973
|
+
color: page === currentPage ? styles?.colors?.textInverse || '#ffffff' : styles?.colors?.text || '#212529',
|
|
974
|
+
cursor: 'pointer'
|
|
975
|
+
}
|
|
976
|
+
}, page)))
|
|
977
|
+
]);
|
|
978
|
+
};
|
|
979
|
+
};
|
|
980
|
+
// Example CategoryChart component
|
|
981
|
+
export const createCategoryChartComponent = (React, Chart) => {
|
|
982
|
+
return function CategoryChart({ data, config, state, onEvent, styles, statePath }) {
|
|
983
|
+
const chartRef = React.useRef(null);
|
|
984
|
+
const chartInstanceRef = React.useRef(null);
|
|
985
|
+
React.useEffect(() => {
|
|
986
|
+
if (!chartRef.current || !Chart)
|
|
987
|
+
return;
|
|
988
|
+
// Destroy existing chart
|
|
989
|
+
if (chartInstanceRef.current) {
|
|
990
|
+
chartInstanceRef.current.destroy();
|
|
991
|
+
}
|
|
992
|
+
// Create new chart
|
|
993
|
+
const ctx = chartRef.current.getContext('2d');
|
|
994
|
+
chartInstanceRef.current = new Chart(ctx, {
|
|
995
|
+
type: 'bar',
|
|
996
|
+
data: {
|
|
997
|
+
labels: data?.map((item) => item.category) || [],
|
|
998
|
+
datasets: [{
|
|
999
|
+
label: 'Sales by Category',
|
|
1000
|
+
data: data?.map((item) => item.value) || [],
|
|
1001
|
+
backgroundColor: styles?.colors?.primary || '#5B4FE9',
|
|
1002
|
+
borderColor: styles?.colors?.primaryHover || '#4940D4',
|
|
1003
|
+
borderWidth: 1
|
|
1004
|
+
}]
|
|
1005
|
+
},
|
|
1006
|
+
options: {
|
|
1007
|
+
responsive: true,
|
|
1008
|
+
maintainAspectRatio: false,
|
|
1009
|
+
plugins: {
|
|
1010
|
+
legend: {
|
|
1011
|
+
display: config?.showLegend !== false
|
|
1012
|
+
},
|
|
1013
|
+
tooltip: {
|
|
1014
|
+
callbacks: {
|
|
1015
|
+
label: (context) => {
|
|
1016
|
+
return `$${context.parsed.y.toLocaleString()}`;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
},
|
|
1021
|
+
scales: {
|
|
1022
|
+
y: {
|
|
1023
|
+
beginAtZero: true,
|
|
1024
|
+
ticks: {
|
|
1025
|
+
callback: (value) => `$${value.toLocaleString()}`
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
onClick: (event, elements) => {
|
|
1030
|
+
if (elements.length > 0 && onEvent) {
|
|
1031
|
+
const index = elements[0].index;
|
|
1032
|
+
const category = data[index];
|
|
1033
|
+
onEvent({
|
|
1034
|
+
type: 'chartClick',
|
|
1035
|
+
payload: { category: category.category, value: category.value }
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
return () => {
|
|
1042
|
+
if (chartInstanceRef.current) {
|
|
1043
|
+
chartInstanceRef.current.destroy();
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}, [data, config, styles]);
|
|
1047
|
+
return React.createElement('div', {
|
|
1048
|
+
style: {
|
|
1049
|
+
backgroundColor: styles?.colors?.background || '#ffffff',
|
|
1050
|
+
border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
|
|
1051
|
+
borderRadius: styles?.borders?.radius?.md || '8px',
|
|
1052
|
+
padding: styles?.spacing?.lg || '24px',
|
|
1053
|
+
height: '400px'
|
|
1054
|
+
}
|
|
1055
|
+
}, [
|
|
1056
|
+
React.createElement('h3', {
|
|
1057
|
+
key: 'title',
|
|
1058
|
+
style: {
|
|
1059
|
+
margin: `0 0 ${styles?.spacing?.md || '16px'} 0`,
|
|
1060
|
+
fontSize: styles?.typography?.fontSize?.lg || '16px',
|
|
1061
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600'
|
|
1062
|
+
}
|
|
1063
|
+
}, 'Sales by Category'),
|
|
1064
|
+
React.createElement('canvas', {
|
|
1065
|
+
key: 'chart',
|
|
1066
|
+
ref: chartRef,
|
|
1067
|
+
style: { maxHeight: '320px' }
|
|
1068
|
+
})
|
|
1069
|
+
]);
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
1072
|
+
// Example ActionCategoryList component string - simulates AI-generated code
|
|
1073
|
+
export const getActionCategoryListComponentString = () => {
|
|
1074
|
+
return String.raw `
|
|
1075
|
+
function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler) {
|
|
1076
|
+
function ActionCategoryList({ data, config, state, onEvent, styles, statePath, utilities, selectedCategoryID }) {
|
|
1077
|
+
const [categories, setCategories] = useState([]);
|
|
1078
|
+
const [expandedCategories, setExpandedCategories] = useState(new Set());
|
|
1079
|
+
const [loading, setLoading] = useState(true);
|
|
1080
|
+
const [error, setError] = useState(null);
|
|
1081
|
+
|
|
1082
|
+
useEffect(() => {
|
|
1083
|
+
loadCategories();
|
|
1084
|
+
}, []);
|
|
1085
|
+
|
|
1086
|
+
const loadCategories = async () => {
|
|
1087
|
+
if (!utilities?.rv) {
|
|
1088
|
+
setError('RunView utility not available');
|
|
1089
|
+
setLoading(false);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
setLoading(true);
|
|
1095
|
+
setError(null);
|
|
1096
|
+
|
|
1097
|
+
const result = await utilities.rv.RunView({
|
|
1098
|
+
EntityName: 'Action Categories',
|
|
1099
|
+
ExtraFilter: '',
|
|
1100
|
+
OrderBy: 'Name',
|
|
1101
|
+
MaxRows: 1000,
|
|
1102
|
+
ResultType: 'entity_object'
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
if (result.Success && result.Results) {
|
|
1106
|
+
setCategories(result.Results);
|
|
1107
|
+
} else {
|
|
1108
|
+
setError(result.ErrorMessage || 'Failed to load categories');
|
|
1109
|
+
}
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
setError('Error loading categories: ' + err);
|
|
1112
|
+
} finally {
|
|
1113
|
+
setLoading(false);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
const handleCategoryClick = (category) => {
|
|
1118
|
+
if (onEvent) {
|
|
1119
|
+
onEvent({
|
|
1120
|
+
type: 'categorySelected',
|
|
1121
|
+
source: 'ActionCategoryList',
|
|
1122
|
+
payload: {
|
|
1123
|
+
categoryID: category.ID,
|
|
1124
|
+
categoryName: category.Name
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
if (loading) {
|
|
1131
|
+
return React.createElement('div', {
|
|
1132
|
+
style: {
|
|
1133
|
+
padding: styles?.spacing?.lg || '24px',
|
|
1134
|
+
textAlign: 'center',
|
|
1135
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
1136
|
+
}
|
|
1137
|
+
}, 'Loading categories...');
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (error) {
|
|
1141
|
+
return React.createElement('div', {
|
|
1142
|
+
style: {
|
|
1143
|
+
padding: styles?.spacing?.lg || '24px',
|
|
1144
|
+
color: styles?.colors?.error || '#dc3545'
|
|
1145
|
+
}
|
|
1146
|
+
}, error);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return React.createElement('div', {
|
|
1150
|
+
style: {
|
|
1151
|
+
height: '100%',
|
|
1152
|
+
overflow: 'auto'
|
|
1153
|
+
}
|
|
1154
|
+
}, [
|
|
1155
|
+
React.createElement('h3', {
|
|
1156
|
+
key: 'title',
|
|
1157
|
+
style: {
|
|
1158
|
+
margin: '0 0 ' + (styles?.spacing?.md || '16px') + ' 0',
|
|
1159
|
+
padding: '0 ' + (styles?.spacing?.md || '16px'),
|
|
1160
|
+
fontSize: styles?.typography?.fontSize?.lg || '16px',
|
|
1161
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600'
|
|
1162
|
+
}
|
|
1163
|
+
}, 'Action Categories'),
|
|
1164
|
+
|
|
1165
|
+
React.createElement('div', {
|
|
1166
|
+
key: 'list',
|
|
1167
|
+
style: {
|
|
1168
|
+
display: 'flex',
|
|
1169
|
+
flexDirection: 'column',
|
|
1170
|
+
gap: styles?.spacing?.xs || '4px'
|
|
1171
|
+
}
|
|
1172
|
+
}, categories.map((category) =>
|
|
1173
|
+
React.createElement('div', {
|
|
1174
|
+
key: category.ID,
|
|
1175
|
+
onClick: () => handleCategoryClick(category),
|
|
1176
|
+
style: {
|
|
1177
|
+
padding: styles?.spacing?.md || '16px',
|
|
1178
|
+
cursor: 'pointer',
|
|
1179
|
+
backgroundColor: selectedCategoryID === category.ID
|
|
1180
|
+
? styles?.colors?.primaryLight || '#e8e6ff'
|
|
1181
|
+
: 'transparent',
|
|
1182
|
+
borderLeft: selectedCategoryID === category.ID
|
|
1183
|
+
? '3px solid ' + (styles?.colors?.primary || '#5B4FE9')
|
|
1184
|
+
: '3px solid transparent',
|
|
1185
|
+
transition: styles?.transitions?.fast || '150ms ease-in-out'
|
|
1186
|
+
},
|
|
1187
|
+
onMouseEnter: (e) => {
|
|
1188
|
+
if (selectedCategoryID !== category.ID) {
|
|
1189
|
+
e.currentTarget.style.backgroundColor = styles?.colors?.surfaceHover || '#f1f5f9';
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
onMouseLeave: (e) => {
|
|
1193
|
+
if (selectedCategoryID !== category.ID) {
|
|
1194
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}, [
|
|
1198
|
+
React.createElement('div', {
|
|
1199
|
+
key: 'name',
|
|
1200
|
+
style: {
|
|
1201
|
+
fontSize: styles?.typography?.fontSize?.md || '14px',
|
|
1202
|
+
fontWeight: selectedCategoryID === category.ID
|
|
1203
|
+
? styles?.typography?.fontWeight?.medium || '500'
|
|
1204
|
+
: styles?.typography?.fontWeight?.regular || '400',
|
|
1205
|
+
color: styles?.colors?.text || '#212529',
|
|
1206
|
+
marginBottom: styles?.spacing?.xs || '4px'
|
|
1207
|
+
}
|
|
1208
|
+
}, category.Name),
|
|
1209
|
+
|
|
1210
|
+
category.Description && React.createElement('div', {
|
|
1211
|
+
key: 'description',
|
|
1212
|
+
style: {
|
|
1213
|
+
fontSize: styles?.typography?.fontSize?.sm || '12px',
|
|
1214
|
+
color: styles?.colors?.textSecondary || '#6c757d',
|
|
1215
|
+
lineHeight: styles?.typography?.lineHeight?.normal || '1.5'
|
|
1216
|
+
}
|
|
1217
|
+
}, category.Description)
|
|
1218
|
+
])
|
|
1219
|
+
))
|
|
1220
|
+
]);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return { component: ActionCategoryList };
|
|
1224
|
+
}
|
|
1225
|
+
`;
|
|
1226
|
+
};
|
|
1227
|
+
// Example ActionList component string - simulates AI-generated code
|
|
1228
|
+
export const getActionListComponentString = () => {
|
|
1229
|
+
return String.raw `
|
|
1230
|
+
function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler) {
|
|
1231
|
+
function ActionList({ data, config, state, onEvent, styles, statePath, utilities, selectedCategoryID }) {
|
|
1232
|
+
const [actions, setActions] = useState([]);
|
|
1233
|
+
const [expandedActions, setExpandedActions] = useState(new Set());
|
|
1234
|
+
const [actionDetails, setActionDetails] = useState({});
|
|
1235
|
+
const [loading, setLoading] = useState(false);
|
|
1236
|
+
const [error, setError] = useState(null);
|
|
1237
|
+
|
|
1238
|
+
useEffect(() => {
|
|
1239
|
+
if (selectedCategoryID) {
|
|
1240
|
+
loadActions(selectedCategoryID);
|
|
1241
|
+
} else {
|
|
1242
|
+
setActions([]);
|
|
1243
|
+
}
|
|
1244
|
+
}, [selectedCategoryID]);
|
|
1245
|
+
|
|
1246
|
+
const loadActions = async (categoryID) => {
|
|
1247
|
+
if (!utilities?.rv) {
|
|
1248
|
+
setError('RunView utility not available');
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
try {
|
|
1253
|
+
setLoading(true);
|
|
1254
|
+
setError(null);
|
|
1255
|
+
|
|
1256
|
+
const result = await utilities.rv.RunView({
|
|
1257
|
+
EntityName: 'Actions',
|
|
1258
|
+
ExtraFilter: 'CategoryID = \'' + categoryID + '\'',
|
|
1259
|
+
OrderBy: 'Name',
|
|
1260
|
+
MaxRows: 1000,
|
|
1261
|
+
ResultType: 'entity_object'
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
if (result.Success && result.Results) {
|
|
1265
|
+
setActions(result.Results);
|
|
1266
|
+
} else {
|
|
1267
|
+
setError(result.ErrorMessage || 'Failed to load actions');
|
|
1268
|
+
}
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
setError('Error loading actions: ' + err);
|
|
1271
|
+
} finally {
|
|
1272
|
+
setLoading(false);
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
const handleActionClick = async (action) => {
|
|
1277
|
+
// Toggle expanded state
|
|
1278
|
+
const newExpanded = new Set(expandedActions);
|
|
1279
|
+
if (newExpanded.has(action.ID)) {
|
|
1280
|
+
newExpanded.delete(action.ID);
|
|
1281
|
+
} else {
|
|
1282
|
+
newExpanded.add(action.ID);
|
|
1283
|
+
// Load details if not already loaded
|
|
1284
|
+
if (!actionDetails[action.ID]) {
|
|
1285
|
+
await loadActionDetails(action.ID);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
setExpandedActions(newExpanded);
|
|
1289
|
+
|
|
1290
|
+
if (onEvent) {
|
|
1291
|
+
onEvent({
|
|
1292
|
+
type: 'actionSelected',
|
|
1293
|
+
source: 'ActionList',
|
|
1294
|
+
payload: {
|
|
1295
|
+
actionID: action.ID,
|
|
1296
|
+
actionName: action.Name
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
const loadActionDetails = async (actionID) => {
|
|
1303
|
+
if (!utilities?.rv) return;
|
|
1304
|
+
|
|
1305
|
+
try {
|
|
1306
|
+
// Load params and result codes in parallel
|
|
1307
|
+
const [paramsResult, resultCodesResult] = await Promise.all([
|
|
1308
|
+
utilities.rv.RunView({
|
|
1309
|
+
EntityName: 'Action Params',
|
|
1310
|
+
ExtraFilter: 'ActionID = \'' + actionID + '\'',
|
|
1311
|
+
OrderBy: 'Name',
|
|
1312
|
+
ResultType: 'entity_object'
|
|
1313
|
+
}),
|
|
1314
|
+
utilities.rv.RunView({
|
|
1315
|
+
EntityName: 'Action Result Codes',
|
|
1316
|
+
ExtraFilter: 'ActionID = \'' + actionID + '\'',
|
|
1317
|
+
OrderBy: 'ResultCode',
|
|
1318
|
+
ResultType: 'entity_object'
|
|
1319
|
+
})
|
|
1320
|
+
]);
|
|
1321
|
+
|
|
1322
|
+
const details = {
|
|
1323
|
+
params: paramsResult.Success ? paramsResult.Results : [],
|
|
1324
|
+
resultCodes: resultCodesResult.Success ? resultCodesResult.Results : []
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
setActionDetails(prev => ({ ...prev, [actionID]: details }));
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
console.error('Error loading action details:', err);
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
if (!selectedCategoryID) {
|
|
1334
|
+
return React.createElement('div', {
|
|
1335
|
+
style: {
|
|
1336
|
+
padding: styles?.spacing?.xl || '32px',
|
|
1337
|
+
textAlign: 'center',
|
|
1338
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
1339
|
+
}
|
|
1340
|
+
}, 'Select a category to view actions');
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
if (loading) {
|
|
1344
|
+
return React.createElement('div', {
|
|
1345
|
+
style: {
|
|
1346
|
+
padding: styles?.spacing?.xl || '32px',
|
|
1347
|
+
textAlign: 'center',
|
|
1348
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
1349
|
+
}
|
|
1350
|
+
}, 'Loading actions...');
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (error) {
|
|
1354
|
+
return React.createElement('div', {
|
|
1355
|
+
style: {
|
|
1356
|
+
padding: styles?.spacing?.lg || '24px',
|
|
1357
|
+
color: styles?.colors?.error || '#dc3545'
|
|
1358
|
+
}
|
|
1359
|
+
}, error);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
if (actions.length === 0) {
|
|
1363
|
+
return React.createElement('div', {
|
|
1364
|
+
style: {
|
|
1365
|
+
padding: styles?.spacing?.xl || '32px',
|
|
1366
|
+
textAlign: 'center',
|
|
1367
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
1368
|
+
}
|
|
1369
|
+
}, 'No actions found in this category');
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
return React.createElement('div', {
|
|
1373
|
+
style: {
|
|
1374
|
+
padding: styles?.spacing?.lg || '24px'
|
|
1375
|
+
}
|
|
1376
|
+
}, [
|
|
1377
|
+
React.createElement('h3', {
|
|
1378
|
+
key: 'title',
|
|
1379
|
+
style: {
|
|
1380
|
+
margin: '0 0 ' + (styles?.spacing?.lg || '24px') + ' 0',
|
|
1381
|
+
fontSize: styles?.typography?.fontSize?.lg || '16px',
|
|
1382
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600'
|
|
1383
|
+
}
|
|
1384
|
+
}, 'Actions (' + actions.length + ')'),
|
|
1385
|
+
|
|
1386
|
+
React.createElement('div', {
|
|
1387
|
+
key: 'grid',
|
|
1388
|
+
style: {
|
|
1389
|
+
display: 'grid',
|
|
1390
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
|
|
1391
|
+
gap: styles?.spacing?.md || '16px'
|
|
1392
|
+
}
|
|
1393
|
+
}, actions.map((action) => {
|
|
1394
|
+
const isExpanded = expandedActions.has(action.ID);
|
|
1395
|
+
const details = actionDetails[action.ID] || { params: [], resultCodes: [] };
|
|
1396
|
+
|
|
1397
|
+
return React.createElement('div', {
|
|
1398
|
+
key: action.ID,
|
|
1399
|
+
style: {
|
|
1400
|
+
backgroundColor: styles?.colors?.surface || '#f8f9fa',
|
|
1401
|
+
border: '1px solid ' + (styles?.colors?.border || '#dee2e6'),
|
|
1402
|
+
borderRadius: styles?.borders?.radius?.md || '8px',
|
|
1403
|
+
overflow: 'hidden',
|
|
1404
|
+
transition: styles?.transitions?.fast || '150ms ease-in-out'
|
|
1405
|
+
}
|
|
1406
|
+
}, [
|
|
1407
|
+
React.createElement('div', {
|
|
1408
|
+
key: 'header',
|
|
1409
|
+
onClick: () => handleActionClick(action),
|
|
1410
|
+
style: {
|
|
1411
|
+
padding: styles?.spacing?.md || '16px',
|
|
1412
|
+
cursor: 'pointer'
|
|
1413
|
+
},
|
|
1414
|
+
onMouseEnter: (e) => {
|
|
1415
|
+
e.currentTarget.style.backgroundColor = styles?.colors?.surfaceHover || '#f1f5f9';
|
|
1416
|
+
},
|
|
1417
|
+
onMouseLeave: (e) => {
|
|
1418
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
1419
|
+
}
|
|
1420
|
+
}, [
|
|
1421
|
+
React.createElement('div', {
|
|
1422
|
+
key: 'header-content',
|
|
1423
|
+
style: {
|
|
1424
|
+
display: 'flex',
|
|
1425
|
+
alignItems: 'center',
|
|
1426
|
+
justifyContent: 'space-between'
|
|
1427
|
+
}
|
|
1428
|
+
}, [
|
|
1429
|
+
React.createElement('div', { key: 'main-content' }, [
|
|
1430
|
+
React.createElement('div', {
|
|
1431
|
+
key: 'name',
|
|
1432
|
+
style: {
|
|
1433
|
+
fontSize: styles?.typography?.fontSize?.md || '14px',
|
|
1434
|
+
fontWeight: styles?.typography?.fontWeight?.medium || '500',
|
|
1435
|
+
color: styles?.colors?.text || '#212529',
|
|
1436
|
+
marginBottom: styles?.spacing?.xs || '4px'
|
|
1437
|
+
}
|
|
1438
|
+
}, action.Name),
|
|
1439
|
+
|
|
1440
|
+
action.Description && React.createElement('div', {
|
|
1441
|
+
key: 'description',
|
|
1442
|
+
style: {
|
|
1443
|
+
fontSize: styles?.typography?.fontSize?.sm || '12px',
|
|
1444
|
+
color: styles?.colors?.textSecondary || '#6c757d',
|
|
1445
|
+
lineHeight: styles?.typography?.lineHeight?.normal || '1.5',
|
|
1446
|
+
marginBottom: styles?.spacing?.xs || '4px'
|
|
1447
|
+
}
|
|
1448
|
+
}, action.Description),
|
|
1449
|
+
|
|
1450
|
+
React.createElement('div', {
|
|
1451
|
+
key: 'metadata',
|
|
1452
|
+
style: {
|
|
1453
|
+
display: 'flex',
|
|
1454
|
+
gap: styles?.spacing?.md || '16px',
|
|
1455
|
+
fontSize: styles?.typography?.fontSize?.xs || '11px',
|
|
1456
|
+
color: styles?.colors?.textTertiary || '#94a3b8'
|
|
1457
|
+
}
|
|
1458
|
+
}, [
|
|
1459
|
+
action.Type && React.createElement('span', { key: 'type' }, 'Type: ' + action.Type),
|
|
1460
|
+
action.Status && React.createElement('span', { key: 'status' }, 'Status: ' + action.Status)
|
|
1461
|
+
])
|
|
1462
|
+
]),
|
|
1463
|
+
|
|
1464
|
+
React.createElement('span', {
|
|
1465
|
+
key: 'expand-icon',
|
|
1466
|
+
style: {
|
|
1467
|
+
fontSize: '12px',
|
|
1468
|
+
color: styles?.colors?.textSecondary || '#6c757d',
|
|
1469
|
+
marginLeft: '8px'
|
|
1470
|
+
}
|
|
1471
|
+
}, isExpanded ? '▼' : '▶')
|
|
1472
|
+
])
|
|
1473
|
+
]),
|
|
1474
|
+
|
|
1475
|
+
isExpanded && React.createElement('div', {
|
|
1476
|
+
key: 'details',
|
|
1477
|
+
style: {
|
|
1478
|
+
borderTop: '1px solid ' + (styles?.colors?.border || '#dee2e6'),
|
|
1479
|
+
backgroundColor: styles?.colors?.background || '#ffffff'
|
|
1480
|
+
}
|
|
1481
|
+
}, [
|
|
1482
|
+
// Parameters section
|
|
1483
|
+
details.params.length > 0 && React.createElement('div', {
|
|
1484
|
+
key: 'params',
|
|
1485
|
+
style: {
|
|
1486
|
+
padding: styles?.spacing?.md || '16px',
|
|
1487
|
+
borderBottom: '1px solid ' + (styles?.colors?.borderLight || '#f1f5f9')
|
|
1488
|
+
}
|
|
1489
|
+
}, [
|
|
1490
|
+
React.createElement('h4', {
|
|
1491
|
+
key: 'params-title',
|
|
1492
|
+
style: {
|
|
1493
|
+
margin: '0 0 ' + (styles?.spacing?.sm || '8px') + ' 0',
|
|
1494
|
+
fontSize: styles?.typography?.fontSize?.sm || '13px',
|
|
1495
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600',
|
|
1496
|
+
color: styles?.colors?.text || '#212529'
|
|
1497
|
+
}
|
|
1498
|
+
}, 'Parameters'),
|
|
1499
|
+
|
|
1500
|
+
React.createElement('div', {
|
|
1501
|
+
key: 'params-list',
|
|
1502
|
+
style: {
|
|
1503
|
+
display: 'flex',
|
|
1504
|
+
flexDirection: 'column',
|
|
1505
|
+
gap: styles?.spacing?.xs || '4px'
|
|
1506
|
+
}
|
|
1507
|
+
}, details.params.map(param =>
|
|
1508
|
+
React.createElement('div', {
|
|
1509
|
+
key: param.ID,
|
|
1510
|
+
style: {
|
|
1511
|
+
display: 'flex',
|
|
1512
|
+
alignItems: 'center',
|
|
1513
|
+
fontSize: styles?.typography?.fontSize?.xs || '12px',
|
|
1514
|
+
padding: '4px 0'
|
|
1515
|
+
}
|
|
1516
|
+
}, [
|
|
1517
|
+
React.createElement('span', {
|
|
1518
|
+
key: 'name',
|
|
1519
|
+
style: {
|
|
1520
|
+
fontWeight: styles?.typography?.fontWeight?.medium || '500',
|
|
1521
|
+
color: styles?.colors?.text || '#212529',
|
|
1522
|
+
marginRight: '8px'
|
|
1523
|
+
}
|
|
1524
|
+
}, param.Name),
|
|
1525
|
+
|
|
1526
|
+
React.createElement('span', {
|
|
1527
|
+
key: 'type',
|
|
1528
|
+
style: {
|
|
1529
|
+
color: styles?.colors?.textSecondary || '#6c757d',
|
|
1530
|
+
fontSize: '11px',
|
|
1531
|
+
backgroundColor: styles?.colors?.surfaceHover || '#f1f5f9',
|
|
1532
|
+
padding: '2px 6px',
|
|
1533
|
+
borderRadius: '3px',
|
|
1534
|
+
marginRight: '8px'
|
|
1535
|
+
}
|
|
1536
|
+
}, param.Type),
|
|
1537
|
+
|
|
1538
|
+
param.IsRequired && React.createElement('span', {
|
|
1539
|
+
key: 'required',
|
|
1540
|
+
style: {
|
|
1541
|
+
color: styles?.colors?.error || '#dc3545',
|
|
1542
|
+
fontSize: '10px',
|
|
1543
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600'
|
|
1544
|
+
}
|
|
1545
|
+
}, 'REQUIRED'),
|
|
1546
|
+
|
|
1547
|
+
param.Description && React.createElement('span', {
|
|
1548
|
+
key: 'desc',
|
|
1549
|
+
style: {
|
|
1550
|
+
color: styles?.colors?.textTertiary || '#94a3b8',
|
|
1551
|
+
marginLeft: 'auto',
|
|
1552
|
+
fontSize: '11px'
|
|
1553
|
+
}
|
|
1554
|
+
}, param.Description)
|
|
1555
|
+
])
|
|
1556
|
+
))
|
|
1557
|
+
]),
|
|
1558
|
+
|
|
1559
|
+
// Result codes section
|
|
1560
|
+
details.resultCodes.length > 0 && React.createElement('div', {
|
|
1561
|
+
key: 'result-codes',
|
|
1562
|
+
style: {
|
|
1563
|
+
padding: styles?.spacing?.md || '16px'
|
|
1564
|
+
}
|
|
1565
|
+
}, [
|
|
1566
|
+
React.createElement('h4', {
|
|
1567
|
+
key: 'codes-title',
|
|
1568
|
+
style: {
|
|
1569
|
+
margin: '0 0 ' + (styles?.spacing?.sm || '8px') + ' 0',
|
|
1570
|
+
fontSize: styles?.typography?.fontSize?.sm || '13px',
|
|
1571
|
+
fontWeight: styles?.typography?.fontWeight?.semibold || '600',
|
|
1572
|
+
color: styles?.colors?.text || '#212529'
|
|
1573
|
+
}
|
|
1574
|
+
}, 'Result Codes'),
|
|
1575
|
+
|
|
1576
|
+
React.createElement('div', {
|
|
1577
|
+
key: 'codes-list',
|
|
1578
|
+
style: {
|
|
1579
|
+
display: 'flex',
|
|
1580
|
+
flexDirection: 'column',
|
|
1581
|
+
gap: styles?.spacing?.xs || '4px'
|
|
1582
|
+
}
|
|
1583
|
+
}, details.resultCodes.map(code =>
|
|
1584
|
+
React.createElement('div', {
|
|
1585
|
+
key: code.ID,
|
|
1586
|
+
style: {
|
|
1587
|
+
display: 'flex',
|
|
1588
|
+
alignItems: 'center',
|
|
1589
|
+
fontSize: styles?.typography?.fontSize?.xs || '12px',
|
|
1590
|
+
padding: '4px 0'
|
|
1591
|
+
}
|
|
1592
|
+
}, [
|
|
1593
|
+
React.createElement('span', {
|
|
1594
|
+
key: 'code',
|
|
1595
|
+
style: {
|
|
1596
|
+
fontFamily: 'monospace',
|
|
1597
|
+
fontWeight: styles?.typography?.fontWeight?.medium || '500',
|
|
1598
|
+
color: code.IsSuccess ? styles?.colors?.success || '#10b981' : styles?.colors?.error || '#dc3545',
|
|
1599
|
+
marginRight: '8px'
|
|
1600
|
+
}
|
|
1601
|
+
}, code.ResultCode),
|
|
1602
|
+
|
|
1603
|
+
code.Description && React.createElement('span', {
|
|
1604
|
+
key: 'desc',
|
|
1605
|
+
style: {
|
|
1606
|
+
color: styles?.colors?.textSecondary || '#6c757d'
|
|
1607
|
+
}
|
|
1608
|
+
}, code.Description)
|
|
1609
|
+
])
|
|
1610
|
+
))
|
|
1611
|
+
])
|
|
1612
|
+
])
|
|
1613
|
+
]);
|
|
1614
|
+
}))
|
|
1615
|
+
]);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
return { component: ActionList };
|
|
1619
|
+
}
|
|
1620
|
+
`;
|
|
1621
|
+
};
|
|
1622
|
+
// Example composite ActionBrowser component string - simulates AI-generated code
|
|
1623
|
+
export const getActionBrowserComponentString = () => {
|
|
1624
|
+
return String.raw `
|
|
1625
|
+
function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler) {
|
|
1626
|
+
function ActionBrowser({ data, utilities, userState, callbacks, styles, components }) {
|
|
1627
|
+
const [fullUserState, setFullUserState] = useState({
|
|
1628
|
+
selectedCategoryID: null,
|
|
1629
|
+
selectedActionID: null,
|
|
1630
|
+
categoryList: {},
|
|
1631
|
+
actionList: {},
|
|
1632
|
+
...userState
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
// Destructure child components from registry
|
|
1636
|
+
const { ActionCategoryList, ActionList } = components;
|
|
1637
|
+
|
|
1638
|
+
const updateUserState = (stateUpdate) => {
|
|
1639
|
+
const newState = { ...fullUserState, ...stateUpdate };
|
|
1640
|
+
setFullUserState(newState);
|
|
1641
|
+
if (callbacks?.UpdateUserState) {
|
|
1642
|
+
callbacks.UpdateUserState(newState);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
const handleComponentEvent = (event) => {
|
|
1647
|
+
if (event.type === 'categorySelected' && event.source === 'ActionCategoryList') {
|
|
1648
|
+
updateUserState({
|
|
1649
|
+
selectedCategoryID: event.payload.categoryID,
|
|
1650
|
+
selectedActionID: null // Reset action selection
|
|
1651
|
+
});
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
if (event.type === 'actionSelected' && event.source === 'ActionList') {
|
|
1656
|
+
updateUserState({
|
|
1657
|
+
selectedActionID: event.payload.actionID
|
|
1658
|
+
});
|
|
1659
|
+
if (callbacks?.NotifyEvent) {
|
|
1660
|
+
callbacks.NotifyEvent('actionSelected', event.payload);
|
|
1661
|
+
}
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// Handle standard state changes
|
|
1666
|
+
if (event.type === 'stateChanged') {
|
|
1667
|
+
const update = {};
|
|
1668
|
+
update[event.payload.statePath] = event.payload.newState;
|
|
1669
|
+
updateUserState(update);
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
return React.createElement('div', {
|
|
1674
|
+
style: {
|
|
1675
|
+
display: 'flex',
|
|
1676
|
+
height: '100%',
|
|
1677
|
+
minHeight: '600px',
|
|
1678
|
+
backgroundColor: styles.colors.background,
|
|
1679
|
+
fontFamily: styles.typography.fontFamily
|
|
1680
|
+
}
|
|
1681
|
+
}, [
|
|
1682
|
+
// Left sidebar with categories
|
|
1683
|
+
React.createElement('div', {
|
|
1684
|
+
key: 'sidebar',
|
|
1685
|
+
style: {
|
|
1686
|
+
width: '300px',
|
|
1687
|
+
backgroundColor: styles.colors.surface,
|
|
1688
|
+
borderRight: '1px solid ' + styles.colors.border,
|
|
1689
|
+
overflow: 'hidden',
|
|
1690
|
+
display: 'flex',
|
|
1691
|
+
flexDirection: 'column'
|
|
1692
|
+
}
|
|
1693
|
+
}, [
|
|
1694
|
+
ActionCategoryList && React.createElement(ActionCategoryList, {
|
|
1695
|
+
key: 'categories',
|
|
1696
|
+
data: [],
|
|
1697
|
+
config: {},
|
|
1698
|
+
state: fullUserState.categoryList || {},
|
|
1699
|
+
onEvent: handleComponentEvent,
|
|
1700
|
+
styles: styles,
|
|
1701
|
+
utilities: utilities,
|
|
1702
|
+
statePath: 'categoryList',
|
|
1703
|
+
selectedCategoryID: fullUserState.selectedCategoryID
|
|
1704
|
+
})
|
|
1705
|
+
]),
|
|
1706
|
+
|
|
1707
|
+
// Main content area with actions
|
|
1708
|
+
React.createElement('div', {
|
|
1709
|
+
key: 'main',
|
|
1710
|
+
style: {
|
|
1711
|
+
flex: 1,
|
|
1712
|
+
overflow: 'auto'
|
|
1713
|
+
}
|
|
1714
|
+
}, [
|
|
1715
|
+
ActionList && React.createElement(ActionList, {
|
|
1716
|
+
key: 'actions',
|
|
1717
|
+
data: [],
|
|
1718
|
+
config: {},
|
|
1719
|
+
state: fullUserState.actionList || {},
|
|
1720
|
+
onEvent: handleComponentEvent,
|
|
1721
|
+
styles: styles,
|
|
1722
|
+
utilities: utilities,
|
|
1723
|
+
statePath: 'actionList',
|
|
1724
|
+
selectedCategoryID: fullUserState.selectedCategoryID
|
|
1725
|
+
})
|
|
1726
|
+
])
|
|
1727
|
+
]);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
return { component: ActionBrowser };
|
|
1731
|
+
}
|
|
1732
|
+
`;
|
|
1733
|
+
};
|
|
1734
|
+
/**
|
|
1735
|
+
* Unit tests for GlobalComponentRegistry
|
|
1736
|
+
* These would normally be in a separate .spec.ts file
|
|
1737
|
+
* Run these tests to ensure the registry works correctly
|
|
1738
|
+
*/
|
|
1739
|
+
export function testGlobalComponentRegistry() {
|
|
1740
|
+
const registry = GlobalComponentRegistry.Instance;
|
|
1741
|
+
const testResults = [];
|
|
1742
|
+
const assert = (condition, testName, error) => {
|
|
1743
|
+
testResults.push({ test: testName, passed: condition, error: condition ? undefined : error });
|
|
1744
|
+
if (!condition) {
|
|
1745
|
+
console.error(`Test failed: ${testName}`, error);
|
|
1746
|
+
}
|
|
1747
|
+
else {
|
|
1748
|
+
console.log(`Test passed: ${testName}`);
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
// Test 1: Singleton pattern
|
|
1752
|
+
const registry2 = GlobalComponentRegistry.Instance;
|
|
1753
|
+
assert(registry === registry2, 'Singleton pattern', 'Multiple instances created');
|
|
1754
|
+
// Test 2: Basic registration and retrieval
|
|
1755
|
+
registry.clear(); // Start fresh
|
|
1756
|
+
const mockComponent = { name: 'MockComponent' };
|
|
1757
|
+
registry.register('TestComponent', mockComponent);
|
|
1758
|
+
assert(registry.get('TestComponent') === mockComponent, 'Basic register/get', 'Component not retrieved correctly');
|
|
1759
|
+
// Test 3: Has method
|
|
1760
|
+
assert(registry.has('TestComponent') === true, 'Has method - existing', 'Should return true for existing component');
|
|
1761
|
+
assert(registry.has('NonExistent') === false, 'Has method - non-existing', 'Should return false for non-existing component');
|
|
1762
|
+
// Test 4: Register with metadata
|
|
1763
|
+
const mockSearchBox = { name: 'SearchBox' };
|
|
1764
|
+
registry.registerWithMetadata('SearchBox', 'CRM', 'v1', mockSearchBox, 'CRM-specific search');
|
|
1765
|
+
assert(registry.get('SearchBox_CRM_v1') === mockSearchBox, 'Register with metadata', 'Component not found with metadata key');
|
|
1766
|
+
assert(registry.get('SearchBox_CRM') === mockSearchBox, 'Backwards compatibility key', 'Component not found with context-only key');
|
|
1767
|
+
// Test 5: Multiple versions
|
|
1768
|
+
const mockSearchBoxV2 = { name: 'SearchBoxV2' };
|
|
1769
|
+
registry.registerWithMetadata('SearchBox', 'CRM', 'v2', mockSearchBoxV2);
|
|
1770
|
+
assert(registry.get('SearchBox_CRM_v1') === mockSearchBox, 'Version v1 still accessible', 'v1 component overwritten');
|
|
1771
|
+
assert(registry.get('SearchBox_CRM_v2') === mockSearchBoxV2, 'Version v2 accessible', 'v2 component not found');
|
|
1772
|
+
// Test 6: GetWithFallback - exact match
|
|
1773
|
+
const found1 = registry.getWithFallback('SearchBox', 'CRM', 'v2');
|
|
1774
|
+
assert(found1 === mockSearchBoxV2, 'GetWithFallback - exact match', 'Should find exact version match');
|
|
1775
|
+
// Test 7: GetWithFallback - context fallback
|
|
1776
|
+
const found2 = registry.getWithFallback('SearchBox', 'CRM', 'v3'); // v3 doesn't exist
|
|
1777
|
+
assert(found2 === mockSearchBoxV2, 'GetWithFallback - context fallback', 'Should fall back to context match');
|
|
1778
|
+
// Test 8: GetWithFallback - global fallback
|
|
1779
|
+
const globalComponent = { name: 'GlobalSearch' };
|
|
1780
|
+
registry.register('SearchBox_Global', globalComponent);
|
|
1781
|
+
const found3 = registry.getWithFallback('SearchBox', 'Finance', 'v1'); // Finance context doesn't exist
|
|
1782
|
+
assert(found3 === globalComponent, 'GetWithFallback - global fallback', 'Should fall back to global component');
|
|
1783
|
+
// Test 9: GetWithFallback - name only fallback
|
|
1784
|
+
const nameOnlyComponent = { name: 'NameOnly' };
|
|
1785
|
+
registry.register('UniqueComponent', nameOnlyComponent);
|
|
1786
|
+
const found4 = registry.getWithFallback('UniqueComponent', 'Any', 'v1');
|
|
1787
|
+
assert(found4 === nameOnlyComponent, 'GetWithFallback - name only fallback', 'Should fall back to name-only registration');
|
|
1788
|
+
// Test 10: GetWithFallback - not found
|
|
1789
|
+
const found5 = registry.getWithFallback('NotRegistered', 'Any', 'v1');
|
|
1790
|
+
assert(found5 === null, 'GetWithFallback - not found', 'Should return null when component not found');
|
|
1791
|
+
// Test 11: Get registered keys
|
|
1792
|
+
const keys = registry.getRegisteredKeys();
|
|
1793
|
+
assert(keys.includes('SearchBox_CRM_v1'), 'Get registered keys', 'Should include registered components');
|
|
1794
|
+
assert(keys.length > 5, 'Multiple registrations', `Should have multiple keys registered, found ${keys.length}`);
|
|
1795
|
+
// Test 12: Clear registry
|
|
1796
|
+
registry.clear();
|
|
1797
|
+
assert(registry.getRegisteredKeys().length === 0, 'Clear registry', 'Registry should be empty after clear');
|
|
1798
|
+
// Summary
|
|
1799
|
+
const passed = testResults.filter(r => r.passed).length;
|
|
1800
|
+
const failed = testResults.filter(r => !r.passed).length;
|
|
1801
|
+
console.log(`\nTest Summary: ${passed} passed, ${failed} failed out of ${testResults.length} total tests`);
|
|
1802
|
+
// Important: Clear the registry at the end of tests so it's ready for actual use
|
|
1803
|
+
registry.clear();
|
|
1804
|
+
return testResults;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Compile and register a component from string code
|
|
1808
|
+
* This simulates how AI-generated components are processed
|
|
1809
|
+
*/
|
|
1810
|
+
export async function compileAndRegisterComponent(componentName, componentCode, context = 'Global', version = 'v1') {
|
|
1811
|
+
const registry = GlobalComponentRegistry.Instance;
|
|
1812
|
+
try {
|
|
1813
|
+
// Get Babel for transpilation
|
|
1814
|
+
const Babel = window.Babel;
|
|
1815
|
+
if (!Babel) {
|
|
1816
|
+
console.error('Babel not loaded - cannot compile component');
|
|
1817
|
+
return false;
|
|
1818
|
+
}
|
|
1819
|
+
// Transpile the code
|
|
1820
|
+
const transpiledCode = Babel.transform(componentCode, {
|
|
1821
|
+
presets: ['react'],
|
|
1822
|
+
filename: `${componentName}.jsx`
|
|
1823
|
+
}).code;
|
|
1824
|
+
// Get React and other dependencies
|
|
1825
|
+
const React = window.React;
|
|
1826
|
+
const ReactDOM = window.ReactDOM;
|
|
1827
|
+
const libraries = {
|
|
1828
|
+
antd: window.antd,
|
|
1829
|
+
ReactBootstrap: window.ReactBootstrap,
|
|
1830
|
+
d3: window.d3,
|
|
1831
|
+
Chart: window.Chart,
|
|
1832
|
+
_: window._,
|
|
1833
|
+
dayjs: window.dayjs
|
|
1834
|
+
};
|
|
1835
|
+
// Create the component factory
|
|
1836
|
+
const createComponent = new Function('React', 'ReactDOM', 'useState', 'useEffect', 'useCallback', 'createStateUpdater', 'createStandardEventHandler', 'libraries', `${transpiledCode}; return createComponent;`)(React, ReactDOM, React.useState, React.useEffect, React.useCallback, () => { }, // createStateUpdater placeholder
|
|
1837
|
+
() => { }, // createStandardEventHandler placeholder
|
|
1838
|
+
libraries);
|
|
1839
|
+
// Get the component from the factory
|
|
1840
|
+
const componentResult = createComponent(React, ReactDOM, React.useState, React.useEffect, React.useCallback, () => { }, // createStateUpdater
|
|
1841
|
+
() => { } // createStandardEventHandler
|
|
1842
|
+
);
|
|
1843
|
+
// Register the component
|
|
1844
|
+
registry.registerWithMetadata(componentName, context, version, componentResult.component);
|
|
1845
|
+
console.log(`Compiled and registered component: ${componentName}`);
|
|
1846
|
+
return true;
|
|
1847
|
+
}
|
|
1848
|
+
catch (error) {
|
|
1849
|
+
console.error(`Failed to compile component ${componentName}:`, error);
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Helper function to register example components for testing
|
|
1855
|
+
* Call this during application initialization
|
|
1856
|
+
*/
|
|
1857
|
+
export async function registerExampleComponents(React, Chart) {
|
|
1858
|
+
const registry = GlobalComponentRegistry.Instance;
|
|
1859
|
+
// Get React reference - either passed in or from window
|
|
1860
|
+
React = React || window.React;
|
|
1861
|
+
Chart = Chart || window.Chart;
|
|
1862
|
+
// Also make this function available globally for debugging
|
|
1863
|
+
window.registerExampleComponents = registerExampleComponents;
|
|
1864
|
+
window.compileAndRegisterComponent = compileAndRegisterComponent;
|
|
1865
|
+
if (React) {
|
|
1866
|
+
// Register simple test components (these use the real React components directly)
|
|
1867
|
+
registry.registerWithMetadata('SearchBox', 'CRM', 'v1', createSearchBoxComponent(React));
|
|
1868
|
+
registry.registerWithMetadata('SearchBox', 'Global', 'v1', createSearchBoxComponent(React));
|
|
1869
|
+
// Register OrderList variants
|
|
1870
|
+
registry.registerWithMetadata('OrderList', 'Standard', 'v1', createOrderListComponent(React));
|
|
1871
|
+
registry.registerWithMetadata('OrderList', 'Advanced', 'v1', createOrderListComponent(React));
|
|
1872
|
+
// Register chart components
|
|
1873
|
+
if (Chart) {
|
|
1874
|
+
registry.registerWithMetadata('CategoryChart', 'Global', 'v1', createCategoryChartComponent(React, Chart));
|
|
1875
|
+
}
|
|
1876
|
+
// Compile and register Action browser components from strings
|
|
1877
|
+
// This simulates how AI-generated components are processed
|
|
1878
|
+
await compileAndRegisterComponent('ActionCategoryList', getActionCategoryListComponentString(), 'Global', 'v1');
|
|
1879
|
+
await compileAndRegisterComponent('ActionList', getActionListComponentString(), 'Global', 'v1');
|
|
1880
|
+
await compileAndRegisterComponent('ActionBrowser', getActionBrowserComponentString(), 'Global', 'v1');
|
|
1881
|
+
console.log('Example components registered successfully');
|
|
1882
|
+
console.log('Registered components:', registry.getRegisteredKeys());
|
|
1883
|
+
return true;
|
|
1884
|
+
}
|
|
1885
|
+
else {
|
|
1886
|
+
console.warn('React not found - cannot register example components');
|
|
1887
|
+
return false;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
668
1890
|
//# sourceMappingURL=skip-react-component-host.js.map
|