@memberjunction/server 2.111.1 → 2.112.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.
Files changed (250) hide show
  1. package/dist/agents/skip-agent.d.ts +4 -4
  2. package/dist/agents/skip-agent.d.ts.map +1 -1
  3. package/dist/agents/skip-agent.js +808 -951
  4. package/dist/agents/skip-agent.js.map +1 -1
  5. package/dist/agents/skip-sdk.d.ts +1 -1
  6. package/dist/agents/skip-sdk.d.ts.map +1 -1
  7. package/dist/agents/skip-sdk.js +53 -43
  8. package/dist/agents/skip-sdk.js.map +1 -1
  9. package/dist/apolloServer/index.js +1 -1
  10. package/dist/auth/AuthProviderFactory.d.ts +1 -1
  11. package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
  12. package/dist/auth/AuthProviderFactory.js +1 -3
  13. package/dist/auth/AuthProviderFactory.js.map +1 -1
  14. package/dist/auth/BaseAuthProvider.d.ts +1 -1
  15. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  16. package/dist/auth/BaseAuthProvider.js +3 -2
  17. package/dist/auth/BaseAuthProvider.js.map +1 -1
  18. package/dist/auth/IAuthProvider.d.ts +1 -1
  19. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  20. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  21. package/dist/auth/exampleNewUserSubClass.js +1 -1
  22. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  23. package/dist/auth/index.d.ts +1 -1
  24. package/dist/auth/index.d.ts.map +1 -1
  25. package/dist/auth/index.js +6 -6
  26. package/dist/auth/index.js.map +1 -1
  27. package/dist/auth/initializeProviders.js +1 -1
  28. package/dist/auth/initializeProviders.js.map +1 -1
  29. package/dist/auth/newUsers.d.ts +1 -1
  30. package/dist/auth/newUsers.d.ts.map +1 -1
  31. package/dist/auth/newUsers.js +7 -7
  32. package/dist/auth/newUsers.js.map +1 -1
  33. package/dist/auth/providers/Auth0Provider.d.ts +1 -1
  34. package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
  35. package/dist/auth/providers/Auth0Provider.js +1 -1
  36. package/dist/auth/providers/Auth0Provider.js.map +1 -1
  37. package/dist/auth/providers/CognitoProvider.d.ts +1 -1
  38. package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
  39. package/dist/auth/providers/CognitoProvider.js +3 -6
  40. package/dist/auth/providers/CognitoProvider.js.map +1 -1
  41. package/dist/auth/providers/GoogleProvider.d.ts +1 -1
  42. package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
  43. package/dist/auth/providers/GoogleProvider.js +1 -1
  44. package/dist/auth/providers/GoogleProvider.js.map +1 -1
  45. package/dist/auth/providers/MSALProvider.d.ts +1 -1
  46. package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
  47. package/dist/auth/providers/MSALProvider.js +1 -1
  48. package/dist/auth/providers/MSALProvider.js.map +1 -1
  49. package/dist/auth/providers/OktaProvider.d.ts +1 -1
  50. package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
  51. package/dist/auth/providers/OktaProvider.js +1 -1
  52. package/dist/auth/providers/OktaProvider.js.map +1 -1
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +22 -10
  55. package/dist/config.js.map +1 -1
  56. package/dist/context.d.ts +1 -1
  57. package/dist/context.d.ts.map +1 -1
  58. package/dist/context.js +9 -7
  59. package/dist/context.js.map +1 -1
  60. package/dist/entitySubclasses/entityPermissions.server.d.ts +1 -1
  61. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  62. package/dist/entitySubclasses/entityPermissions.server.js +1 -1
  63. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  64. package/dist/generated/generated.d.ts +648 -648
  65. package/dist/generated/generated.d.ts.map +1 -1
  66. package/dist/generated/generated.js +2986 -1133
  67. package/dist/generated/generated.js.map +1 -1
  68. package/dist/generic/KeyInputOutputTypes.d.ts +1 -1
  69. package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -1
  70. package/dist/generic/KeyInputOutputTypes.js +1 -1
  71. package/dist/generic/KeyInputOutputTypes.js.map +1 -1
  72. package/dist/generic/ResolverBase.d.ts +1 -1
  73. package/dist/generic/ResolverBase.d.ts.map +1 -1
  74. package/dist/generic/ResolverBase.js +15 -10
  75. package/dist/generic/ResolverBase.js.map +1 -1
  76. package/dist/generic/RunViewResolver.d.ts +1 -1
  77. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  78. package/dist/generic/RunViewResolver.js +15 -15
  79. package/dist/generic/RunViewResolver.js.map +1 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +18 -9
  82. package/dist/index.js.map +1 -1
  83. package/dist/resolvers/ActionResolver.d.ts +2 -2
  84. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  85. package/dist/resolvers/ActionResolver.js +28 -30
  86. package/dist/resolvers/ActionResolver.js.map +1 -1
  87. package/dist/resolvers/AskSkipResolver.d.ts +2 -2
  88. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  89. package/dist/resolvers/AskSkipResolver.js +60 -50
  90. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  91. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  92. package/dist/resolvers/ComponentRegistryResolver.js +36 -38
  93. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  94. package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
  95. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  96. package/dist/resolvers/CreateQueryResolver.js +43 -40
  97. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  98. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  99. package/dist/resolvers/DatasetResolver.js +1 -1
  100. package/dist/resolvers/DatasetResolver.js.map +1 -1
  101. package/dist/resolvers/EntityRecordNameResolver.d.ts +1 -1
  102. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  103. package/dist/resolvers/EntityRecordNameResolver.js +1 -1
  104. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  105. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  106. package/dist/resolvers/EntityResolver.js +1 -1
  107. package/dist/resolvers/EntityResolver.js.map +1 -1
  108. package/dist/resolvers/FileCategoryResolver.js +1 -1
  109. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  110. package/dist/resolvers/FileResolver.js +1 -1
  111. package/dist/resolvers/FileResolver.js.map +1 -1
  112. package/dist/resolvers/GetDataContextDataResolver.d.ts +1 -1
  113. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  114. package/dist/resolvers/GetDataContextDataResolver.js +5 -5
  115. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  116. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  117. package/dist/resolvers/GetDataResolver.js +8 -6
  118. package/dist/resolvers/GetDataResolver.js.map +1 -1
  119. package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
  120. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  121. package/dist/resolvers/MergeRecordsResolver.js +3 -3
  122. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  123. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
  124. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
  125. package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
  126. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  127. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  128. package/dist/resolvers/QueryResolver.js +11 -11
  129. package/dist/resolvers/QueryResolver.js.map +1 -1
  130. package/dist/resolvers/ReportResolver.js +1 -1
  131. package/dist/resolvers/ReportResolver.js.map +1 -1
  132. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  133. package/dist/resolvers/RunAIAgentResolver.js +27 -28
  134. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  135. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  136. package/dist/resolvers/RunAIPromptResolver.js +31 -31
  137. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  138. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  139. package/dist/resolvers/RunTemplateResolver.js +9 -9
  140. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  141. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  142. package/dist/resolvers/SqlLoggingConfigResolver.js +10 -10
  143. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  144. package/dist/resolvers/SyncDataResolver.d.ts +1 -1
  145. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  146. package/dist/resolvers/SyncDataResolver.js +15 -14
  147. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  148. package/dist/resolvers/SyncRolesUsersResolver.d.ts +1 -1
  149. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  150. package/dist/resolvers/SyncRolesUsersResolver.js +48 -44
  151. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  152. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  153. package/dist/resolvers/TaskResolver.js +7 -7
  154. package/dist/resolvers/TaskResolver.js.map +1 -1
  155. package/dist/resolvers/TransactionGroupResolver.d.ts +1 -1
  156. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  157. package/dist/resolvers/TransactionGroupResolver.js +12 -12
  158. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  159. package/dist/resolvers/UserFavoriteResolver.d.ts +1 -1
  160. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  161. package/dist/resolvers/UserFavoriteResolver.js +1 -1
  162. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  163. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  164. package/dist/resolvers/UserViewResolver.js.map +1 -1
  165. package/dist/rest/EntityCRUDHandler.d.ts +1 -1
  166. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  167. package/dist/rest/EntityCRUDHandler.js +14 -16
  168. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  169. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  170. package/dist/rest/RESTEndpointHandler.js +23 -25
  171. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  172. package/dist/rest/ViewOperationsHandler.d.ts +1 -1
  173. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  174. package/dist/rest/ViewOperationsHandler.js +17 -21
  175. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  176. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
  177. package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
  178. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  179. package/dist/services/ScheduledJobsService.js +4 -6
  180. package/dist/services/ScheduledJobsService.js.map +1 -1
  181. package/dist/services/TaskOrchestrator.d.ts +1 -1
  182. package/dist/services/TaskOrchestrator.d.ts.map +1 -1
  183. package/dist/services/TaskOrchestrator.js +30 -30
  184. package/dist/services/TaskOrchestrator.js.map +1 -1
  185. package/dist/types.d.ts +3 -3
  186. package/dist/types.d.ts.map +1 -1
  187. package/dist/types.js +0 -1
  188. package/dist/types.js.map +1 -1
  189. package/dist/util.d.ts +1 -1
  190. package/dist/util.d.ts.map +1 -1
  191. package/dist/util.js +2 -2
  192. package/dist/util.js.map +1 -1
  193. package/package.json +36 -37
  194. package/src/agents/skip-agent.ts +1067 -1200
  195. package/src/agents/skip-sdk.ts +877 -851
  196. package/src/apolloServer/index.ts +2 -2
  197. package/src/auth/AuthProviderFactory.ts +8 -14
  198. package/src/auth/BaseAuthProvider.ts +5 -4
  199. package/src/auth/IAuthProvider.ts +2 -2
  200. package/src/auth/exampleNewUserSubClass.ts +9 -2
  201. package/src/auth/index.ts +31 -26
  202. package/src/auth/initializeProviders.ts +3 -3
  203. package/src/auth/newUsers.ts +166 -134
  204. package/src/auth/providers/Auth0Provider.ts +5 -5
  205. package/src/auth/providers/CognitoProvider.ts +7 -10
  206. package/src/auth/providers/GoogleProvider.ts +4 -5
  207. package/src/auth/providers/MSALProvider.ts +5 -5
  208. package/src/auth/providers/OktaProvider.ts +6 -7
  209. package/src/config.ts +63 -54
  210. package/src/context.ts +42 -30
  211. package/src/entitySubclasses/entityPermissions.server.ts +3 -3
  212. package/src/generated/generated.ts +48130 -39930
  213. package/src/generic/KeyInputOutputTypes.ts +3 -6
  214. package/src/generic/ResolverBase.ts +119 -78
  215. package/src/generic/RunViewResolver.ts +27 -23
  216. package/src/index.ts +66 -42
  217. package/src/resolvers/ActionResolver.ts +46 -57
  218. package/src/resolvers/AskSkipResolver.ts +607 -533
  219. package/src/resolvers/ComponentRegistryResolver.ts +547 -562
  220. package/src/resolvers/CreateQueryResolver.ts +683 -655
  221. package/src/resolvers/DatasetResolver.ts +5 -6
  222. package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
  223. package/src/resolvers/EntityRecordNameResolver.ts +9 -5
  224. package/src/resolvers/EntityResolver.ts +9 -7
  225. package/src/resolvers/FileCategoryResolver.ts +2 -2
  226. package/src/resolvers/FileResolver.ts +4 -4
  227. package/src/resolvers/GetDataContextDataResolver.ts +106 -118
  228. package/src/resolvers/GetDataResolver.ts +194 -205
  229. package/src/resolvers/MergeRecordsResolver.ts +5 -5
  230. package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
  231. package/src/resolvers/QueryResolver.ts +95 -78
  232. package/src/resolvers/ReportResolver.ts +2 -2
  233. package/src/resolvers/RunAIAgentResolver.ts +818 -828
  234. package/src/resolvers/RunAIPromptResolver.ts +693 -709
  235. package/src/resolvers/RunTemplateResolver.ts +105 -103
  236. package/src/resolvers/SqlLoggingConfigResolver.ts +69 -72
  237. package/src/resolvers/SyncDataResolver.ts +386 -352
  238. package/src/resolvers/SyncRolesUsersResolver.ts +387 -350
  239. package/src/resolvers/TaskResolver.ts +110 -115
  240. package/src/resolvers/TransactionGroupResolver.ts +143 -138
  241. package/src/resolvers/UserFavoriteResolver.ts +17 -8
  242. package/src/resolvers/UserViewResolver.ts +17 -12
  243. package/src/rest/EntityCRUDHandler.ts +291 -268
  244. package/src/rest/RESTEndpointHandler.ts +782 -776
  245. package/src/rest/ViewOperationsHandler.ts +191 -195
  246. package/src/scheduler/LearningCycleScheduler.ts +8 -52
  247. package/src/services/ScheduledJobsService.ts +129 -132
  248. package/src/services/TaskOrchestrator.ts +792 -776
  249. package/src/types.ts +15 -9
  250. package/src/util.ts +112 -109
@@ -6,53 +6,49 @@
6
6
  * while maintaining compatibility with the existing Skip infrastructure.
7
7
  */
8
8
 
9
- import { BaseAgent } from "@memberjunction/ai-agents";
9
+ import { BaseAgent } from '@memberjunction/ai-agents';
10
+ import { ExecuteAgentParams, AgentConfiguration, BaseAgentNextStep } from '@memberjunction/ai-core-plus';
10
11
  import {
11
- ExecuteAgentParams,
12
- AgentConfiguration,
13
- BaseAgentNextStep
14
- } from "@memberjunction/ai-core-plus";
15
- import {
16
- SkipAPIResponse,
17
- SkipAPIAnalysisCompleteResponse,
18
- SkipAPIClarifyingQuestionResponse,
19
- SkipMessage
20
- } from "@memberjunction/skip-types";
21
- import { SkipSDK, SkipCallOptions } from "./skip-sdk.js";
22
- import { DataContext } from "@memberjunction/data-context";
23
- import { LogStatus, LogError, RunView, UserInfo } from "@memberjunction/core";
24
- import { ChatMessage } from "@memberjunction/ai";
25
- import { RegisterClass } from "@memberjunction/global";
26
- import { ComponentSpec } from "@memberjunction/interactive-component-types";
12
+ SkipAPIResponse,
13
+ SkipAPIAnalysisCompleteResponse,
14
+ SkipAPIClarifyingQuestionResponse,
15
+ SkipMessage,
16
+ } from '@memberjunction/skip-types';
17
+ import { SkipSDK, SkipCallOptions } from './skip-sdk.js';
18
+ import { DataContext } from '@memberjunction/data-context';
19
+ import { LogStatus, LogError, RunView, UserInfo } from '@memberjunction/global';
20
+ import { ChatMessage } from '@memberjunction/ai';
21
+ import { RegisterClass } from '@memberjunction/global';
22
+ import { ComponentSpec } from '@memberjunction/interactive-component-types';
27
23
 
28
24
  /**
29
25
  * Context type for Skip agent execution
30
26
  */
31
27
  export interface SkipAgentContext {
32
- /**
33
- * Optional data context ID to load
34
- */
35
- dataContextId?: string;
28
+ /**
29
+ * Optional data context ID to load
30
+ */
31
+ dataContextId?: string;
36
32
 
37
- /**
38
- * Optional pre-loaded data context
39
- */
40
- dataContext?: DataContext;
33
+ /**
34
+ * Optional pre-loaded data context
35
+ */
36
+ dataContext?: DataContext;
41
37
 
42
- /**
43
- * Conversation ID for tracking Skip conversations
44
- */
45
- conversationId?: string;
38
+ /**
39
+ * Conversation ID for tracking Skip conversations
40
+ */
41
+ conversationId?: string;
46
42
 
47
- /**
48
- * Force entity metadata refresh
49
- */
50
- forceEntityRefresh?: boolean;
43
+ /**
44
+ * Force entity metadata refresh
45
+ */
46
+ forceEntityRefresh?: boolean;
51
47
 
52
- /**
53
- * Database connection (injected by caller)
54
- */
55
- dataSource?: any;
48
+ /**
49
+ * Database connection (injected by caller)
50
+ */
51
+ dataSource?: any;
56
52
  }
57
53
 
58
54
  /**
@@ -60,25 +56,25 @@ export interface SkipAgentContext {
60
56
  * Contains the full Skip API response for downstream consumers
61
57
  */
62
58
  export interface SkipAgentPayload {
63
- /**
64
- * The full Skip API response
65
- */
66
- skipResponse: SkipAPIResponse;
59
+ /**
60
+ * The full Skip API response
61
+ */
62
+ skipResponse: SkipAPIResponse;
67
63
 
68
- /**
69
- * Response phase from Skip
70
- */
71
- responsePhase: string;
64
+ /**
65
+ * Response phase from Skip
66
+ */
67
+ responsePhase: string;
72
68
 
73
- /**
74
- * Conversation ID
75
- */
76
- conversationId: string;
69
+ /**
70
+ * Conversation ID
71
+ */
72
+ conversationId: string;
77
73
 
78
- /**
79
- * User-facing message (title or clarifying question)
80
- */
81
- message?: string;
74
+ /**
75
+ * User-facing message (title or clarifying question)
76
+ */
77
+ message?: string;
82
78
  }
83
79
 
84
80
  /**
@@ -93,1415 +89,1286 @@ export interface SkipAgentPayload {
93
89
  */
94
90
  @RegisterClass(BaseAgent, 'SkipProxyAgent')
95
91
  export class SkipProxyAgent extends BaseAgent {
96
- private skipSDK: SkipSDK;
92
+ private skipSDK: SkipSDK;
97
93
 
98
- constructor() {
99
- super();
100
- this.skipSDK = new SkipSDK();
101
- }
94
+ constructor() {
95
+ super();
96
+ this.skipSDK = new SkipSDK();
97
+ }
102
98
 
103
- /**
104
- * Execute the Skip agent - proxies to Skip SaaS API
105
- */
106
- protected override async executeAgentInternal<P = SkipAgentPayload>(
107
- params: ExecuteAgentParams<SkipAgentContext, P>,
108
- config: AgentConfiguration
109
- ): Promise<{ finalStep: BaseAgentNextStep<P>; stepCount: number; }> {
99
+ /**
100
+ * Execute the Skip agent - proxies to Skip SaaS API
101
+ */
102
+ protected override async executeAgentInternal<P = SkipAgentPayload>(
103
+ params: ExecuteAgentParams<SkipAgentContext, P>,
104
+ config: AgentConfiguration
105
+ ): Promise<{ finalStep: BaseAgentNextStep<P>; stepCount: number }> {
106
+ LogStatus(`[SkipProxyAgent] Starting Skip agent execution`);
110
107
 
111
- LogStatus(`[SkipProxyAgent] Starting Skip agent execution`);
108
+ // Extract context
109
+ const context = params.context || ({} as SkipAgentContext);
110
+ const conversationId = params.data?.conversationId;
112
111
 
113
- // Extract context
114
- const context = params.context || {} as SkipAgentContext;
115
- const conversationId = params.data?.conversationId;
112
+ if (!params.contextUser) {
113
+ LogError('[SkipProxyAgent] contextUser is required');
114
+ return {
115
+ finalStep: {
116
+ terminate: true,
117
+ step: 'Failed',
118
+ message: 'Missing required contextUser',
119
+ errorMessage: 'Missing required contextUser',
120
+ } as BaseAgentNextStep<P>,
121
+ stepCount: 1,
122
+ };
123
+ }
116
124
 
117
- if (!params.contextUser) {
118
- LogError('[SkipProxyAgent] contextUser is required');
119
- return {
120
- finalStep: {
121
- terminate: true,
122
- step: 'Failed',
123
- message: 'Missing required contextUser',
124
- errorMessage: 'Missing required contextUser'
125
- } as BaseAgentNextStep<P>,
126
- stepCount: 1
127
- };
128
- }
125
+ // Load conversation messages from database if conversationId is provided
126
+ // This ensures we get real UUIDs from ConversationDetailEntity records
127
+ let skipMessages: SkipMessage[];
128
+ if (conversationId && params.contextUser) {
129
+ skipMessages = await this.loadMessagesFromDatabase(conversationId, params.contextUser);
130
+ } else {
131
+ // Fallback to converting provided conversation messages
132
+ skipMessages = this.convertMessagesToSkipFormat(params.conversationMessages || []);
133
+ }
129
134
 
130
- // Load conversation messages from database if conversationId is provided
131
- // This ensures we get real UUIDs from ConversationDetailEntity records
132
- let skipMessages: SkipMessage[];
133
- if (conversationId && params.contextUser) {
134
- skipMessages = await this.loadMessagesFromDatabase(conversationId, params.contextUser);
135
- } else {
136
- // Fallback to converting provided conversation messages
137
- skipMessages = this.convertMessagesToSkipFormat(params.conversationMessages || []);
135
+ // Prepare Skip SDK call options
136
+ const skipOptions: SkipCallOptions = {
137
+ messages: skipMessages,
138
+ conversationId,
139
+ dataContext: context.dataContext,
140
+ requestPhase: 'initial_request', // Could be parameterized if needed
141
+ contextUser: params.contextUser,
142
+ dataSource: context.dataSource,
143
+ includeEntities: true,
144
+ includeQueries: true,
145
+ includeNotes: true,
146
+ includeRequests: false,
147
+ forceEntityRefresh: context.forceEntityRefresh || false,
148
+ includeCallbackAuth: true,
149
+ onStatusUpdate: (message: string, responsePhase?: string) => {
150
+ // Forward Skip status updates to MJ progress callback
151
+ if (params.onProgress) {
152
+ params.onProgress({
153
+ step: 'prompt_execution', // Skip execution is essentially a prompt to an external service
154
+ message,
155
+ percentage: 0, // Skip doesn't provide percentage
156
+ metadata: {
157
+ conversationId,
158
+ responsePhase,
159
+ },
160
+ });
138
161
  }
162
+ },
163
+ };
139
164
 
140
- // Prepare Skip SDK call options
141
- const skipOptions: SkipCallOptions = {
142
- messages: skipMessages,
143
- conversationId,
144
- dataContext: context.dataContext,
145
- requestPhase: 'initial_request', // Could be parameterized if needed
146
- contextUser: params.contextUser,
147
- dataSource: context.dataSource,
148
- includeEntities: true,
149
- includeQueries: true,
150
- includeNotes: true,
151
- includeRequests: false,
152
- forceEntityRefresh: context.forceEntityRefresh || false,
153
- includeCallbackAuth: true,
154
- onStatusUpdate: (message: string, responsePhase?: string) => {
155
- // Forward Skip status updates to MJ progress callback
156
- if (params.onProgress) {
157
- params.onProgress({
158
- step: 'prompt_execution', // Skip execution is essentially a prompt to an external service
159
- message,
160
- percentage: 0, // Skip doesn't provide percentage
161
- metadata: {
162
- conversationId,
163
- responsePhase
164
- }
165
- });
166
- }
167
- }
168
- };
169
-
170
- // Call Skip API
171
- const result = await this.skipSDK.chat(skipOptions);
165
+ // Call Skip API
166
+ const result = await this.skipSDK.chat(skipOptions);
172
167
 
173
- // Handle Skip API errors
174
- if (!result.success || !result.response) {
175
- LogError(`[SkipProxyAgent] Skip API call failed: ${result.error}`);
176
- return {
177
- finalStep: {
178
- terminate: true,
179
- step: 'Failed',
180
- message: 'Skip API call failed',
181
- errorMessage: result.error
182
- } as BaseAgentNextStep<P>,
183
- stepCount: 1
184
- };
185
- }
168
+ // Handle Skip API errors
169
+ if (!result.success || !result.response) {
170
+ LogError(`[SkipProxyAgent] Skip API call failed: ${result.error}`);
171
+ return {
172
+ finalStep: {
173
+ terminate: true,
174
+ step: 'Failed',
175
+ message: 'Skip API call failed',
176
+ errorMessage: result.error,
177
+ } as BaseAgentNextStep<P>,
178
+ stepCount: 1,
179
+ };
180
+ }
186
181
 
187
- // Map Skip response to MJ agent next step
188
- const nextStep = this.mapSkipResponseToNextStep(result.response, conversationId);
182
+ // Map Skip response to MJ agent next step
183
+ const nextStep = this.mapSkipResponseToNextStep(result.response, conversationId);
189
184
 
190
- LogStatus(`[SkipProxyAgent] Skip execution completed with phase: ${result.responsePhase}`);
185
+ LogStatus(`[SkipProxyAgent] Skip execution completed with phase: ${result.responsePhase}`);
191
186
 
192
- return {
193
- finalStep: nextStep as BaseAgentNextStep<P>,
194
- stepCount: 1 // Skip is a single-step proxy
195
- };
196
- }
187
+ return {
188
+ finalStep: nextStep as BaseAgentNextStep<P>,
189
+ stepCount: 1, // Skip is a single-step proxy
190
+ };
191
+ }
197
192
 
198
- /**
199
- * Load conversation messages from database with real UUIDs using MemberJunction's RunView pattern
200
- * This is the preferred method as it ensures all messages have proper conversationDetailIDs
201
- */
202
- private async loadMessagesFromDatabase(conversationId: string, contextUser: UserInfo): Promise<SkipMessage[]> {
203
- try {
204
- const rv = new RunView();
205
- const result = await rv.RunView({
206
- EntityName: 'Conversation Details',
207
- ExtraFilter: `ConversationID='${conversationId}'`,
208
- OrderBy: '__mj_CreatedAt ASC'
209
- }, contextUser);
193
+ /**
194
+ * Load conversation messages from database with real UUIDs using MemberJunction's RunView pattern
195
+ * This is the preferred method as it ensures all messages have proper conversationDetailIDs
196
+ */
197
+ private async loadMessagesFromDatabase(conversationId: string, contextUser: UserInfo): Promise<SkipMessage[]> {
198
+ try {
199
+ const rv = new RunView();
200
+ const result = await rv.RunView(
201
+ {
202
+ EntityName: 'Conversation Details',
203
+ ExtraFilter: `ConversationID='${conversationId}'`,
204
+ OrderBy: '__mj_CreatedAt ASC',
205
+ },
206
+ contextUser
207
+ );
210
208
 
211
- if (!result.Success) {
212
- throw new Error(`Failed to load conversation details: ${result.ErrorMessage}`);
213
- }
209
+ if (!result.Success) {
210
+ throw new Error(`Failed to load conversation details: ${result.ErrorMessage}`);
211
+ }
214
212
 
215
- const allMessages = (result.Results || []).map((r: any) => {
216
- // Map database role to Skip role
217
- const dbRole = (r.Role || '').trim().toLowerCase();
218
- const skipRole: 'user' | 'system' =
219
- (dbRole === 'ai' || dbRole === 'system' || dbRole === 'assistant') ? 'system' : 'user';
213
+ const allMessages = (result.Results || []).map((r: any) => {
214
+ // Map database role to Skip role
215
+ const dbRole = (r.Role || '').trim().toLowerCase();
216
+ const skipRole: 'user' | 'system' = dbRole === 'ai' || dbRole === 'system' || dbRole === 'assistant' ? 'system' : 'user';
220
217
 
221
- // For system messages, always send the raw Message from database
222
- // Skip Brain needs the full JSON response to extract component specs for modification
223
- // For user messages, use the message as-is
224
- const content = r.Message;
218
+ // For system messages, always send the raw Message from database
219
+ // Skip Brain needs the full JSON response to extract component specs for modification
220
+ // For user messages, use the message as-is
221
+ const content = r.Message;
225
222
 
226
- const message: SkipMessage = {
227
- content: content,
228
- role: skipRole,
229
- conversationDetailID: r.ID,
230
- hiddenToUser: r.HiddenToUser,
231
- userRating: r.UserRating,
232
- userFeedback: r.UserFeedback,
233
- reflectionInsights: r.ReflectionInsights,
234
- summaryOfEarlierConveration: r.SummaryOfEarlierConversation,
235
- createdAt: r.__mj_CreatedAt,
236
- updatedAt: r.__mj_UpdatedAt,
237
- };
238
- return message;
239
- });
223
+ const message: SkipMessage = {
224
+ content: content,
225
+ role: skipRole,
226
+ conversationDetailID: r.ID,
227
+ hiddenToUser: r.HiddenToUser,
228
+ userRating: r.UserRating,
229
+ userFeedback: r.UserFeedback,
230
+ reflectionInsights: r.ReflectionInsights,
231
+ summaryOfEarlierConveration: r.SummaryOfEarlierConversation,
232
+ createdAt: r.__mj_CreatedAt,
233
+ updatedAt: r.__mj_UpdatedAt,
234
+ };
235
+ return message;
236
+ });
240
237
 
241
- // Find the index of the last user message
242
- // We only want to include messages up to and including the most recent user message
243
- // This filters out status messages and incomplete AI responses
244
- const lastUserMessageIndex = allMessages.reduce((lastIndex, msg, currentIndex) => {
245
- return msg.role === 'user' ? currentIndex : lastIndex;
246
- }, -1);
238
+ // Find the index of the last user message
239
+ // We only want to include messages up to and including the most recent user message
240
+ // This filters out status messages and incomplete AI responses
241
+ const lastUserMessageIndex = allMessages.reduce((lastIndex, msg, currentIndex) => {
242
+ return msg.role === 'user' ? currentIndex : lastIndex;
243
+ }, -1);
247
244
 
248
- if (lastUserMessageIndex === -1) {
249
- // No user messages found, return all messages (shouldn't happen in practice)
250
- return allMessages;
251
- }
245
+ if (lastUserMessageIndex === -1) {
246
+ // No user messages found, return all messages (shouldn't happen in practice)
247
+ return allMessages;
248
+ }
252
249
 
253
- // Return messages up to and including the last user message
254
- return allMessages.slice(0, lastUserMessageIndex + 1);
255
- } catch (error) {
256
- LogError(`[SkipProxyAgent] Error loading messages from database: ${error}`);
257
- throw error;
258
- }
250
+ // Return messages up to and including the last user message
251
+ return allMessages.slice(0, lastUserMessageIndex + 1);
252
+ } catch (error) {
253
+ LogError(`[SkipProxyAgent] Error loading messages from database: ${error}`);
254
+ throw error;
259
255
  }
256
+ }
260
257
 
261
- /**
262
- * Convert MJ ChatMessage format to Skip SkipMessage format
263
- * This is a fallback method when database loading is not available
264
- */
265
- private convertMessagesToSkipFormat(messages: ChatMessage[]): SkipMessage[] {
266
- return messages.map((msg, index) => {
267
- // Extract conversationDetailID from metadata if available
268
- const conversationDetailID = msg.metadata?.conversationDetailID || `temp-${index}`;
258
+ /**
259
+ * Convert MJ ChatMessage format to Skip SkipMessage format
260
+ * This is a fallback method when database loading is not available
261
+ */
262
+ private convertMessagesToSkipFormat(messages: ChatMessage[]): SkipMessage[] {
263
+ return messages.map((msg, index) => {
264
+ // Extract conversationDetailID from metadata if available
265
+ const conversationDetailID = msg.metadata?.conversationDetailID || `temp-${index}`;
269
266
 
270
- return {
271
- // Skip only accepts 'user' or 'system' roles, map 'assistant' to 'system'
272
- role: (msg.role === 'assistant' ? 'system' : msg.role) as 'user' | 'system',
273
- content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
274
- conversationDetailID,
275
- // Include other SkipMessage fields from metadata if available
276
- hiddenToUser: msg.metadata?.hiddenToUser,
277
- userRating: msg.metadata?.userRating,
278
- userFeedback: msg.metadata?.userFeedback,
279
- reflectionInsights: msg.metadata?.reflectionInsights,
280
- summaryOfEarlierConveration: msg.metadata?.summaryOfEarlierConversation,
281
- createdAt: msg.metadata?.createdAt,
282
- updatedAt: msg.metadata?.updatedAt
283
- };
284
- });
285
- }
267
+ return {
268
+ // Skip only accepts 'user' or 'system' roles, map 'assistant' to 'system'
269
+ role: (msg.role === 'assistant' ? 'system' : msg.role) as 'user' | 'system',
270
+ content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
271
+ conversationDetailID,
272
+ // Include other SkipMessage fields from metadata if available
273
+ hiddenToUser: msg.metadata?.hiddenToUser,
274
+ userRating: msg.metadata?.userRating,
275
+ userFeedback: msg.metadata?.userFeedback,
276
+ reflectionInsights: msg.metadata?.reflectionInsights,
277
+ summaryOfEarlierConveration: msg.metadata?.summaryOfEarlierConversation,
278
+ createdAt: msg.metadata?.createdAt,
279
+ updatedAt: msg.metadata?.updatedAt,
280
+ };
281
+ });
282
+ }
286
283
 
287
- /**
288
- * Map Skip API response to MJ agent next step
289
- */
290
- private mapSkipResponseToNextStep(
291
- apiResponse: SkipAPIResponse,
292
- conversationId: string
293
- ): BaseAgentNextStep<ComponentSpec> {
294
- //return this.tempHack();
284
+ /**
285
+ * Map Skip API response to MJ agent next step
286
+ */
287
+ private mapSkipResponseToNextStep(apiResponse: SkipAPIResponse, conversationId: string): BaseAgentNextStep<ComponentSpec> {
288
+ //return this.tempHack();
295
289
 
296
- switch (apiResponse.responsePhase) {
297
- case 'analysis_complete': {
298
- // Skip has completed analysis and returned results
299
- const completeResponse = apiResponse as SkipAPIAnalysisCompleteResponse;
300
- const componentSpec = completeResponse.componentOptions[0].option;
301
- return {
302
- terminate: true,
303
- step: 'Success',
304
- message: completeResponse.title || 'Analysis complete',
305
- newPayload: componentSpec
306
- };
307
- }
308
-
309
- case 'clarifying_question': {
310
- // Skip needs more information from the user
311
- const clarifyResponse = apiResponse as SkipAPIClarifyingQuestionResponse;
290
+ switch (apiResponse.responsePhase) {
291
+ case 'analysis_complete': {
292
+ // Skip has completed analysis and returned results
293
+ const completeResponse = apiResponse as SkipAPIAnalysisCompleteResponse;
294
+ const componentSpec = completeResponse.componentOptions[0].option;
295
+ return {
296
+ terminate: true,
297
+ step: 'Success',
298
+ message: completeResponse.title || 'Analysis complete',
299
+ newPayload: componentSpec,
300
+ };
301
+ }
312
302
 
313
- return {
314
- terminate: true,
315
- step: 'Chat',
316
- message: clarifyResponse.clarifyingQuestion,
317
- newPayload: undefined
318
- };
319
- }
303
+ case 'clarifying_question': {
304
+ // Skip needs more information from the user
305
+ const clarifyResponse = apiResponse as SkipAPIClarifyingQuestionResponse;
320
306
 
321
- default: {
322
- // Unknown or unexpected response phase
323
- LogError(`[SkipProxyAgent] Unknown Skip response phase: ${apiResponse.responsePhase}`);
324
- return {
325
- terminate: true,
326
- step: 'Failed',
327
- message: `Unknown Skip response phase: ${apiResponse.responsePhase}`,
328
- errorMessage: `Unknown Skip response phase: ${apiResponse.responsePhase}`,
329
- newPayload: undefined
330
- };
331
- }
307
+ return {
308
+ terminate: true,
309
+ step: 'Chat',
310
+ message: clarifyResponse.clarifyingQuestion,
311
+ newPayload: undefined,
312
+ };
332
313
  }
333
- }
334
314
 
335
- private tempHack(): BaseAgentNextStep<SkipAgentPayload> {
315
+ default: {
316
+ // Unknown or unexpected response phase
317
+ LogError(`[SkipProxyAgent] Unknown Skip response phase: ${apiResponse.responsePhase}`);
336
318
  return {
337
- terminate: true,
338
- step: 'Success',
339
- message: "Demo Report (not real)",
340
- newPayload: demoSpecJson as unknown as SkipAgentPayload
319
+ terminate: true,
320
+ step: 'Failed',
321
+ message: `Unknown Skip response phase: ${apiResponse.responsePhase}`,
322
+ errorMessage: `Unknown Skip response phase: ${apiResponse.responsePhase}`,
323
+ newPayload: undefined,
341
324
  };
325
+ }
342
326
  }
327
+ }
328
+
329
+ private tempHack(): BaseAgentNextStep<SkipAgentPayload> {
330
+ return {
331
+ terminate: true,
332
+ step: 'Success',
333
+ message: 'Demo Report (not real)',
334
+ newPayload: demoSpecJson as unknown as SkipAgentPayload,
335
+ };
336
+ }
343
337
  }
344
338
 
345
339
  const demoSpecJson = {
346
- "name": "EntityBrowser",
347
- "title": "Entity Browser",
348
- "description": "A comprehensive entity browser with multi-panel display showing entities in a grid or card view with a sliding details panel, collapsible filters, sorting, and entity record opening capability.",
349
- "type": "dashboard",
350
- "functionalRequirements": "## Entity Browser Requirements\n\n### Core Functionality\n- Display entities in a responsive grid or card layout based on user preference\n- Allow users to select view mode (grid vs card)\n- Click on an entity to slide in a details panel from the right\n- Show entity metadata including fields and relationships in the details panel\n- Provide a collapsible filter panel on the left side\n- Support sorting by multiple fields with visual indicators\n- Include a search bar for quick entity filtering\n- Provide an 'Open' button to trigger the OpenEntityRecord callback\n- Remember user's last selected entity and view preferences\n\n### UX Considerations\n- Smooth animations for panel transitions\n- Responsive design that works on different screen sizes\n- Loading states while fetching data\n- Empty states with helpful messages\n- Keyboard navigation support (arrow keys, tab, enter)\n- Visual feedback for hover and selection states\n- Maintain scroll position when switching between entities",
351
- "dataRequirements": {
352
- "mode": "views",
353
- "entities": [
340
+ name: 'EntityBrowser',
341
+ title: 'Entity Browser',
342
+ description:
343
+ 'A comprehensive entity browser with multi-panel display showing entities in a grid or card view with a sliding details panel, collapsible filters, sorting, and entity record opening capability.',
344
+ type: 'dashboard',
345
+ functionalRequirements:
346
+ "## Entity Browser Requirements\n\n### Core Functionality\n- Display entities in a responsive grid or card layout based on user preference\n- Allow users to select view mode (grid vs card)\n- Click on an entity to slide in a details panel from the right\n- Show entity metadata including fields and relationships in the details panel\n- Provide a collapsible filter panel on the left side\n- Support sorting by multiple fields with visual indicators\n- Include a search bar for quick entity filtering\n- Provide an 'Open' button to trigger the OpenEntityRecord callback\n- Remember user's last selected entity and view preferences\n\n### UX Considerations\n- Smooth animations for panel transitions\n- Responsive design that works on different screen sizes\n- Loading states while fetching data\n- Empty states with helpful messages\n- Keyboard navigation support (arrow keys, tab, enter)\n- Visual feedback for hover and selection states\n- Maintain scroll position when switching between entities",
347
+ dataRequirements: {
348
+ mode: 'views',
349
+ entities: [
354
350
  {
355
- "name": "Entities",
356
- "description": "Metadata about all entities in the system",
357
- "displayFields": [
358
- "ID",
359
- "Name",
360
- "DisplayName",
361
- "NameSuffix",
362
- "Description",
363
- "SchemaName",
364
- "BaseTable",
365
- "BaseView"
366
- ],
367
- "filterFields": [
368
- "SchemaName",
369
- "BaseTable"
370
- ],
371
- "sortFields": [
372
- "Name",
373
- "DisplayName"
374
- ],
375
- "fieldMetadata": [
351
+ name: 'Entities',
352
+ description: 'Metadata about all entities in the system',
353
+ displayFields: ['ID', 'Name', 'DisplayName', 'NameSuffix', 'Description', 'SchemaName', 'BaseTable', 'BaseView'],
354
+ filterFields: ['SchemaName', 'BaseTable'],
355
+ sortFields: ['Name', 'DisplayName'],
356
+ fieldMetadata: [
376
357
  {
377
- "name": "ID",
378
- "sequence": 1,
379
- "defaultInView": false,
380
- "type": "uniqueidentifier",
381
- "allowsNull": false,
382
- "isPrimaryKey": true,
383
- "description": "Unique identifier for the entity"
358
+ name: 'ID',
359
+ sequence: 1,
360
+ defaultInView: false,
361
+ type: 'uniqueidentifier',
362
+ allowsNull: false,
363
+ isPrimaryKey: true,
364
+ description: 'Unique identifier for the entity',
384
365
  },
385
366
  {
386
- "name": "Name",
387
- "sequence": 2,
388
- "defaultInView": true,
389
- "type": "nvarchar",
390
- "allowsNull": false,
391
- "isPrimaryKey": false,
392
- "description": "System name of the entity"
367
+ name: 'Name',
368
+ sequence: 2,
369
+ defaultInView: true,
370
+ type: 'nvarchar',
371
+ allowsNull: false,
372
+ isPrimaryKey: false,
373
+ description: 'System name of the entity',
393
374
  },
394
375
  {
395
- "name": "DisplayName",
396
- "sequence": 3,
397
- "defaultInView": true,
398
- "type": "nvarchar",
399
- "allowsNull": true,
400
- "isPrimaryKey": false,
401
- "description": "User-friendly display name for the entity"
376
+ name: 'DisplayName',
377
+ sequence: 3,
378
+ defaultInView: true,
379
+ type: 'nvarchar',
380
+ allowsNull: true,
381
+ isPrimaryKey: false,
382
+ description: 'User-friendly display name for the entity',
402
383
  },
403
384
  {
404
- "name": "NameSuffix",
405
- "sequence": 4,
406
- "defaultInView": true,
407
- "type": "nvarchar",
408
- "allowsNull": true,
409
- "isPrimaryKey": false,
410
- "description": "Optional suffix appended to entity names for display purposes"
385
+ name: 'NameSuffix',
386
+ sequence: 4,
387
+ defaultInView: true,
388
+ type: 'nvarchar',
389
+ allowsNull: true,
390
+ isPrimaryKey: false,
391
+ description: 'Optional suffix appended to entity names for display purposes',
411
392
  },
412
393
  {
413
- "name": "Description",
414
- "sequence": 5,
415
- "defaultInView": true,
416
- "type": "nvarchar",
417
- "allowsNull": true,
418
- "isPrimaryKey": false,
419
- "description": "Description of the entity"
394
+ name: 'Description',
395
+ sequence: 5,
396
+ defaultInView: true,
397
+ type: 'nvarchar',
398
+ allowsNull: true,
399
+ isPrimaryKey: false,
400
+ description: 'Description of the entity',
420
401
  },
421
402
  {
422
- "name": "SchemaName",
423
- "sequence": 6,
424
- "defaultInView": true,
425
- "type": "nvarchar",
426
- "allowsNull": true,
427
- "isPrimaryKey": false,
428
- "description": "Database schema name"
403
+ name: 'SchemaName',
404
+ sequence: 6,
405
+ defaultInView: true,
406
+ type: 'nvarchar',
407
+ allowsNull: true,
408
+ isPrimaryKey: false,
409
+ description: 'Database schema name',
429
410
  },
430
411
  {
431
- "name": "BaseTable",
432
- "sequence": 7,
433
- "defaultInView": true,
434
- "type": "nvarchar",
435
- "allowsNull": true,
436
- "isPrimaryKey": false,
437
- "description": "Base table in the database"
412
+ name: 'BaseTable',
413
+ sequence: 7,
414
+ defaultInView: true,
415
+ type: 'nvarchar',
416
+ allowsNull: true,
417
+ isPrimaryKey: false,
418
+ description: 'Base table in the database',
438
419
  },
439
420
  {
440
- "name": "BaseView",
441
- "sequence": 8,
442
- "defaultInView": true,
443
- "type": "nvarchar",
444
- "allowsNull": false,
445
- "isPrimaryKey": false,
446
- "description": "Base view used for the entity"
447
- }
448
- ],
449
- "permissionLevelNeeded": [
450
- "read"
421
+ name: 'BaseView',
422
+ sequence: 8,
423
+ defaultInView: true,
424
+ type: 'nvarchar',
425
+ allowsNull: false,
426
+ isPrimaryKey: false,
427
+ description: 'Base view used for the entity',
428
+ },
451
429
  ],
452
- "usageContext": "Main entity list display and filtering"
430
+ permissionLevelNeeded: ['read'],
431
+ usageContext: 'Main entity list display and filtering',
453
432
  },
454
433
  {
455
- "name": "Entity Fields",
456
- "description": "Fields belonging to each entity",
457
- "displayFields": [
458
- "Name",
459
- "DisplayName",
460
- "Type",
461
- "Length",
462
- "AllowsNull",
463
- "IsPrimaryKey",
464
- "IsUnique"
465
- ],
466
- "filterFields": [
467
- "EntityID"
468
- ],
469
- "sortFields": [
470
- "Sequence",
471
- "Name"
472
- ],
473
- "fieldMetadata": [
434
+ name: 'Entity Fields',
435
+ description: 'Fields belonging to each entity',
436
+ displayFields: ['Name', 'DisplayName', 'Type', 'Length', 'AllowsNull', 'IsPrimaryKey', 'IsUnique'],
437
+ filterFields: ['EntityID'],
438
+ sortFields: ['Sequence', 'Name'],
439
+ fieldMetadata: [
474
440
  {
475
- "name": "EntityID",
476
- "sequence": 1,
477
- "defaultInView": false,
478
- "type": "uniqueidentifier",
479
- "allowsNull": false,
480
- "isPrimaryKey": false,
481
- "description": "Reference to parent entity"
441
+ name: 'EntityID',
442
+ sequence: 1,
443
+ defaultInView: false,
444
+ type: 'uniqueidentifier',
445
+ allowsNull: false,
446
+ isPrimaryKey: false,
447
+ description: 'Reference to parent entity',
482
448
  },
483
449
  {
484
- "name": "Name",
485
- "sequence": 2,
486
- "defaultInView": true,
487
- "type": "nvarchar",
488
- "allowsNull": false,
489
- "isPrimaryKey": false,
490
- "description": "Field name"
450
+ name: 'Name',
451
+ sequence: 2,
452
+ defaultInView: true,
453
+ type: 'nvarchar',
454
+ allowsNull: false,
455
+ isPrimaryKey: false,
456
+ description: 'Field name',
491
457
  },
492
458
  {
493
- "name": "DisplayName",
494
- "sequence": 3,
495
- "defaultInView": true,
496
- "type": "nvarchar",
497
- "allowsNull": true,
498
- "isPrimaryKey": false,
499
- "description": "User-friendly field name"
459
+ name: 'DisplayName',
460
+ sequence: 3,
461
+ defaultInView: true,
462
+ type: 'nvarchar',
463
+ allowsNull: true,
464
+ isPrimaryKey: false,
465
+ description: 'User-friendly field name',
500
466
  },
501
467
  {
502
- "name": "Type",
503
- "sequence": 4,
504
- "defaultInView": true,
505
- "type": "nvarchar",
506
- "allowsNull": false,
507
- "isPrimaryKey": false,
508
- "description": "Data type of the field"
468
+ name: 'Type',
469
+ sequence: 4,
470
+ defaultInView: true,
471
+ type: 'nvarchar',
472
+ allowsNull: false,
473
+ isPrimaryKey: false,
474
+ description: 'Data type of the field',
509
475
  },
510
476
  {
511
- "name": "Length",
512
- "sequence": 5,
513
- "defaultInView": true,
514
- "type": "int",
515
- "allowsNull": true,
516
- "isPrimaryKey": false,
517
- "description": "Maximum length for string fields"
477
+ name: 'Length',
478
+ sequence: 5,
479
+ defaultInView: true,
480
+ type: 'int',
481
+ allowsNull: true,
482
+ isPrimaryKey: false,
483
+ description: 'Maximum length for string fields',
518
484
  },
519
485
  {
520
- "name": "AllowsNull",
521
- "sequence": 6,
522
- "defaultInView": true,
523
- "type": "bit",
524
- "allowsNull": false,
525
- "isPrimaryKey": false,
526
- "description": "Whether field allows null values"
486
+ name: 'AllowsNull',
487
+ sequence: 6,
488
+ defaultInView: true,
489
+ type: 'bit',
490
+ allowsNull: false,
491
+ isPrimaryKey: false,
492
+ description: 'Whether field allows null values',
527
493
  },
528
494
  {
529
- "name": "IsPrimaryKey",
530
- "sequence": 7,
531
- "defaultInView": true,
532
- "type": "bit",
533
- "allowsNull": false,
534
- "isPrimaryKey": false,
535
- "description": "Whether field is part of primary key"
495
+ name: 'IsPrimaryKey',
496
+ sequence: 7,
497
+ defaultInView: true,
498
+ type: 'bit',
499
+ allowsNull: false,
500
+ isPrimaryKey: false,
501
+ description: 'Whether field is part of primary key',
536
502
  },
537
503
  {
538
- "name": "IsUnique",
539
- "sequence": 8,
540
- "defaultInView": true,
541
- "type": "bit",
542
- "allowsNull": false,
543
- "isPrimaryKey": false,
544
- "description": "Whether field must be unique"
504
+ name: 'IsUnique',
505
+ sequence: 8,
506
+ defaultInView: true,
507
+ type: 'bit',
508
+ allowsNull: false,
509
+ isPrimaryKey: false,
510
+ description: 'Whether field must be unique',
545
511
  },
546
512
  {
547
- "name": "Sequence",
548
- "sequence": 9,
549
- "defaultInView": false,
550
- "type": "int",
551
- "allowsNull": false,
552
- "isPrimaryKey": false,
553
- "description": "Display order of the field"
554
- }
555
- ],
556
- "permissionLevelNeeded": [
557
- "read"
513
+ name: 'Sequence',
514
+ sequence: 9,
515
+ defaultInView: false,
516
+ type: 'int',
517
+ allowsNull: false,
518
+ isPrimaryKey: false,
519
+ description: 'Display order of the field',
520
+ },
558
521
  ],
559
- "usageContext": "Details panel to show entity fields"
522
+ permissionLevelNeeded: ['read'],
523
+ usageContext: 'Details panel to show entity fields',
560
524
  },
561
525
  {
562
- "name": "Entity Relationships",
563
- "description": "Relationships between entities",
564
- "displayFields": [
565
- "RelatedEntity",
566
- "Type",
567
- "DisplayName",
568
- "RelatedEntityJoinField"
569
- ],
570
- "filterFields": [
571
- "EntityID"
572
- ],
573
- "sortFields": [
574
- "Sequence",
575
- "RelatedEntity"
576
- ],
577
- "fieldMetadata": [
526
+ name: 'Entity Relationships',
527
+ description: 'Relationships between entities',
528
+ displayFields: ['RelatedEntity', 'Type', 'DisplayName', 'RelatedEntityJoinField'],
529
+ filterFields: ['EntityID'],
530
+ sortFields: ['Sequence', 'RelatedEntity'],
531
+ fieldMetadata: [
578
532
  {
579
- "name": "EntityID",
580
- "sequence": 1,
581
- "defaultInView": false,
582
- "type": "uniqueidentifier",
583
- "allowsNull": false,
584
- "isPrimaryKey": false,
585
- "description": "Reference to parent entity"
533
+ name: 'EntityID',
534
+ sequence: 1,
535
+ defaultInView: false,
536
+ type: 'uniqueidentifier',
537
+ allowsNull: false,
538
+ isPrimaryKey: false,
539
+ description: 'Reference to parent entity',
586
540
  },
587
541
  {
588
- "name": "RelatedEntity",
589
- "sequence": 2,
590
- "defaultInView": true,
591
- "type": "nvarchar",
592
- "allowsNull": false,
593
- "isPrimaryKey": false,
594
- "description": "The related entity in the relationship"
542
+ name: 'RelatedEntity',
543
+ sequence: 2,
544
+ defaultInView: true,
545
+ type: 'nvarchar',
546
+ allowsNull: false,
547
+ isPrimaryKey: false,
548
+ description: 'The related entity in the relationship',
595
549
  },
596
550
  {
597
- "name": "Type",
598
- "sequence": 3,
599
- "defaultInView": true,
600
- "type": "nvarchar",
601
- "allowsNull": false,
602
- "isPrimaryKey": false,
603
- "description": "Type of relationship (One to Many, Many to One, etc.)"
551
+ name: 'Type',
552
+ sequence: 3,
553
+ defaultInView: true,
554
+ type: 'nvarchar',
555
+ allowsNull: false,
556
+ isPrimaryKey: false,
557
+ description: 'Type of relationship (One to Many, Many to One, etc.)',
604
558
  },
605
559
  {
606
- "name": "DisplayName",
607
- "sequence": 4,
608
- "defaultInView": true,
609
- "type": "nvarchar",
610
- "allowsNull": true,
611
- "isPrimaryKey": false,
612
- "description": "User-friendly name for the relationship"
560
+ name: 'DisplayName',
561
+ sequence: 4,
562
+ defaultInView: true,
563
+ type: 'nvarchar',
564
+ allowsNull: true,
565
+ isPrimaryKey: false,
566
+ description: 'User-friendly name for the relationship',
613
567
  },
614
568
  {
615
- "name": "RelatedEntityJoinField",
616
- "sequence": 5,
617
- "defaultInView": true,
618
- "type": "nvarchar",
619
- "allowsNull": true,
620
- "isPrimaryKey": false,
621
- "description": "The field in the related entity that joins to this entity"
569
+ name: 'RelatedEntityJoinField',
570
+ sequence: 5,
571
+ defaultInView: true,
572
+ type: 'nvarchar',
573
+ allowsNull: true,
574
+ isPrimaryKey: false,
575
+ description: 'The field in the related entity that joins to this entity',
622
576
  },
623
577
  {
624
- "name": "Sequence",
625
- "sequence": 6,
626
- "defaultInView": false,
627
- "type": "int",
628
- "allowsNull": false,
629
- "isPrimaryKey": false,
630
- "description": "Display order"
631
- }
632
- ],
633
- "permissionLevelNeeded": [
634
- "read"
578
+ name: 'Sequence',
579
+ sequence: 6,
580
+ defaultInView: false,
581
+ type: 'int',
582
+ allowsNull: false,
583
+ isPrimaryKey: false,
584
+ description: 'Display order',
585
+ },
635
586
  ],
636
- "usageContext": "Details panel to show entity relationships"
637
- }
587
+ permissionLevelNeeded: ['read'],
588
+ usageContext: 'Details panel to show entity relationships',
589
+ },
638
590
  ],
639
- "queries": [],
640
- "description": "This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience"
591
+ queries: [],
592
+ description:
593
+ 'This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience',
641
594
  },
642
- "technicalDesign": "## Technical Architecture\n\n### Component Structure\n- **Root Component (EntityBrowser)**: Manages overall layout and state coordination\n- **EntityList (Child)**: Displays entities in grid/card view with sorting\n- **EntityDetails (Child)**: Sliding panel showing entity fields and relationships\n- **EntityFilter (Child)**: Collapsible filter panel with dynamic filters\n\n### State Management\n- Selected entity ID (persisted in savedUserSettings)\n- View mode (grid/card) (persisted)\n- Active filters (persisted)\n- Sort configuration (persisted)\n- Panel visibility states (details open, filters collapsed)\n- Search query\n- Loading states for async operations\n\n### Layout\n```\n+------------------+------------------------+------------------+\n| | | |\n| Filter Panel | Entity Grid/Cards | Details Panel |\n| (Collapsible) | (Main Content) | (Sliding) |\n| | | |\n| [Schema Filter] | +-----+ +-----+ | Entity: Orders |\n| [Table Filter] | | Card | | Card | | |\n| [Search Box] | +-----+ +-----+ | Fields: |\n| | | - ID |\n| Sort By: | +-----+ +-----+ | - CustomerID |\n| [Name ↓] | | Card | | Card | | - OrderDate |\n| | +-----+ +-----+ | |\n| | | Relationships: |\n| | | → Customers |\n| | | → OrderItems |\n| | | |\n| | | [Open Record] |\n+------------------+------------------------+------------------+\n```\n\n### Data Flow\n1. Root component loads entities on mount\n2. Passes entity data to EntityList\n3. EntityList handles selection and passes selectedId up\n4. Root loads fields/relationships for selected entity\n5. Passes detailed data to EntityDetails\n6. Filter changes trigger data reload\n7. All user preferences saved via onSaveUserSettings\n\n### Interaction Patterns\n- Click entity card → Select and open details\n- Click filter → Apply and reload data\n- Click sort → Update sort and reload\n- Click 'Open' → Trigger OpenEntityRecord callback\n- Press Escape → Close details panel\n- Click outside → Close details panel",
643
- "properties": [],
644
- "events": [],
645
- "exampleUsage": "<EntityBrowser\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n/>",
646
- "dependencies": [
595
+ technicalDesign:
596
+ "## Technical Architecture\n\n### Component Structure\n- **Root Component (EntityBrowser)**: Manages overall layout and state coordination\n- **EntityList (Child)**: Displays entities in grid/card view with sorting\n- **EntityDetails (Child)**: Sliding panel showing entity fields and relationships\n- **EntityFilter (Child)**: Collapsible filter panel with dynamic filters\n\n### State Management\n- Selected entity ID (persisted in savedUserSettings)\n- View mode (grid/card) (persisted)\n- Active filters (persisted)\n- Sort configuration (persisted)\n- Panel visibility states (details open, filters collapsed)\n- Search query\n- Loading states for async operations\n\n### Layout\n```\n+------------------+------------------------+------------------+\n| | | |\n| Filter Panel | Entity Grid/Cards | Details Panel |\n| (Collapsible) | (Main Content) | (Sliding) |\n| | | |\n| [Schema Filter] | +-----+ +-----+ | Entity: Orders |\n| [Table Filter] | | Card | | Card | | |\n| [Search Box] | +-----+ +-----+ | Fields: |\n| | | - ID |\n| Sort By: | +-----+ +-----+ | - CustomerID |\n| [Name ↓] | | Card | | Card | | - OrderDate |\n| | +-----+ +-----+ | |\n| | | Relationships: |\n| | | → Customers |\n| | | → OrderItems |\n| | | |\n| | | [Open Record] |\n+------------------+------------------------+------------------+\n```\n\n### Data Flow\n1. Root component loads entities on mount\n2. Passes entity data to EntityList\n3. EntityList handles selection and passes selectedId up\n4. Root loads fields/relationships for selected entity\n5. Passes detailed data to EntityDetails\n6. Filter changes trigger data reload\n7. All user preferences saved via onSaveUserSettings\n\n### Interaction Patterns\n- Click entity card → Select and open details\n- Click filter → Apply and reload data\n- Click sort → Update sort and reload\n- Click 'Open' → Trigger OpenEntityRecord callback\n- Press Escape → Close details panel\n- Click outside → Close details panel",
597
+ properties: [],
598
+ events: [],
599
+ exampleUsage:
600
+ '<EntityBrowser\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n/>',
601
+ dependencies: [
647
602
  {
648
- "name": "EntityList",
649
- "title": "Entity List",
650
- "description": "Displays entities in a grid or card layout with sorting capabilities",
651
- "type": "table",
652
- "functionalRequirements": "## Entity List Requirements\n\n- Display entities in grid or card view based on viewMode prop\n- Support sorting by multiple fields\n- Handle entity selection and notify parent\n- Show loading state while data loads\n- Display record count badges\n- Highlight selected entity\n- Support keyboard navigation",
653
- "dataRequirements": {
654
- "mode": "views",
655
- "entities": [
603
+ name: 'EntityList',
604
+ title: 'Entity List',
605
+ description: 'Displays entities in a grid or card layout with sorting capabilities',
606
+ type: 'table',
607
+ functionalRequirements:
608
+ '## Entity List Requirements\n\n- Display entities in grid or card view based on viewMode prop\n- Support sorting by multiple fields\n- Handle entity selection and notify parent\n- Show loading state while data loads\n- Display record count badges\n- Highlight selected entity\n- Support keyboard navigation',
609
+ dataRequirements: {
610
+ mode: 'views',
611
+ entities: [
656
612
  {
657
- "name": "Entities",
658
- "description": "Metadata about all entities in the system",
659
- "displayFields": [
660
- "ID",
661
- "Name",
662
- "DisplayName",
663
- "NameSuffix",
664
- "Description",
665
- "SchemaName",
666
- "BaseTable",
667
- "BaseView"
668
- ],
669
- "filterFields": [
670
- "SchemaName",
671
- "BaseTable"
672
- ],
673
- "sortFields": [
674
- "Name",
675
- "DisplayName"
676
- ],
677
- "fieldMetadata": [
613
+ name: 'Entities',
614
+ description: 'Metadata about all entities in the system',
615
+ displayFields: ['ID', 'Name', 'DisplayName', 'NameSuffix', 'Description', 'SchemaName', 'BaseTable', 'BaseView'],
616
+ filterFields: ['SchemaName', 'BaseTable'],
617
+ sortFields: ['Name', 'DisplayName'],
618
+ fieldMetadata: [
678
619
  {
679
- "name": "ID",
680
- "sequence": 1,
681
- "defaultInView": false,
682
- "type": "uniqueidentifier",
683
- "allowsNull": false,
684
- "isPrimaryKey": true,
685
- "description": "Unique identifier for the entity"
620
+ name: 'ID',
621
+ sequence: 1,
622
+ defaultInView: false,
623
+ type: 'uniqueidentifier',
624
+ allowsNull: false,
625
+ isPrimaryKey: true,
626
+ description: 'Unique identifier for the entity',
686
627
  },
687
628
  {
688
- "name": "Name",
689
- "sequence": 2,
690
- "defaultInView": true,
691
- "type": "nvarchar",
692
- "allowsNull": false,
693
- "isPrimaryKey": false,
694
- "description": "System name of the entity"
629
+ name: 'Name',
630
+ sequence: 2,
631
+ defaultInView: true,
632
+ type: 'nvarchar',
633
+ allowsNull: false,
634
+ isPrimaryKey: false,
635
+ description: 'System name of the entity',
695
636
  },
696
637
  {
697
- "name": "DisplayName",
698
- "sequence": 3,
699
- "defaultInView": true,
700
- "type": "nvarchar",
701
- "allowsNull": true,
702
- "isPrimaryKey": false,
703
- "description": "User-friendly display name for the entity"
638
+ name: 'DisplayName',
639
+ sequence: 3,
640
+ defaultInView: true,
641
+ type: 'nvarchar',
642
+ allowsNull: true,
643
+ isPrimaryKey: false,
644
+ description: 'User-friendly display name for the entity',
704
645
  },
705
646
  {
706
- "name": "NameSuffix",
707
- "sequence": 4,
708
- "defaultInView": true,
709
- "type": "nvarchar",
710
- "allowsNull": true,
711
- "isPrimaryKey": false,
712
- "description": "Optional suffix appended to entity names for display purposes"
647
+ name: 'NameSuffix',
648
+ sequence: 4,
649
+ defaultInView: true,
650
+ type: 'nvarchar',
651
+ allowsNull: true,
652
+ isPrimaryKey: false,
653
+ description: 'Optional suffix appended to entity names for display purposes',
713
654
  },
714
655
  {
715
- "name": "Description",
716
- "sequence": 5,
717
- "defaultInView": true,
718
- "type": "nvarchar",
719
- "allowsNull": true,
720
- "isPrimaryKey": false,
721
- "description": "Description of the entity"
656
+ name: 'Description',
657
+ sequence: 5,
658
+ defaultInView: true,
659
+ type: 'nvarchar',
660
+ allowsNull: true,
661
+ isPrimaryKey: false,
662
+ description: 'Description of the entity',
722
663
  },
723
664
  {
724
- "name": "SchemaName",
725
- "sequence": 6,
726
- "defaultInView": true,
727
- "type": "nvarchar",
728
- "allowsNull": true,
729
- "isPrimaryKey": false,
730
- "description": "Database schema name"
665
+ name: 'SchemaName',
666
+ sequence: 6,
667
+ defaultInView: true,
668
+ type: 'nvarchar',
669
+ allowsNull: true,
670
+ isPrimaryKey: false,
671
+ description: 'Database schema name',
731
672
  },
732
673
  {
733
- "name": "BaseTable",
734
- "sequence": 7,
735
- "defaultInView": true,
736
- "type": "nvarchar",
737
- "allowsNull": true,
738
- "isPrimaryKey": false,
739
- "description": "Base table in the database"
674
+ name: 'BaseTable',
675
+ sequence: 7,
676
+ defaultInView: true,
677
+ type: 'nvarchar',
678
+ allowsNull: true,
679
+ isPrimaryKey: false,
680
+ description: 'Base table in the database',
740
681
  },
741
682
  {
742
- "name": "BaseView",
743
- "sequence": 8,
744
- "defaultInView": true,
745
- "type": "nvarchar",
746
- "allowsNull": false,
747
- "isPrimaryKey": false,
748
- "description": "Base view used for the entity"
749
- }
750
- ],
751
- "permissionLevelNeeded": [
752
- "read"
683
+ name: 'BaseView',
684
+ sequence: 8,
685
+ defaultInView: true,
686
+ type: 'nvarchar',
687
+ allowsNull: false,
688
+ isPrimaryKey: false,
689
+ description: 'Base view used for the entity',
690
+ },
753
691
  ],
754
- "usageContext": "Main entity list display and filtering"
692
+ permissionLevelNeeded: ['read'],
693
+ usageContext: 'Main entity list display and filtering',
755
694
  },
756
695
  {
757
- "name": "Entity Fields",
758
- "description": "Fields belonging to each entity",
759
- "displayFields": [
760
- "Name",
761
- "DisplayName",
762
- "Type",
763
- "Length",
764
- "AllowsNull",
765
- "IsPrimaryKey",
766
- "IsUnique"
767
- ],
768
- "filterFields": [
769
- "EntityID"
770
- ],
771
- "sortFields": [
772
- "Sequence",
773
- "Name"
774
- ],
775
- "fieldMetadata": [
696
+ name: 'Entity Fields',
697
+ description: 'Fields belonging to each entity',
698
+ displayFields: ['Name', 'DisplayName', 'Type', 'Length', 'AllowsNull', 'IsPrimaryKey', 'IsUnique'],
699
+ filterFields: ['EntityID'],
700
+ sortFields: ['Sequence', 'Name'],
701
+ fieldMetadata: [
776
702
  {
777
- "name": "EntityID",
778
- "sequence": 1,
779
- "defaultInView": false,
780
- "type": "uniqueidentifier",
781
- "allowsNull": false,
782
- "isPrimaryKey": false,
783
- "description": "Reference to parent entity"
703
+ name: 'EntityID',
704
+ sequence: 1,
705
+ defaultInView: false,
706
+ type: 'uniqueidentifier',
707
+ allowsNull: false,
708
+ isPrimaryKey: false,
709
+ description: 'Reference to parent entity',
784
710
  },
785
711
  {
786
- "name": "Name",
787
- "sequence": 2,
788
- "defaultInView": true,
789
- "type": "nvarchar",
790
- "allowsNull": false,
791
- "isPrimaryKey": false,
792
- "description": "Field name"
712
+ name: 'Name',
713
+ sequence: 2,
714
+ defaultInView: true,
715
+ type: 'nvarchar',
716
+ allowsNull: false,
717
+ isPrimaryKey: false,
718
+ description: 'Field name',
793
719
  },
794
720
  {
795
- "name": "DisplayName",
796
- "sequence": 3,
797
- "defaultInView": true,
798
- "type": "nvarchar",
799
- "allowsNull": true,
800
- "isPrimaryKey": false,
801
- "description": "User-friendly field name"
721
+ name: 'DisplayName',
722
+ sequence: 3,
723
+ defaultInView: true,
724
+ type: 'nvarchar',
725
+ allowsNull: true,
726
+ isPrimaryKey: false,
727
+ description: 'User-friendly field name',
802
728
  },
803
729
  {
804
- "name": "Type",
805
- "sequence": 4,
806
- "defaultInView": true,
807
- "type": "nvarchar",
808
- "allowsNull": false,
809
- "isPrimaryKey": false,
810
- "description": "Data type of the field"
730
+ name: 'Type',
731
+ sequence: 4,
732
+ defaultInView: true,
733
+ type: 'nvarchar',
734
+ allowsNull: false,
735
+ isPrimaryKey: false,
736
+ description: 'Data type of the field',
811
737
  },
812
738
  {
813
- "name": "Length",
814
- "sequence": 5,
815
- "defaultInView": true,
816
- "type": "int",
817
- "allowsNull": true,
818
- "isPrimaryKey": false,
819
- "description": "Maximum length for string fields"
739
+ name: 'Length',
740
+ sequence: 5,
741
+ defaultInView: true,
742
+ type: 'int',
743
+ allowsNull: true,
744
+ isPrimaryKey: false,
745
+ description: 'Maximum length for string fields',
820
746
  },
821
747
  {
822
- "name": "AllowsNull",
823
- "sequence": 6,
824
- "defaultInView": true,
825
- "type": "bit",
826
- "allowsNull": false,
827
- "isPrimaryKey": false,
828
- "description": "Whether field allows null values"
748
+ name: 'AllowsNull',
749
+ sequence: 6,
750
+ defaultInView: true,
751
+ type: 'bit',
752
+ allowsNull: false,
753
+ isPrimaryKey: false,
754
+ description: 'Whether field allows null values',
829
755
  },
830
756
  {
831
- "name": "IsPrimaryKey",
832
- "sequence": 7,
833
- "defaultInView": true,
834
- "type": "bit",
835
- "allowsNull": false,
836
- "isPrimaryKey": false,
837
- "description": "Whether field is part of primary key"
757
+ name: 'IsPrimaryKey',
758
+ sequence: 7,
759
+ defaultInView: true,
760
+ type: 'bit',
761
+ allowsNull: false,
762
+ isPrimaryKey: false,
763
+ description: 'Whether field is part of primary key',
838
764
  },
839
765
  {
840
- "name": "IsUnique",
841
- "sequence": 8,
842
- "defaultInView": true,
843
- "type": "bit",
844
- "allowsNull": false,
845
- "isPrimaryKey": false,
846
- "description": "Whether field must be unique"
766
+ name: 'IsUnique',
767
+ sequence: 8,
768
+ defaultInView: true,
769
+ type: 'bit',
770
+ allowsNull: false,
771
+ isPrimaryKey: false,
772
+ description: 'Whether field must be unique',
847
773
  },
848
774
  {
849
- "name": "Sequence",
850
- "sequence": 9,
851
- "defaultInView": false,
852
- "type": "int",
853
- "allowsNull": false,
854
- "isPrimaryKey": false,
855
- "description": "Display order of the field"
856
- }
857
- ],
858
- "permissionLevelNeeded": [
859
- "read"
775
+ name: 'Sequence',
776
+ sequence: 9,
777
+ defaultInView: false,
778
+ type: 'int',
779
+ allowsNull: false,
780
+ isPrimaryKey: false,
781
+ description: 'Display order of the field',
782
+ },
860
783
  ],
861
- "usageContext": "Details panel to show entity fields"
784
+ permissionLevelNeeded: ['read'],
785
+ usageContext: 'Details panel to show entity fields',
862
786
  },
863
787
  {
864
- "name": "Entity Relationships",
865
- "description": "Relationships between entities",
866
- "displayFields": [
867
- "RelatedEntity",
868
- "Type",
869
- "DisplayName",
870
- "RelatedEntityJoinField"
871
- ],
872
- "filterFields": [
873
- "EntityID"
874
- ],
875
- "sortFields": [
876
- "Sequence",
877
- "RelatedEntity"
878
- ],
879
- "fieldMetadata": [
788
+ name: 'Entity Relationships',
789
+ description: 'Relationships between entities',
790
+ displayFields: ['RelatedEntity', 'Type', 'DisplayName', 'RelatedEntityJoinField'],
791
+ filterFields: ['EntityID'],
792
+ sortFields: ['Sequence', 'RelatedEntity'],
793
+ fieldMetadata: [
880
794
  {
881
- "name": "EntityID",
882
- "sequence": 1,
883
- "defaultInView": false,
884
- "type": "uniqueidentifier",
885
- "allowsNull": false,
886
- "isPrimaryKey": false,
887
- "description": "Reference to parent entity"
795
+ name: 'EntityID',
796
+ sequence: 1,
797
+ defaultInView: false,
798
+ type: 'uniqueidentifier',
799
+ allowsNull: false,
800
+ isPrimaryKey: false,
801
+ description: 'Reference to parent entity',
888
802
  },
889
803
  {
890
- "name": "RelatedEntity",
891
- "sequence": 2,
892
- "defaultInView": true,
893
- "type": "nvarchar",
894
- "allowsNull": false,
895
- "isPrimaryKey": false,
896
- "description": "The related entity in the relationship"
804
+ name: 'RelatedEntity',
805
+ sequence: 2,
806
+ defaultInView: true,
807
+ type: 'nvarchar',
808
+ allowsNull: false,
809
+ isPrimaryKey: false,
810
+ description: 'The related entity in the relationship',
897
811
  },
898
812
  {
899
- "name": "Type",
900
- "sequence": 3,
901
- "defaultInView": true,
902
- "type": "nvarchar",
903
- "allowsNull": false,
904
- "isPrimaryKey": false,
905
- "description": "Type of relationship (One to Many, Many to One, etc.)"
813
+ name: 'Type',
814
+ sequence: 3,
815
+ defaultInView: true,
816
+ type: 'nvarchar',
817
+ allowsNull: false,
818
+ isPrimaryKey: false,
819
+ description: 'Type of relationship (One to Many, Many to One, etc.)',
906
820
  },
907
821
  {
908
- "name": "DisplayName",
909
- "sequence": 4,
910
- "defaultInView": true,
911
- "type": "nvarchar",
912
- "allowsNull": true,
913
- "isPrimaryKey": false,
914
- "description": "User-friendly name for the relationship"
822
+ name: 'DisplayName',
823
+ sequence: 4,
824
+ defaultInView: true,
825
+ type: 'nvarchar',
826
+ allowsNull: true,
827
+ isPrimaryKey: false,
828
+ description: 'User-friendly name for the relationship',
915
829
  },
916
830
  {
917
- "name": "RelatedEntityJoinField",
918
- "sequence": 5,
919
- "defaultInView": true,
920
- "type": "nvarchar",
921
- "allowsNull": true,
922
- "isPrimaryKey": false,
923
- "description": "The field in the related entity that joins to this entity"
831
+ name: 'RelatedEntityJoinField',
832
+ sequence: 5,
833
+ defaultInView: true,
834
+ type: 'nvarchar',
835
+ allowsNull: true,
836
+ isPrimaryKey: false,
837
+ description: 'The field in the related entity that joins to this entity',
924
838
  },
925
839
  {
926
- "name": "Sequence",
927
- "sequence": 6,
928
- "defaultInView": false,
929
- "type": "int",
930
- "allowsNull": false,
931
- "isPrimaryKey": false,
932
- "description": "Display order"
933
- }
934
- ],
935
- "permissionLevelNeeded": [
936
- "read"
840
+ name: 'Sequence',
841
+ sequence: 6,
842
+ defaultInView: false,
843
+ type: 'int',
844
+ allowsNull: false,
845
+ isPrimaryKey: false,
846
+ description: 'Display order',
847
+ },
937
848
  ],
938
- "usageContext": "Details panel to show entity relationships"
939
- }
849
+ permissionLevelNeeded: ['read'],
850
+ usageContext: 'Details panel to show entity relationships',
851
+ },
940
852
  ],
941
- "queries": [],
942
- "description": "This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience"
853
+ queries: [],
854
+ description:
855
+ 'This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience',
943
856
  },
944
- "technicalDesign": "## Technical Design\n\n### Props\n- entities: Array of entity objects\n- viewMode: 'grid' | 'card'\n- selectedEntityId: Currently selected entity\n- onSelectEntity: Callback when entity selected\n- sortBy: Current sort field\n- sortDirection: 'asc' | 'desc'\n- onSortChange: Callback for sort changes\n\n### Rendering\n- Grid mode: Compact table with columns\n- Card mode: Cards with entity info\n- Sort indicators in headers\n- Selection highlighting",
945
- "properties": [
857
+ technicalDesign:
858
+ "## Technical Design\n\n### Props\n- entities: Array of entity objects\n- viewMode: 'grid' | 'card'\n- selectedEntityId: Currently selected entity\n- onSelectEntity: Callback when entity selected\n- sortBy: Current sort field\n- sortDirection: 'asc' | 'desc'\n- onSortChange: Callback for sort changes\n\n### Rendering\n- Grid mode: Compact table with columns\n- Card mode: Cards with entity info\n- Sort indicators in headers\n- Selection highlighting",
859
+ properties: [
946
860
  {
947
- "name": "entities",
948
- "description": "Array of entity objects to display",
949
- "type": "array",
950
- "required": true
861
+ name: 'entities',
862
+ description: 'Array of entity objects to display',
863
+ type: 'array',
864
+ required: true,
951
865
  },
952
866
  {
953
- "name": "viewMode",
954
- "description": "Display mode - grid or card view",
955
- "type": "string",
956
- "required": true,
957
- "possibleValues": [
958
- "grid",
959
- "card"
960
- ]
867
+ name: 'viewMode',
868
+ description: 'Display mode - grid or card view',
869
+ type: 'string',
870
+ required: true,
871
+ possibleValues: ['grid', 'card'],
961
872
  },
962
873
  {
963
- "name": "selectedEntityId",
964
- "description": "ID of the currently selected entity",
965
- "type": "string",
966
- "required": true
874
+ name: 'selectedEntityId',
875
+ description: 'ID of the currently selected entity',
876
+ type: 'string',
877
+ required: true,
967
878
  },
968
879
  {
969
- "name": "onSelectEntity",
970
- "description": "Callback when an entity is selected",
971
- "type": "function",
972
- "required": true
880
+ name: 'onSelectEntity',
881
+ description: 'Callback when an entity is selected',
882
+ type: 'function',
883
+ required: true,
973
884
  },
974
885
  {
975
- "name": "sortBy",
976
- "description": "Field to sort by",
977
- "type": "string",
978
- "required": true,
979
- "defaultValue": "Name"
886
+ name: 'sortBy',
887
+ description: 'Field to sort by',
888
+ type: 'string',
889
+ required: true,
890
+ defaultValue: 'Name',
980
891
  },
981
892
  {
982
- "name": "sortDirection",
983
- "description": "Sort direction",
984
- "type": "string",
985
- "required": true,
986
- "defaultValue": "asc",
987
- "possibleValues": [
988
- "asc",
989
- "desc"
990
- ]
893
+ name: 'sortDirection',
894
+ description: 'Sort direction',
895
+ type: 'string',
896
+ required: true,
897
+ defaultValue: 'asc',
898
+ possibleValues: ['asc', 'desc'],
991
899
  },
992
900
  {
993
- "name": "onSortChange",
994
- "description": "Callback when sort changes",
995
- "type": "function",
996
- "required": true
997
- }
901
+ name: 'onSortChange',
902
+ description: 'Callback when sort changes',
903
+ type: 'function',
904
+ required: true,
905
+ },
998
906
  ],
999
- "events": [
907
+ events: [
1000
908
  {
1001
- "name": "onSelectEntity",
1002
- "description": "Fired when an entity is selected",
1003
- "parameters": [
909
+ name: 'onSelectEntity',
910
+ description: 'Fired when an entity is selected',
911
+ parameters: [
1004
912
  {
1005
- "name": "entityId",
1006
- "description": "ID of the selected entity",
1007
- "type": "string"
1008
- }
1009
- ]
913
+ name: 'entityId',
914
+ description: 'ID of the selected entity',
915
+ type: 'string',
916
+ },
917
+ ],
1010
918
  },
1011
919
  {
1012
- "name": "onSortChange",
1013
- "description": "Fired when sort configuration changes",
1014
- "parameters": [
920
+ name: 'onSortChange',
921
+ description: 'Fired when sort configuration changes',
922
+ parameters: [
1015
923
  {
1016
- "name": "sortBy",
1017
- "description": "Field to sort by",
1018
- "type": "string"
924
+ name: 'sortBy',
925
+ description: 'Field to sort by',
926
+ type: 'string',
1019
927
  },
1020
928
  {
1021
- "name": "sortDirection",
1022
- "description": "Sort direction",
1023
- "type": "string"
1024
- }
1025
- ]
1026
- }
929
+ name: 'sortDirection',
930
+ description: 'Sort direction',
931
+ type: 'string',
932
+ },
933
+ ],
934
+ },
1027
935
  ],
1028
- "exampleUsage": "<EntityList\n entities={entities}\n viewMode={viewMode}\n selectedEntityId={selectedEntityId}\n onSelectEntity={handleSelectEntity}\n sortBy={sortBy}\n sortDirection={sortDirection}\n onSortChange={handleSortChange}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n/>",
1029
- "code": "function EntityList({\n entities,\n viewMode,\n selectedEntityId,\n onSelectEntity,\n sortBy,\n sortDirection,\n onSortChange,\n utilities,\n styles,\n components,\n callbacks,\n savedUserSettings,\n onSaveUserSettings\n}) {\n // Load DataGrid component from registry\n const DataGrid = components['DataGrid'];\n\n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n\n // Handle entity selection\n const handleEntityClick = useCallback((entityId) => {\n onSelectEntity?.(entityId);\n }, [onSelectEntity]);\n\n // Define columns for DataGrid\n const gridColumns = [\n {\n field: 'Name',\n header: 'Name',\n sortable: true,\n width: '150px'\n },\n {\n field: 'DisplayName',\n header: 'Display Name',\n sortable: true,\n width: '150px',\n render: (value, row) => value || row.Name\n },\n {\n field: 'Description',\n header: 'Description',\n sortable: false,\n width: '300px',\n render: (value) => value || '-'\n },\n {\n field: 'SchemaName',\n header: 'Schema',\n sortable: false,\n width: '120px',\n render: (value) => value || '-'\n },\n {\n field: 'BaseTable',\n header: 'Table',\n sortable: false,\n width: '150px',\n render: (value) => value || '-'\n },\n {\n field: 'BaseView',\n header: 'Base View',\n sortable: false,\n width: '150px',\n render: (value) => value || '-'\n }\n ];\n\n // Handle row click to open entity details\n const handleRowClick = useCallback((row) => {\n // When a row is clicked, select the entity and open its details\n handleEntityClick(row.ID);\n }, [handleEntityClick]);\n\n // Grid View\n if (viewMode === 'grid') {\n return (\n <div style={{\n width: '100%',\n overflowX: 'auto'\n }}>\n {DataGrid ? (\n <DataGrid\n data={entities}\n columns={gridColumns}\n pageSize={50}\n showFilters={true}\n showExport={false}\n selectionMode=\"none\" // Disable selection mode since we're using row clicks\n onRowClick={handleRowClick} // Handle row clicks to open the entity\n sortBy={sortBy}\n sortDirection={sortDirection}\n onSortChange={onSortChange}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n />\n ) : (\n <div style={{\n padding: styles.spacing.lg,\n textAlign: 'center',\n color: styles.colors.textSecondary\n }}>\n DataGrid component not available\n </div>\n )}\n </div>\n );\n }\n \n // Card View\n return (\n <div style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',\n gap: styles.spacing.lg\n }}>\n {entities.map((entity) => (\n <div\n key={entity.ID}\n onClick={() => handleEntityClick(entity.ID)}\n style={{\n padding: styles.spacing.lg,\n backgroundColor: selectedEntityId === entity.ID \n ? styles.colors.primary + '20'\n : styles.colors.surface,\n border: selectedEntityId === entity.ID\n ? `2px solid ${styles.colors.primary}`\n : `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('md'),\n cursor: 'pointer',\n transition: 'all 0.2s',\n position: 'relative'\n }}\n onMouseEnter={(e) => {\n if (selectedEntityId !== entity.ID) {\n e.currentTarget.style.transform = 'translateY(-2px)';\n e.currentTarget.style.boxShadow = `0 4px 12px ${styles.colors.shadow || 'rgba(0, 0, 0, 0.1)'}`;\n }\n }}\n onMouseLeave={(e) => {\n if (selectedEntityId !== entity.ID) {\n e.currentTarget.style.transform = 'translateY(0)';\n e.currentTarget.style.boxShadow = 'none';\n }\n }}\n >\n {/* Card Header */}\n <div style={{\n marginBottom: styles.spacing.md,\n paddingBottom: styles.spacing.md,\n borderBottom: `1px solid ${styles.colors.borderLight || styles.colors.border}`\n }}>\n <h3 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text,\n marginBottom: styles.spacing.xs\n }}>\n {entity.DisplayName || entity.Name}\n </h3>\n {entity.DisplayName && entity.DisplayName !== entity.Name && (\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n {entity.Name}\n </div>\n )}\n </div>\n \n {/* Card Body */}\n {entity.Description && (\n <p style={{\n margin: 0,\n marginBottom: styles.spacing.md,\n fontSize: styles.typography.fontSize.md,\n color: styles.colors.textSecondary,\n lineHeight: 1.5,\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden'\n }}>\n {entity.Description}\n </p>\n )}\n \n {/* Card Footer */}\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n <div>\n {entity.SchemaName && (\n <span style={{ marginRight: styles.spacing.md }}>\n Schema: <strong>{entity.SchemaName}</strong>\n </span>\n )}\n {entity.BaseTable && (\n <span>\n Table: <strong>{entity.BaseTable}</strong>\n </span>\n )}\n </div>\n {entity.BaseView && (\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n View: {entity.BaseView}\n </div>\n )}\n </div>\n \n {/* Selection Indicator */}\n {selectedEntityId === entity.ID && (\n <div style={{\n position: 'absolute',\n top: styles.spacing.sm,\n right: styles.spacing.sm,\n width: '8px',\n height: '8px',\n backgroundColor: styles.colors.primary,\n borderRadius: '50%'\n }} />\n )}\n </div>\n ))}\n </div>\n );\n}",
1030
- "dependencies": [
936
+ exampleUsage:
937
+ '<EntityList\n entities={entities}\n viewMode={viewMode}\n selectedEntityId={selectedEntityId}\n onSelectEntity={handleSelectEntity}\n sortBy={sortBy}\n sortDirection={sortDirection}\n onSortChange={handleSortChange}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n/>',
938
+ code: "function EntityList({\n entities,\n viewMode,\n selectedEntityId,\n onSelectEntity,\n sortBy,\n sortDirection,\n onSortChange,\n utilities,\n styles,\n components,\n callbacks,\n savedUserSettings,\n onSaveUserSettings\n}) {\n // Load DataGrid component from registry\n const DataGrid = components['DataGrid'];\n\n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n\n // Handle entity selection\n const handleEntityClick = useCallback((entityId) => {\n onSelectEntity?.(entityId);\n }, [onSelectEntity]);\n\n // Define columns for DataGrid\n const gridColumns = [\n {\n field: 'Name',\n header: 'Name',\n sortable: true,\n width: '150px'\n },\n {\n field: 'DisplayName',\n header: 'Display Name',\n sortable: true,\n width: '150px',\n render: (value, row) => value || row.Name\n },\n {\n field: 'Description',\n header: 'Description',\n sortable: false,\n width: '300px',\n render: (value) => value || '-'\n },\n {\n field: 'SchemaName',\n header: 'Schema',\n sortable: false,\n width: '120px',\n render: (value) => value || '-'\n },\n {\n field: 'BaseTable',\n header: 'Table',\n sortable: false,\n width: '150px',\n render: (value) => value || '-'\n },\n {\n field: 'BaseView',\n header: 'Base View',\n sortable: false,\n width: '150px',\n render: (value) => value || '-'\n }\n ];\n\n // Handle row click to open entity details\n const handleRowClick = useCallback((row) => {\n // When a row is clicked, select the entity and open its details\n handleEntityClick(row.ID);\n }, [handleEntityClick]);\n\n // Grid View\n if (viewMode === 'grid') {\n return (\n <div style={{\n width: '100%',\n overflowX: 'auto'\n }}>\n {DataGrid ? (\n <DataGrid\n data={entities}\n columns={gridColumns}\n pageSize={50}\n showFilters={true}\n showExport={false}\n selectionMode=\"none\" // Disable selection mode since we're using row clicks\n onRowClick={handleRowClick} // Handle row clicks to open the entity\n sortBy={sortBy}\n sortDirection={sortDirection}\n onSortChange={onSortChange}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n />\n ) : (\n <div style={{\n padding: styles.spacing.lg,\n textAlign: 'center',\n color: styles.colors.textSecondary\n }}>\n DataGrid component not available\n </div>\n )}\n </div>\n );\n }\n \n // Card View\n return (\n <div style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',\n gap: styles.spacing.lg\n }}>\n {entities.map((entity) => (\n <div\n key={entity.ID}\n onClick={() => handleEntityClick(entity.ID)}\n style={{\n padding: styles.spacing.lg,\n backgroundColor: selectedEntityId === entity.ID \n ? styles.colors.primary + '20'\n : styles.colors.surface,\n border: selectedEntityId === entity.ID\n ? `2px solid ${styles.colors.primary}`\n : `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('md'),\n cursor: 'pointer',\n transition: 'all 0.2s',\n position: 'relative'\n }}\n onMouseEnter={(e) => {\n if (selectedEntityId !== entity.ID) {\n e.currentTarget.style.transform = 'translateY(-2px)';\n e.currentTarget.style.boxShadow = `0 4px 12px ${styles.colors.shadow || 'rgba(0, 0, 0, 0.1)'}`;\n }\n }}\n onMouseLeave={(e) => {\n if (selectedEntityId !== entity.ID) {\n e.currentTarget.style.transform = 'translateY(0)';\n e.currentTarget.style.boxShadow = 'none';\n }\n }}\n >\n {/* Card Header */}\n <div style={{\n marginBottom: styles.spacing.md,\n paddingBottom: styles.spacing.md,\n borderBottom: `1px solid ${styles.colors.borderLight || styles.colors.border}`\n }}>\n <h3 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text,\n marginBottom: styles.spacing.xs\n }}>\n {entity.DisplayName || entity.Name}\n </h3>\n {entity.DisplayName && entity.DisplayName !== entity.Name && (\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n {entity.Name}\n </div>\n )}\n </div>\n \n {/* Card Body */}\n {entity.Description && (\n <p style={{\n margin: 0,\n marginBottom: styles.spacing.md,\n fontSize: styles.typography.fontSize.md,\n color: styles.colors.textSecondary,\n lineHeight: 1.5,\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden'\n }}>\n {entity.Description}\n </p>\n )}\n \n {/* Card Footer */}\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n <div>\n {entity.SchemaName && (\n <span style={{ marginRight: styles.spacing.md }}>\n Schema: <strong>{entity.SchemaName}</strong>\n </span>\n )}\n {entity.BaseTable && (\n <span>\n Table: <strong>{entity.BaseTable}</strong>\n </span>\n )}\n </div>\n {entity.BaseView && (\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n View: {entity.BaseView}\n </div>\n )}\n </div>\n \n {/* Selection Indicator */}\n {selectedEntityId === entity.ID && (\n <div style={{\n position: 'absolute',\n top: styles.spacing.sm,\n right: styles.spacing.sm,\n width: '8px',\n height: '8px',\n backgroundColor: styles.colors.primary,\n borderRadius: '50%'\n }} />\n )}\n </div>\n ))}\n </div>\n );\n}",
939
+ dependencies: [
1031
940
  {
1032
- "name": "DataGrid",
1033
- "location": "registry",
1034
- "namespace": "Generic/UI/Table",
1035
- "version": "^1.0.0"
1036
- }
941
+ name: 'DataGrid',
942
+ location: 'registry',
943
+ namespace: 'Generic/UI/Table',
944
+ version: '^1.0.0',
945
+ },
1037
946
  ],
1038
- "libraries": []
947
+ libraries: [],
1039
948
  },
1040
949
  {
1041
- "name": "EntityDetails",
1042
- "title": "Entity Details Panel",
1043
- "description": "Sliding panel that displays detailed information about a selected entity including fields and relationships",
1044
- "type": "form",
1045
- "functionalRequirements": "## Entity Details Requirements\n\n- Slide in from the right when an entity is selected\n- Display entity metadata at the top\n- Show fields in a formatted table\n- Display relationships with icons\n- Include 'Open Record' button\n- Support closing via X button or Escape key\n- Smooth slide animation\n- Scrollable content area",
1046
- "technicalDesign": "## Technical Design\n\n### Props\n- entity: Selected entity object\n- fields: Array of entity fields\n- relationships: Array of entity relationships\n- isOpen: Whether panel is visible\n- onClose: Callback to close panel\n- onOpenRecord: Callback to open entity record\n\n### Layout\n- Fixed position overlay\n- Slide animation using transform\n- Header with entity name and close button\n- Sections for metadata, fields, relationships\n- Sticky 'Open Record' button at bottom",
1047
- "dataRequirements": {
1048
- "mode": "views",
1049
- "entities": [
950
+ name: 'EntityDetails',
951
+ title: 'Entity Details Panel',
952
+ description: 'Sliding panel that displays detailed information about a selected entity including fields and relationships',
953
+ type: 'form',
954
+ functionalRequirements:
955
+ "## Entity Details Requirements\n\n- Slide in from the right when an entity is selected\n- Display entity metadata at the top\n- Show fields in a formatted table\n- Display relationships with icons\n- Include 'Open Record' button\n- Support closing via X button or Escape key\n- Smooth slide animation\n- Scrollable content area",
956
+ technicalDesign:
957
+ "## Technical Design\n\n### Props\n- entity: Selected entity object\n- fields: Array of entity fields\n- relationships: Array of entity relationships\n- isOpen: Whether panel is visible\n- onClose: Callback to close panel\n- onOpenRecord: Callback to open entity record\n\n### Layout\n- Fixed position overlay\n- Slide animation using transform\n- Header with entity name and close button\n- Sections for metadata, fields, relationships\n- Sticky 'Open Record' button at bottom",
958
+ dataRequirements: {
959
+ mode: 'views',
960
+ entities: [
1050
961
  {
1051
- "name": "Entities",
1052
- "description": "Metadata about all entities in the system",
1053
- "displayFields": [
1054
- "ID",
1055
- "Name",
1056
- "DisplayName",
1057
- "NameSuffix",
1058
- "Description",
1059
- "SchemaName",
1060
- "BaseTable",
1061
- "BaseView"
1062
- ],
1063
- "filterFields": [
1064
- "SchemaName",
1065
- "BaseTable"
1066
- ],
1067
- "sortFields": [
1068
- "Name",
1069
- "DisplayName"
1070
- ],
1071
- "fieldMetadata": [
962
+ name: 'Entities',
963
+ description: 'Metadata about all entities in the system',
964
+ displayFields: ['ID', 'Name', 'DisplayName', 'NameSuffix', 'Description', 'SchemaName', 'BaseTable', 'BaseView'],
965
+ filterFields: ['SchemaName', 'BaseTable'],
966
+ sortFields: ['Name', 'DisplayName'],
967
+ fieldMetadata: [
1072
968
  {
1073
- "name": "ID",
1074
- "sequence": 1,
1075
- "defaultInView": false,
1076
- "type": "uniqueidentifier",
1077
- "allowsNull": false,
1078
- "isPrimaryKey": true,
1079
- "description": "Unique identifier for the entity"
969
+ name: 'ID',
970
+ sequence: 1,
971
+ defaultInView: false,
972
+ type: 'uniqueidentifier',
973
+ allowsNull: false,
974
+ isPrimaryKey: true,
975
+ description: 'Unique identifier for the entity',
1080
976
  },
1081
977
  {
1082
- "name": "Name",
1083
- "sequence": 2,
1084
- "defaultInView": true,
1085
- "type": "nvarchar",
1086
- "allowsNull": false,
1087
- "isPrimaryKey": false,
1088
- "description": "System name of the entity"
978
+ name: 'Name',
979
+ sequence: 2,
980
+ defaultInView: true,
981
+ type: 'nvarchar',
982
+ allowsNull: false,
983
+ isPrimaryKey: false,
984
+ description: 'System name of the entity',
1089
985
  },
1090
986
  {
1091
- "name": "DisplayName",
1092
- "sequence": 3,
1093
- "defaultInView": true,
1094
- "type": "nvarchar",
1095
- "allowsNull": true,
1096
- "isPrimaryKey": false,
1097
- "description": "User-friendly display name for the entity"
987
+ name: 'DisplayName',
988
+ sequence: 3,
989
+ defaultInView: true,
990
+ type: 'nvarchar',
991
+ allowsNull: true,
992
+ isPrimaryKey: false,
993
+ description: 'User-friendly display name for the entity',
1098
994
  },
1099
995
  {
1100
- "name": "NameSuffix",
1101
- "sequence": 4,
1102
- "defaultInView": true,
1103
- "type": "nvarchar",
1104
- "allowsNull": true,
1105
- "isPrimaryKey": false,
1106
- "description": "Optional suffix appended to entity names for display purposes"
996
+ name: 'NameSuffix',
997
+ sequence: 4,
998
+ defaultInView: true,
999
+ type: 'nvarchar',
1000
+ allowsNull: true,
1001
+ isPrimaryKey: false,
1002
+ description: 'Optional suffix appended to entity names for display purposes',
1107
1003
  },
1108
1004
  {
1109
- "name": "Description",
1110
- "sequence": 5,
1111
- "defaultInView": true,
1112
- "type": "nvarchar",
1113
- "allowsNull": true,
1114
- "isPrimaryKey": false,
1115
- "description": "Description of the entity"
1005
+ name: 'Description',
1006
+ sequence: 5,
1007
+ defaultInView: true,
1008
+ type: 'nvarchar',
1009
+ allowsNull: true,
1010
+ isPrimaryKey: false,
1011
+ description: 'Description of the entity',
1116
1012
  },
1117
1013
  {
1118
- "name": "SchemaName",
1119
- "sequence": 6,
1120
- "defaultInView": true,
1121
- "type": "nvarchar",
1122
- "allowsNull": true,
1123
- "isPrimaryKey": false,
1124
- "description": "Database schema name"
1014
+ name: 'SchemaName',
1015
+ sequence: 6,
1016
+ defaultInView: true,
1017
+ type: 'nvarchar',
1018
+ allowsNull: true,
1019
+ isPrimaryKey: false,
1020
+ description: 'Database schema name',
1125
1021
  },
1126
1022
  {
1127
- "name": "BaseTable",
1128
- "sequence": 7,
1129
- "defaultInView": true,
1130
- "type": "nvarchar",
1131
- "allowsNull": true,
1132
- "isPrimaryKey": false,
1133
- "description": "Base table in the database"
1023
+ name: 'BaseTable',
1024
+ sequence: 7,
1025
+ defaultInView: true,
1026
+ type: 'nvarchar',
1027
+ allowsNull: true,
1028
+ isPrimaryKey: false,
1029
+ description: 'Base table in the database',
1134
1030
  },
1135
1031
  {
1136
- "name": "BaseView",
1137
- "sequence": 8,
1138
- "defaultInView": true,
1139
- "type": "nvarchar",
1140
- "allowsNull": false,
1141
- "isPrimaryKey": false,
1142
- "description": "Base view used for the entity"
1143
- }
1144
- ],
1145
- "permissionLevelNeeded": [
1146
- "read"
1032
+ name: 'BaseView',
1033
+ sequence: 8,
1034
+ defaultInView: true,
1035
+ type: 'nvarchar',
1036
+ allowsNull: false,
1037
+ isPrimaryKey: false,
1038
+ description: 'Base view used for the entity',
1039
+ },
1147
1040
  ],
1148
- "usageContext": "Main entity list display and filtering"
1041
+ permissionLevelNeeded: ['read'],
1042
+ usageContext: 'Main entity list display and filtering',
1149
1043
  },
1150
1044
  {
1151
- "name": "Entity Fields",
1152
- "description": "Fields belonging to each entity",
1153
- "displayFields": [
1154
- "Name",
1155
- "DisplayName",
1156
- "Type",
1157
- "Length",
1158
- "AllowsNull",
1159
- "IsPrimaryKey",
1160
- "IsUnique"
1161
- ],
1162
- "filterFields": [
1163
- "EntityID"
1164
- ],
1165
- "sortFields": [
1166
- "Sequence",
1167
- "Name"
1168
- ],
1169
- "fieldMetadata": [
1045
+ name: 'Entity Fields',
1046
+ description: 'Fields belonging to each entity',
1047
+ displayFields: ['Name', 'DisplayName', 'Type', 'Length', 'AllowsNull', 'IsPrimaryKey', 'IsUnique'],
1048
+ filterFields: ['EntityID'],
1049
+ sortFields: ['Sequence', 'Name'],
1050
+ fieldMetadata: [
1170
1051
  {
1171
- "name": "EntityID",
1172
- "sequence": 1,
1173
- "defaultInView": false,
1174
- "type": "uniqueidentifier",
1175
- "allowsNull": false,
1176
- "isPrimaryKey": false,
1177
- "description": "Reference to parent entity"
1052
+ name: 'EntityID',
1053
+ sequence: 1,
1054
+ defaultInView: false,
1055
+ type: 'uniqueidentifier',
1056
+ allowsNull: false,
1057
+ isPrimaryKey: false,
1058
+ description: 'Reference to parent entity',
1178
1059
  },
1179
1060
  {
1180
- "name": "Name",
1181
- "sequence": 2,
1182
- "defaultInView": true,
1183
- "type": "nvarchar",
1184
- "allowsNull": false,
1185
- "isPrimaryKey": false,
1186
- "description": "Field name"
1061
+ name: 'Name',
1062
+ sequence: 2,
1063
+ defaultInView: true,
1064
+ type: 'nvarchar',
1065
+ allowsNull: false,
1066
+ isPrimaryKey: false,
1067
+ description: 'Field name',
1187
1068
  },
1188
1069
  {
1189
- "name": "DisplayName",
1190
- "sequence": 3,
1191
- "defaultInView": true,
1192
- "type": "nvarchar",
1193
- "allowsNull": true,
1194
- "isPrimaryKey": false,
1195
- "description": "User-friendly field name"
1070
+ name: 'DisplayName',
1071
+ sequence: 3,
1072
+ defaultInView: true,
1073
+ type: 'nvarchar',
1074
+ allowsNull: true,
1075
+ isPrimaryKey: false,
1076
+ description: 'User-friendly field name',
1196
1077
  },
1197
1078
  {
1198
- "name": "Type",
1199
- "sequence": 4,
1200
- "defaultInView": true,
1201
- "type": "nvarchar",
1202
- "allowsNull": false,
1203
- "isPrimaryKey": false,
1204
- "description": "Data type of the field"
1079
+ name: 'Type',
1080
+ sequence: 4,
1081
+ defaultInView: true,
1082
+ type: 'nvarchar',
1083
+ allowsNull: false,
1084
+ isPrimaryKey: false,
1085
+ description: 'Data type of the field',
1205
1086
  },
1206
1087
  {
1207
- "name": "Length",
1208
- "sequence": 5,
1209
- "defaultInView": true,
1210
- "type": "int",
1211
- "allowsNull": true,
1212
- "isPrimaryKey": false,
1213
- "description": "Maximum length for string fields"
1088
+ name: 'Length',
1089
+ sequence: 5,
1090
+ defaultInView: true,
1091
+ type: 'int',
1092
+ allowsNull: true,
1093
+ isPrimaryKey: false,
1094
+ description: 'Maximum length for string fields',
1214
1095
  },
1215
1096
  {
1216
- "name": "AllowsNull",
1217
- "sequence": 6,
1218
- "defaultInView": true,
1219
- "type": "bit",
1220
- "allowsNull": false,
1221
- "isPrimaryKey": false,
1222
- "description": "Whether field allows null values"
1097
+ name: 'AllowsNull',
1098
+ sequence: 6,
1099
+ defaultInView: true,
1100
+ type: 'bit',
1101
+ allowsNull: false,
1102
+ isPrimaryKey: false,
1103
+ description: 'Whether field allows null values',
1223
1104
  },
1224
1105
  {
1225
- "name": "IsPrimaryKey",
1226
- "sequence": 7,
1227
- "defaultInView": true,
1228
- "type": "bit",
1229
- "allowsNull": false,
1230
- "isPrimaryKey": false,
1231
- "description": "Whether field is part of primary key"
1106
+ name: 'IsPrimaryKey',
1107
+ sequence: 7,
1108
+ defaultInView: true,
1109
+ type: 'bit',
1110
+ allowsNull: false,
1111
+ isPrimaryKey: false,
1112
+ description: 'Whether field is part of primary key',
1232
1113
  },
1233
1114
  {
1234
- "name": "IsUnique",
1235
- "sequence": 8,
1236
- "defaultInView": true,
1237
- "type": "bit",
1238
- "allowsNull": false,
1239
- "isPrimaryKey": false,
1240
- "description": "Whether field must be unique"
1115
+ name: 'IsUnique',
1116
+ sequence: 8,
1117
+ defaultInView: true,
1118
+ type: 'bit',
1119
+ allowsNull: false,
1120
+ isPrimaryKey: false,
1121
+ description: 'Whether field must be unique',
1241
1122
  },
1242
1123
  {
1243
- "name": "Sequence",
1244
- "sequence": 9,
1245
- "defaultInView": false,
1246
- "type": "int",
1247
- "allowsNull": false,
1248
- "isPrimaryKey": false,
1249
- "description": "Display order of the field"
1250
- }
1251
- ],
1252
- "permissionLevelNeeded": [
1253
- "read"
1124
+ name: 'Sequence',
1125
+ sequence: 9,
1126
+ defaultInView: false,
1127
+ type: 'int',
1128
+ allowsNull: false,
1129
+ isPrimaryKey: false,
1130
+ description: 'Display order of the field',
1131
+ },
1254
1132
  ],
1255
- "usageContext": "Details panel to show entity fields"
1133
+ permissionLevelNeeded: ['read'],
1134
+ usageContext: 'Details panel to show entity fields',
1256
1135
  },
1257
1136
  {
1258
- "name": "Entity Relationships",
1259
- "description": "Relationships between entities",
1260
- "displayFields": [
1261
- "RelatedEntity",
1262
- "Type",
1263
- "DisplayName",
1264
- "RelatedEntityJoinField"
1265
- ],
1266
- "filterFields": [
1267
- "EntityID"
1268
- ],
1269
- "sortFields": [
1270
- "Sequence",
1271
- "RelatedEntity"
1272
- ],
1273
- "fieldMetadata": [
1137
+ name: 'Entity Relationships',
1138
+ description: 'Relationships between entities',
1139
+ displayFields: ['RelatedEntity', 'Type', 'DisplayName', 'RelatedEntityJoinField'],
1140
+ filterFields: ['EntityID'],
1141
+ sortFields: ['Sequence', 'RelatedEntity'],
1142
+ fieldMetadata: [
1274
1143
  {
1275
- "name": "EntityID",
1276
- "sequence": 1,
1277
- "defaultInView": false,
1278
- "type": "uniqueidentifier",
1279
- "allowsNull": false,
1280
- "isPrimaryKey": false,
1281
- "description": "Reference to parent entity"
1144
+ name: 'EntityID',
1145
+ sequence: 1,
1146
+ defaultInView: false,
1147
+ type: 'uniqueidentifier',
1148
+ allowsNull: false,
1149
+ isPrimaryKey: false,
1150
+ description: 'Reference to parent entity',
1282
1151
  },
1283
1152
  {
1284
- "name": "RelatedEntity",
1285
- "sequence": 2,
1286
- "defaultInView": true,
1287
- "type": "nvarchar",
1288
- "allowsNull": false,
1289
- "isPrimaryKey": false,
1290
- "description": "The related entity in the relationship"
1153
+ name: 'RelatedEntity',
1154
+ sequence: 2,
1155
+ defaultInView: true,
1156
+ type: 'nvarchar',
1157
+ allowsNull: false,
1158
+ isPrimaryKey: false,
1159
+ description: 'The related entity in the relationship',
1291
1160
  },
1292
1161
  {
1293
- "name": "Type",
1294
- "sequence": 3,
1295
- "defaultInView": true,
1296
- "type": "nvarchar",
1297
- "allowsNull": false,
1298
- "isPrimaryKey": false,
1299
- "description": "Type of relationship (One to Many, Many to One, etc.)"
1162
+ name: 'Type',
1163
+ sequence: 3,
1164
+ defaultInView: true,
1165
+ type: 'nvarchar',
1166
+ allowsNull: false,
1167
+ isPrimaryKey: false,
1168
+ description: 'Type of relationship (One to Many, Many to One, etc.)',
1300
1169
  },
1301
1170
  {
1302
- "name": "DisplayName",
1303
- "sequence": 4,
1304
- "defaultInView": true,
1305
- "type": "nvarchar",
1306
- "allowsNull": true,
1307
- "isPrimaryKey": false,
1308
- "description": "User-friendly name for the relationship"
1171
+ name: 'DisplayName',
1172
+ sequence: 4,
1173
+ defaultInView: true,
1174
+ type: 'nvarchar',
1175
+ allowsNull: true,
1176
+ isPrimaryKey: false,
1177
+ description: 'User-friendly name for the relationship',
1309
1178
  },
1310
1179
  {
1311
- "name": "RelatedEntityJoinField",
1312
- "sequence": 5,
1313
- "defaultInView": true,
1314
- "type": "nvarchar",
1315
- "allowsNull": true,
1316
- "isPrimaryKey": false,
1317
- "description": "The field in the related entity that joins to this entity"
1180
+ name: 'RelatedEntityJoinField',
1181
+ sequence: 5,
1182
+ defaultInView: true,
1183
+ type: 'nvarchar',
1184
+ allowsNull: true,
1185
+ isPrimaryKey: false,
1186
+ description: 'The field in the related entity that joins to this entity',
1318
1187
  },
1319
1188
  {
1320
- "name": "Sequence",
1321
- "sequence": 6,
1322
- "defaultInView": false,
1323
- "type": "int",
1324
- "allowsNull": false,
1325
- "isPrimaryKey": false,
1326
- "description": "Display order"
1327
- }
1328
- ],
1329
- "permissionLevelNeeded": [
1330
- "read"
1189
+ name: 'Sequence',
1190
+ sequence: 6,
1191
+ defaultInView: false,
1192
+ type: 'int',
1193
+ allowsNull: false,
1194
+ isPrimaryKey: false,
1195
+ description: 'Display order',
1196
+ },
1331
1197
  ],
1332
- "usageContext": "Details panel to show entity relationships"
1333
- }
1198
+ permissionLevelNeeded: ['read'],
1199
+ usageContext: 'Details panel to show entity relationships',
1200
+ },
1334
1201
  ],
1335
- "queries": [],
1336
- "description": "This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience"
1202
+ queries: [],
1203
+ description:
1204
+ 'This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience',
1337
1205
  },
1338
- "properties": [
1206
+ properties: [
1339
1207
  {
1340
- "name": "entity",
1341
- "description": "The selected entity object",
1342
- "type": "object",
1343
- "required": true
1208
+ name: 'entity',
1209
+ description: 'The selected entity object',
1210
+ type: 'object',
1211
+ required: true,
1344
1212
  },
1345
1213
  {
1346
- "name": "fields",
1347
- "description": "Array of fields for the entity",
1348
- "type": "array",
1349
- "required": true,
1350
- "defaultValue": []
1214
+ name: 'fields',
1215
+ description: 'Array of fields for the entity',
1216
+ type: 'array',
1217
+ required: true,
1218
+ defaultValue: [],
1351
1219
  },
1352
1220
  {
1353
- "name": "relationships",
1354
- "description": "Array of relationships for the entity",
1355
- "type": "array",
1356
- "required": true,
1357
- "defaultValue": []
1221
+ name: 'relationships',
1222
+ description: 'Array of relationships for the entity',
1223
+ type: 'array',
1224
+ required: true,
1225
+ defaultValue: [],
1358
1226
  },
1359
1227
  {
1360
- "name": "isOpen",
1361
- "description": "Whether the panel is open",
1362
- "type": "boolean",
1363
- "required": true
1228
+ name: 'isOpen',
1229
+ description: 'Whether the panel is open',
1230
+ type: 'boolean',
1231
+ required: true,
1364
1232
  },
1365
1233
  {
1366
- "name": "onClose",
1367
- "description": "Callback to close the panel",
1368
- "type": "function",
1369
- "required": true
1234
+ name: 'onClose',
1235
+ description: 'Callback to close the panel',
1236
+ type: 'function',
1237
+ required: true,
1370
1238
  },
1371
1239
  {
1372
- "name": "onOpenRecord",
1373
- "description": "Callback to open the entity record",
1374
- "type": "function",
1375
- "required": true
1376
- }
1240
+ name: 'onOpenRecord',
1241
+ description: 'Callback to open the entity record',
1242
+ type: 'function',
1243
+ required: true,
1244
+ },
1377
1245
  ],
1378
- "events": [
1246
+ events: [
1379
1247
  {
1380
- "name": "onClose",
1381
- "description": "Fired when the panel should close",
1382
- "parameters": []
1248
+ name: 'onClose',
1249
+ description: 'Fired when the panel should close',
1250
+ parameters: [],
1383
1251
  },
1384
1252
  {
1385
- "name": "onOpenRecord",
1386
- "description": "Fired when the open record button is clicked",
1387
- "parameters": [
1253
+ name: 'onOpenRecord',
1254
+ description: 'Fired when the open record button is clicked',
1255
+ parameters: [
1388
1256
  {
1389
- "name": "entityName",
1390
- "description": "Name of the entity to open",
1391
- "type": "string"
1392
- }
1393
- ]
1394
- }
1257
+ name: 'entityName',
1258
+ description: 'Name of the entity to open',
1259
+ type: 'string',
1260
+ },
1261
+ ],
1262
+ },
1395
1263
  ],
1396
- "exampleUsage": "<EntityDetails\n entity={selectedEntity}\n fields={entityFields}\n relationships={entityRelationships}\n isOpen={detailsPanelOpen}\n onClose={handleCloseDetails}\n onOpenRecord={handleOpenRecord}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n/>",
1397
- "code": "function EntityDetails({ \n entity, \n fields, \n relationships, \n isOpen, \n onClose, \n onOpenRecord,\n utilities, \n styles, \n components, \n callbacks, \n savedUserSettings, \n onSaveUserSettings \n}) {\n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n \n // Handle escape key to close panel\n useEffect(() => {\n const handleEscape = (e) => {\n if (e.key === 'Escape' && isOpen) {\n onClose?.();\n }\n };\n \n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isOpen, onClose]);\n \n // Load OpenRecordButton component\n const OpenRecordButton = components['OpenRecordButton'];\n \n // Render field type badge\n const renderFieldType = (type) => {\n const typeColors = {\n 'nvarchar': styles.colors.info || styles.colors.primary,\n 'varchar': styles.colors.info || styles.colors.primary,\n 'int': styles.colors.success || styles.colors.primary,\n 'bigint': styles.colors.success || styles.colors.primary,\n 'decimal': styles.colors.success || styles.colors.primary,\n 'float': styles.colors.success || styles.colors.primary,\n 'bit': styles.colors.warning || styles.colors.secondary,\n 'datetime': styles.colors.secondary,\n 'uniqueidentifier': styles.colors.primary,\n 'text': styles.colors.info || styles.colors.primary,\n 'ntext': styles.colors.info || styles.colors.primary\n };\n \n const color = typeColors[type?.toLowerCase()] || styles.colors.textSecondary;\n \n return (\n <span style={{\n display: 'inline-block',\n padding: `${styles.spacing.xs} ${styles.spacing.sm}`,\n backgroundColor: color + '15',\n color: color,\n borderRadius: getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500'\n }}>\n {type}\n </span>\n );\n };\n \n // Render relationship type icon\n const renderRelationshipIcon = (type) => {\n const icons = {\n 'One to Many': '1:N',\n 'Many to One': 'N:1',\n 'Many to Many': 'N:N',\n 'One to One': '1:1'\n };\n \n return (\n <span style={{\n display: 'inline-block',\n padding: `${styles.spacing.xs} ${styles.spacing.sm}`,\n backgroundColor: styles.colors.primary + '15',\n color: styles.colors.primary,\n borderRadius: getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700',\n fontFamily: 'monospace'\n }}>\n {icons[type] || type}\n </span>\n );\n };\n \n return (\n <>\n {/* Backdrop */}\n {isOpen && (\n <div\n onClick={onClose}\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'rgba(0, 0, 0, 0.3)',\n zIndex: 99999,\n opacity: isOpen ? 1 : 0,\n transition: 'opacity 0.3s',\n pointerEvents: isOpen ? 'auto' : 'none'\n }}\n />\n )}\n \n {/* Panel */}\n <div style={{\n position: 'fixed',\n top: '75px',\n right: 0,\n bottom: 0,\n width: '480px',\n backgroundColor: styles.colors.background,\n boxShadow: isOpen ? `-4px 0 24px ${styles.colors.shadow || 'rgba(0, 0, 0, 0.1)'}` : 'none',\n transform: isOpen ? 'translateX(0)' : 'translateX(100%)',\n transition: 'transform 0.3s ease-out',\n zIndex: 100000,\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden'\n }}>\n {/* Header */}\n <div style={{\n padding: styles.spacing.lg,\n borderBottom: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.surface\n }}>\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'flex-start'\n }}>\n <div style={{ flex: 1 }}>\n <h2 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.xl,\n fontWeight: styles.typography.fontWeight?.bold || '700',\n color: styles.colors.text,\n marginBottom: styles.spacing.xs\n }}>\n {entity?.DisplayName || entity?.Name || 'No Entity Selected'}\n </h2>\n {entity?.DisplayName && entity?.Name && entity.DisplayName !== entity.Name && (\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n fontFamily: 'monospace'\n }}>\n {entity.Name}\n </div>\n )}\n </div>\n <button\n onClick={onClose}\n style={{\n width: '32px',\n height: '32px',\n borderRadius: getBorderRadius('sm'),\n border: 'none',\n backgroundColor: 'transparent',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.lg,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n transition: 'background-color 0.2s'\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent';\n }}\n >\n ✕\n </button>\n </div>\n </div>\n \n {/* Content */}\n <div style={{\n flex: 1,\n overflow: 'auto',\n padding: styles.spacing.lg\n }}>\n {entity ? (\n <>\n {/* Entity Metadata */}\n {entity.Description && (\n <div style={{\n marginBottom: styles.spacing.xl,\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('md'),\n borderLeft: `3px solid ${styles.colors.primary}`\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n color: styles.colors.textSecondary,\n lineHeight: 1.6\n }}>\n {entity.Description}\n </div>\n </div>\n )}\n \n {/* Quick Info */}\n <div style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(2, 1fr)',\n gap: styles.spacing.md,\n marginBottom: styles.spacing.xl\n }}>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Schema\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {entity.SchemaName || '-'}\n </div>\n </div>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Base Table\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {entity.BaseTable || '-'}\n </div>\n </div>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Base View\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {entity.BaseView || '-'}\n </div>\n </div>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Field Count\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {fields?.length || 0}\n </div>\n </div>\n </div>\n \n {/* Fields Section */}\n <div style={{ marginBottom: styles.spacing.xl }}>\n <h3 style={{\n margin: 0,\n marginBottom: styles.spacing.md,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n Fields ({fields?.length || 0})\n </h3>\n <div style={{\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('md'),\n overflow: 'hidden'\n }}>\n {fields && fields.length > 0 ? (\n <table style={{\n width: '100%',\n borderCollapse: 'collapse'\n }}>\n <thead>\n <tr style={{\n borderBottom: `1px solid ${styles.colors.border}`\n }}>\n <th style={{\n padding: styles.spacing.sm,\n textAlign: 'left',\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Field\n </th>\n <th style={{\n padding: styles.spacing.sm,\n textAlign: 'left',\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Type\n </th>\n <th style={{\n padding: styles.spacing.sm,\n textAlign: 'center',\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Attributes\n </th>\n </tr>\n </thead>\n <tbody>\n {fields.map((field, index) => (\n <tr\n key={index}\n style={{\n borderBottom: index < fields.length - 1 \n ? `1px solid ${styles.colors.borderLight || styles.colors.border}` \n : 'none'\n }}\n >\n <td style={{\n padding: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.text\n }}>\n <div>\n <div style={{\n fontWeight: field.IsPrimaryKey \n ? (styles.typography.fontWeight?.semibold || '600')\n : (styles.typography.fontWeight?.regular || '400')\n }}>\n {field.DisplayName || field.Name}\n </div>\n {field.DisplayName && (\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n fontFamily: 'monospace'\n }}>\n {field.Name}\n </div>\n )}\n </div>\n </td>\n <td style={{\n padding: styles.spacing.sm\n }}>\n {renderFieldType(field.Type)}\n {field.Length && (\n <span style={{\n marginLeft: styles.spacing.xs,\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n ({field.Length})\n </span>\n )}\n </td>\n <td style={{\n padding: styles.spacing.sm,\n textAlign: 'center'\n }}>\n <div style={{\n display: 'flex',\n gap: styles.spacing.xs,\n justifyContent: 'center',\n flexWrap: 'wrap'\n }}>\n {field.IsPrimaryKey && (\n <span style={{\n padding: `2px ${styles.spacing.xs}`,\n backgroundColor: (styles.colors.warning || styles.colors.secondary) + '15',\n color: styles.colors.warning || styles.colors.secondary,\n borderRadius: getBorderRadius('xs') || getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n PK\n </span>\n )}\n {field.IsUnique && (\n <span style={{\n padding: `2px ${styles.spacing.xs}`,\n backgroundColor: (styles.colors.info || styles.colors.primary) + '15',\n color: styles.colors.info || styles.colors.primary,\n borderRadius: getBorderRadius('xs') || getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n UQ\n </span>\n )}\n {!field.AllowsNull && !field.IsPrimaryKey && (\n <span style={{\n padding: `2px ${styles.spacing.xs}`,\n backgroundColor: (styles.colors.error || styles.colors.secondary) + '15',\n color: styles.colors.error || styles.colors.secondary,\n borderRadius: getBorderRadius('xs') || getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n NN\n </span>\n )}\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n ) : (\n <div style={{\n padding: styles.spacing.lg,\n textAlign: 'center',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm\n }}>\n No fields available\n </div>\n )}\n </div>\n </div>\n \n {/* Relationships Section */}\n <div style={{ marginBottom: styles.spacing.xl }}>\n <h3 style={{\n margin: 0,\n marginBottom: styles.spacing.md,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n Relationships ({relationships?.length || 0})\n </h3>\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n gap: styles.spacing.sm\n }}>\n {relationships && relationships.length > 0 ? (\n relationships.map((rel, index) => (\n <div\n key={index}\n style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm'),\n display: 'flex',\n alignItems: 'center',\n gap: styles.spacing.md\n }}\n >\n {renderRelationshipIcon(rel.Type)}\n <div style={{ flex: 1 }}>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.text\n }}>\n {rel.DisplayName || rel.RelatedEntity}\n </div>\n {rel.RelatedEntityJoinField && (\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n fontFamily: 'monospace'\n }}>\n via {rel.RelatedEntityJoinField}\n </div>\n )}\n </div>\n </div>\n ))\n ) : (\n <div style={{\n padding: styles.spacing.lg,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm'),\n textAlign: 'center',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm\n }}>\n No relationships defined\n </div>\n )}\n </div>\n </div>\n </>\n ) : (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n color: styles.colors.textSecondary\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.lg,\n marginBottom: styles.spacing.md\n }}>\n No Entity Selected\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md\n }}>\n Select an entity from the list to view its details\n </div>\n </div>\n )}\n </div>\n \n {/* Footer with Open Record Button */}\n {entity && OpenRecordButton && (\n <div style={{\n padding: styles.spacing.lg,\n borderTop: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.surface\n }}>\n <OpenRecordButton\n entityName=\"Entities\"\n record={entity}\n buttonText=\"Open Entity Record\"\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n buttonStyle={{\n width: '100%',\n padding: styles.spacing.md,\n backgroundColor: styles.colors.primary,\n color: 'white',\n border: 'none',\n borderRadius: getBorderRadius('md'),\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n cursor: 'pointer',\n transition: 'background-color 0.2s'\n }}\n />\n </div>\n )}\n </div>\n </>\n );\n}",
1398
- "dependencies": [
1264
+ exampleUsage:
1265
+ '<EntityDetails\n entity={selectedEntity}\n fields={entityFields}\n relationships={entityRelationships}\n isOpen={detailsPanelOpen}\n onClose={handleCloseDetails}\n onOpenRecord={handleOpenRecord}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n/>',
1266
+ code: "function EntityDetails({ \n entity, \n fields, \n relationships, \n isOpen, \n onClose, \n onOpenRecord,\n utilities, \n styles, \n components, \n callbacks, \n savedUserSettings, \n onSaveUserSettings \n}) {\n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n \n // Handle escape key to close panel\n useEffect(() => {\n const handleEscape = (e) => {\n if (e.key === 'Escape' && isOpen) {\n onClose?.();\n }\n };\n \n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isOpen, onClose]);\n \n // Load OpenRecordButton component\n const OpenRecordButton = components['OpenRecordButton'];\n \n // Render field type badge\n const renderFieldType = (type) => {\n const typeColors = {\n 'nvarchar': styles.colors.info || styles.colors.primary,\n 'varchar': styles.colors.info || styles.colors.primary,\n 'int': styles.colors.success || styles.colors.primary,\n 'bigint': styles.colors.success || styles.colors.primary,\n 'decimal': styles.colors.success || styles.colors.primary,\n 'float': styles.colors.success || styles.colors.primary,\n 'bit': styles.colors.warning || styles.colors.secondary,\n 'datetime': styles.colors.secondary,\n 'uniqueidentifier': styles.colors.primary,\n 'text': styles.colors.info || styles.colors.primary,\n 'ntext': styles.colors.info || styles.colors.primary\n };\n \n const color = typeColors[type?.toLowerCase()] || styles.colors.textSecondary;\n \n return (\n <span style={{\n display: 'inline-block',\n padding: `${styles.spacing.xs} ${styles.spacing.sm}`,\n backgroundColor: color + '15',\n color: color,\n borderRadius: getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500'\n }}>\n {type}\n </span>\n );\n };\n \n // Render relationship type icon\n const renderRelationshipIcon = (type) => {\n const icons = {\n 'One to Many': '1:N',\n 'Many to One': 'N:1',\n 'Many to Many': 'N:N',\n 'One to One': '1:1'\n };\n \n return (\n <span style={{\n display: 'inline-block',\n padding: `${styles.spacing.xs} ${styles.spacing.sm}`,\n backgroundColor: styles.colors.primary + '15',\n color: styles.colors.primary,\n borderRadius: getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700',\n fontFamily: 'monospace'\n }}>\n {icons[type] || type}\n </span>\n );\n };\n \n return (\n <>\n {/* Backdrop */}\n {isOpen && (\n <div\n onClick={onClose}\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'rgba(0, 0, 0, 0.3)',\n zIndex: 99999,\n opacity: isOpen ? 1 : 0,\n transition: 'opacity 0.3s',\n pointerEvents: isOpen ? 'auto' : 'none'\n }}\n />\n )}\n \n {/* Panel */}\n <div style={{\n position: 'fixed',\n top: '75px',\n right: 0,\n bottom: 0,\n width: '480px',\n backgroundColor: styles.colors.background,\n boxShadow: isOpen ? `-4px 0 24px ${styles.colors.shadow || 'rgba(0, 0, 0, 0.1)'}` : 'none',\n transform: isOpen ? 'translateX(0)' : 'translateX(100%)',\n transition: 'transform 0.3s ease-out',\n zIndex: 100000,\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden'\n }}>\n {/* Header */}\n <div style={{\n padding: styles.spacing.lg,\n borderBottom: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.surface\n }}>\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'flex-start'\n }}>\n <div style={{ flex: 1 }}>\n <h2 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.xl,\n fontWeight: styles.typography.fontWeight?.bold || '700',\n color: styles.colors.text,\n marginBottom: styles.spacing.xs\n }}>\n {entity?.DisplayName || entity?.Name || 'No Entity Selected'}\n </h2>\n {entity?.DisplayName && entity?.Name && entity.DisplayName !== entity.Name && (\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n fontFamily: 'monospace'\n }}>\n {entity.Name}\n </div>\n )}\n </div>\n <button\n onClick={onClose}\n style={{\n width: '32px',\n height: '32px',\n borderRadius: getBorderRadius('sm'),\n border: 'none',\n backgroundColor: 'transparent',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.lg,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n transition: 'background-color 0.2s'\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent';\n }}\n >\n ✕\n </button>\n </div>\n </div>\n \n {/* Content */}\n <div style={{\n flex: 1,\n overflow: 'auto',\n padding: styles.spacing.lg\n }}>\n {entity ? (\n <>\n {/* Entity Metadata */}\n {entity.Description && (\n <div style={{\n marginBottom: styles.spacing.xl,\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('md'),\n borderLeft: `3px solid ${styles.colors.primary}`\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n color: styles.colors.textSecondary,\n lineHeight: 1.6\n }}>\n {entity.Description}\n </div>\n </div>\n )}\n \n {/* Quick Info */}\n <div style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(2, 1fr)',\n gap: styles.spacing.md,\n marginBottom: styles.spacing.xl\n }}>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Schema\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {entity.SchemaName || '-'}\n </div>\n </div>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Base Table\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {entity.BaseTable || '-'}\n </div>\n </div>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Base View\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {entity.BaseView || '-'}\n </div>\n </div>\n <div style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm')\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.xs\n }}>\n Field Count\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n {fields?.length || 0}\n </div>\n </div>\n </div>\n \n {/* Fields Section */}\n <div style={{ marginBottom: styles.spacing.xl }}>\n <h3 style={{\n margin: 0,\n marginBottom: styles.spacing.md,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n Fields ({fields?.length || 0})\n </h3>\n <div style={{\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('md'),\n overflow: 'hidden'\n }}>\n {fields && fields.length > 0 ? (\n <table style={{\n width: '100%',\n borderCollapse: 'collapse'\n }}>\n <thead>\n <tr style={{\n borderBottom: `1px solid ${styles.colors.border}`\n }}>\n <th style={{\n padding: styles.spacing.sm,\n textAlign: 'left',\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Field\n </th>\n <th style={{\n padding: styles.spacing.sm,\n textAlign: 'left',\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Type\n </th>\n <th style={{\n padding: styles.spacing.sm,\n textAlign: 'center',\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Attributes\n </th>\n </tr>\n </thead>\n <tbody>\n {fields.map((field, index) => (\n <tr\n key={index}\n style={{\n borderBottom: index < fields.length - 1 \n ? `1px solid ${styles.colors.borderLight || styles.colors.border}` \n : 'none'\n }}\n >\n <td style={{\n padding: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.text\n }}>\n <div>\n <div style={{\n fontWeight: field.IsPrimaryKey \n ? (styles.typography.fontWeight?.semibold || '600')\n : (styles.typography.fontWeight?.regular || '400')\n }}>\n {field.DisplayName || field.Name}\n </div>\n {field.DisplayName && (\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n fontFamily: 'monospace'\n }}>\n {field.Name}\n </div>\n )}\n </div>\n </td>\n <td style={{\n padding: styles.spacing.sm\n }}>\n {renderFieldType(field.Type)}\n {field.Length && (\n <span style={{\n marginLeft: styles.spacing.xs,\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n ({field.Length})\n </span>\n )}\n </td>\n <td style={{\n padding: styles.spacing.sm,\n textAlign: 'center'\n }}>\n <div style={{\n display: 'flex',\n gap: styles.spacing.xs,\n justifyContent: 'center',\n flexWrap: 'wrap'\n }}>\n {field.IsPrimaryKey && (\n <span style={{\n padding: `2px ${styles.spacing.xs}`,\n backgroundColor: (styles.colors.warning || styles.colors.secondary) + '15',\n color: styles.colors.warning || styles.colors.secondary,\n borderRadius: getBorderRadius('xs') || getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n PK\n </span>\n )}\n {field.IsUnique && (\n <span style={{\n padding: `2px ${styles.spacing.xs}`,\n backgroundColor: (styles.colors.info || styles.colors.primary) + '15',\n color: styles.colors.info || styles.colors.primary,\n borderRadius: getBorderRadius('xs') || getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n UQ\n </span>\n )}\n {!field.AllowsNull && !field.IsPrimaryKey && (\n <span style={{\n padding: `2px ${styles.spacing.xs}`,\n backgroundColor: (styles.colors.error || styles.colors.secondary) + '15',\n color: styles.colors.error || styles.colors.secondary,\n borderRadius: getBorderRadius('xs') || getBorderRadius('sm'),\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n NN\n </span>\n )}\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n ) : (\n <div style={{\n padding: styles.spacing.lg,\n textAlign: 'center',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm\n }}>\n No fields available\n </div>\n )}\n </div>\n </div>\n \n {/* Relationships Section */}\n <div style={{ marginBottom: styles.spacing.xl }}>\n <h3 style={{\n margin: 0,\n marginBottom: styles.spacing.md,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text\n }}>\n Relationships ({relationships?.length || 0})\n </h3>\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n gap: styles.spacing.sm\n }}>\n {relationships && relationships.length > 0 ? (\n relationships.map((rel, index) => (\n <div\n key={index}\n style={{\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm'),\n display: 'flex',\n alignItems: 'center',\n gap: styles.spacing.md\n }}\n >\n {renderRelationshipIcon(rel.Type)}\n <div style={{ flex: 1 }}>\n <div style={{\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.text\n }}>\n {rel.DisplayName || rel.RelatedEntity}\n </div>\n {rel.RelatedEntityJoinField && (\n <div style={{\n fontSize: styles.typography.fontSize.xs || styles.typography.fontSize.sm,\n color: styles.colors.textSecondary,\n fontFamily: 'monospace'\n }}>\n via {rel.RelatedEntityJoinField}\n </div>\n )}\n </div>\n </div>\n ))\n ) : (\n <div style={{\n padding: styles.spacing.lg,\n backgroundColor: styles.colors.surface,\n borderRadius: getBorderRadius('sm'),\n textAlign: 'center',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm\n }}>\n No relationships defined\n </div>\n )}\n </div>\n </div>\n </>\n ) : (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n color: styles.colors.textSecondary\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.lg,\n marginBottom: styles.spacing.md\n }}>\n No Entity Selected\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md\n }}>\n Select an entity from the list to view its details\n </div>\n </div>\n )}\n </div>\n \n {/* Footer with Open Record Button */}\n {entity && OpenRecordButton && (\n <div style={{\n padding: styles.spacing.lg,\n borderTop: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.surface\n }}>\n <OpenRecordButton\n entityName=\"Entities\"\n record={entity}\n buttonText=\"Open Entity Record\"\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n buttonStyle={{\n width: '100%',\n padding: styles.spacing.md,\n backgroundColor: styles.colors.primary,\n color: 'white',\n border: 'none',\n borderRadius: getBorderRadius('md'),\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n cursor: 'pointer',\n transition: 'background-color 0.2s'\n }}\n />\n </div>\n )}\n </div>\n </>\n );\n}",
1267
+ dependencies: [
1399
1268
  {
1400
- "name": "OpenRecordButton",
1401
- "location": "registry",
1402
- "namespace": "Generic/Navigation",
1403
- "version": "^1.0.0"
1404
- }
1269
+ name: 'OpenRecordButton',
1270
+ location: 'registry',
1271
+ namespace: 'Generic/Navigation',
1272
+ version: '^1.0.0',
1273
+ },
1405
1274
  ],
1406
- "libraries": []
1275
+ libraries: [],
1407
1276
  },
1408
1277
  {
1409
- "name": "EntityFilter",
1410
- "title": "Entity Filter Panel",
1411
- "description": "Collapsible filter panel for filtering entities by various criteria",
1412
- "type": "form",
1413
- "functionalRequirements": "## Entity Filter Requirements\n\n- Collapsible panel on the left side\n- Filter by schema name (dropdown)\n- Filter by base table (dropdown)\n- Search box for text search\n- Clear all filters button\n- Show active filter count\n- Smooth collapse/expand animation\n- Remember collapsed state",
1414
- "dataRequirements": {
1415
- "mode": "views",
1416
- "description": "Receives filter options derived from Entities metadata",
1417
- "entities": [
1278
+ name: 'EntityFilter',
1279
+ title: 'Entity Filter Panel',
1280
+ description: 'Collapsible filter panel for filtering entities by various criteria',
1281
+ type: 'form',
1282
+ functionalRequirements:
1283
+ '## Entity Filter Requirements\n\n- Collapsible panel on the left side\n- Filter by schema name (dropdown)\n- Filter by base table (dropdown)\n- Search box for text search\n- Clear all filters button\n- Show active filter count\n- Smooth collapse/expand animation\n- Remember collapsed state',
1284
+ dataRequirements: {
1285
+ mode: 'views',
1286
+ description: 'Receives filter options derived from Entities metadata',
1287
+ entities: [
1418
1288
  {
1419
- "name": "Entities",
1420
- "description": "Source of schema and table filter options",
1421
- "displayFields": [
1422
- "SchemaName",
1423
- "BaseTable"
1424
- ],
1425
- "filterFields": [],
1426
- "sortFields": [],
1427
- "fieldMetadata": [],
1428
- "permissionLevelNeeded": [
1429
- "read"
1430
- ],
1431
- "usageContext": "Extracts unique schema names and base tables for filter dropdowns"
1432
- }
1289
+ name: 'Entities',
1290
+ description: 'Source of schema and table filter options',
1291
+ displayFields: ['SchemaName', 'BaseTable'],
1292
+ filterFields: [],
1293
+ sortFields: [],
1294
+ fieldMetadata: [],
1295
+ permissionLevelNeeded: ['read'],
1296
+ usageContext: 'Extracts unique schema names and base tables for filter dropdowns',
1297
+ },
1433
1298
  ],
1434
- "queries": []
1299
+ queries: [],
1435
1300
  },
1436
- "technicalDesign": "## Technical Design\n\n### Props\n- filters: Current filter values\n- onFilterChange: Callback when filters change\n- schemas: Available schema options\n- tables: Available table options\n- isCollapsed: Whether panel is collapsed\n- onToggleCollapse: Callback to toggle collapse\n\n### Components\n- Collapse toggle button\n- Schema dropdown\n- Table dropdown\n- Search input\n- Clear filters button\n- Active filter badges",
1437
- "properties": [
1301
+ technicalDesign:
1302
+ '## Technical Design\n\n### Props\n- filters: Current filter values\n- onFilterChange: Callback when filters change\n- schemas: Available schema options\n- tables: Available table options\n- isCollapsed: Whether panel is collapsed\n- onToggleCollapse: Callback to toggle collapse\n\n### Components\n- Collapse toggle button\n- Schema dropdown\n- Table dropdown\n- Search input\n- Clear filters button\n- Active filter badges',
1303
+ properties: [
1438
1304
  {
1439
- "name": "filters",
1440
- "description": "Current filter values",
1441
- "type": "{schema?: string, table?: string, search?: string}",
1442
- "required": true
1305
+ name: 'filters',
1306
+ description: 'Current filter values',
1307
+ type: '{schema?: string, table?: string, search?: string}',
1308
+ required: true,
1443
1309
  },
1444
1310
  {
1445
- "name": "onFilterChange",
1446
- "description": "Callback when filters change",
1447
- "type": "(filters: {schema?: string, table?: string, search?: string}) => void",
1448
- "required": true
1311
+ name: 'onFilterChange',
1312
+ description: 'Callback when filters change',
1313
+ type: '(filters: {schema?: string, table?: string, search?: string}) => void',
1314
+ required: true,
1449
1315
  },
1450
1316
  {
1451
- "name": "schemas",
1452
- "description": "Available schema options",
1453
- "type": "Array<string>",
1454
- "required": true
1317
+ name: 'schemas',
1318
+ description: 'Available schema options',
1319
+ type: 'Array<string>',
1320
+ required: true,
1455
1321
  },
1456
1322
  {
1457
- "name": "tables",
1458
- "description": "Available table options",
1459
- "type": "Array<string>",
1460
- "required": true
1323
+ name: 'tables',
1324
+ description: 'Available table options',
1325
+ type: 'Array<string>',
1326
+ required: true,
1461
1327
  },
1462
1328
  {
1463
- "name": "isCollapsed",
1464
- "description": "Whether the panel is collapsed",
1465
- "type": "boolean",
1466
- "required": true
1329
+ name: 'isCollapsed',
1330
+ description: 'Whether the panel is collapsed',
1331
+ type: 'boolean',
1332
+ required: true,
1467
1333
  },
1468
1334
  {
1469
- "name": "onToggleCollapse",
1470
- "description": "Callback to toggle collapse state",
1471
- "type": "() => void",
1472
- "required": true
1473
- }
1335
+ name: 'onToggleCollapse',
1336
+ description: 'Callback to toggle collapse state',
1337
+ type: '() => void',
1338
+ required: true,
1339
+ },
1474
1340
  ],
1475
- "events": [
1341
+ events: [
1476
1342
  {
1477
- "name": "onFilterChange",
1478
- "description": "Fired when filter values change",
1479
- "parameters": [
1343
+ name: 'onFilterChange',
1344
+ description: 'Fired when filter values change',
1345
+ parameters: [
1480
1346
  {
1481
- "name": "filters",
1482
- "description": "Updated filter object",
1483
- "type": "object"
1484
- }
1485
- ]
1347
+ name: 'filters',
1348
+ description: 'Updated filter object',
1349
+ type: 'object',
1350
+ },
1351
+ ],
1486
1352
  },
1487
1353
  {
1488
- "name": "onToggleCollapse",
1489
- "description": "Fired when collapse state should toggle",
1490
- "parameters": []
1491
- }
1354
+ name: 'onToggleCollapse',
1355
+ description: 'Fired when collapse state should toggle',
1356
+ parameters: [],
1357
+ },
1492
1358
  ],
1493
- "exampleUsage": "<EntityFilter\n filters={filters}\n onFilterChange={handleFilterChange}\n schemas={uniqueSchemas}\n tables={uniqueTables}\n isCollapsed={filterPanelCollapsed}\n onToggleCollapse={handleToggleFilter}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n/>",
1494
- "code": "function EntityFilter({ \n filters, \n onFilterChange, \n schemas, \n tables, \n isCollapsed, \n onToggleCollapse,\n utilities, \n styles, \n components, \n callbacks, \n savedUserSettings, \n onSaveUserSettings \n}) {\n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n \n // Calculate active filter count\n const activeFilterCount = Object.values(filters || {}).filter(Boolean).length;\n \n // Handle schema filter change\n const handleSchemaChange = useCallback((e) => {\n const newFilters = {\n ...filters,\n schema: e.target.value || undefined\n };\n onFilterChange?.(newFilters);\n }, [filters, onFilterChange]);\n \n // Handle table filter change\n const handleTableChange = useCallback((e) => {\n const newFilters = {\n ...filters,\n table: e.target.value || undefined\n };\n onFilterChange?.(newFilters);\n }, [filters, onFilterChange]);\n \n // Handle clear all filters\n const handleClearFilters = useCallback(() => {\n onFilterChange?.({});\n }, [onFilterChange]);\n \n // Handle toggle collapse\n const handleToggle = useCallback(() => {\n onToggleCollapse?.();\n }, [onToggleCollapse]);\n \n return (\n <div style={{\n width: isCollapsed ? '48px' : '280px',\n minWidth: isCollapsed ? '48px' : '280px',\n backgroundColor: styles.colors.surface,\n borderRight: `1px solid ${styles.colors.border}`,\n transition: 'width 0.3s ease-out',\n display: 'flex',\n flexDirection: 'column',\n position: 'relative',\n overflow: 'hidden'\n }}>\n {/* Toggle Button */}\n <button\n onClick={handleToggle}\n style={{\n position: 'absolute',\n top: styles.spacing.md,\n right: styles.spacing.md,\n width: '32px',\n height: '32px',\n borderRadius: getBorderRadius('sm'),\n border: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.background,\n color: styles.colors.text,\n fontSize: styles.typography.fontSize.md,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 1,\n transition: 'all 0.2s'\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.background;\n }}\n >\n {isCollapsed ? '→' : '←'}\n </button>\n \n {/* Filter Icon when collapsed */}\n {isCollapsed && (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n flex: 1,\n opacity: 1,\n transition: 'opacity 0.3s'\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xl,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.sm\n }}>\n 🔍\n </div>\n {activeFilterCount > 0 && (\n <div style={{\n width: '24px',\n height: '24px',\n borderRadius: '50%',\n backgroundColor: styles.colors.primary,\n color: 'white',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: styles.typography.fontSize.xs,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n {activeFilterCount}\n </div>\n )}\n </div>\n )}\n \n {/* Filter Content */}\n <div style={{\n padding: styles.spacing.lg,\n opacity: isCollapsed ? 0 : 1,\n transition: 'opacity 0.3s',\n pointerEvents: isCollapsed ? 'none' : 'auto',\n flex: 1,\n display: 'flex',\n flexDirection: 'column'\n }}>\n {/* Header */}\n <div style={{\n marginBottom: styles.spacing.xl,\n paddingRight: '40px'\n }}>\n <h2 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text,\n marginBottom: styles.spacing.xs\n }}>\n Filters\n </h2>\n {activeFilterCount > 0 && (\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n {activeFilterCount} active filter{activeFilterCount !== 1 ? 's' : ''}\n </div>\n )}\n </div>\n \n {/* Filter Controls */}\n <div style={{\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n gap: styles.spacing.lg\n }}>\n {/* Schema Filter */}\n <div>\n <label style={{\n display: 'block',\n marginBottom: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Schema\n </label>\n <select\n value={filters?.schema || ''}\n onChange={handleSchemaChange}\n style={{\n width: '100%',\n padding: styles.spacing.sm,\n fontSize: styles.typography.fontSize.md,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n backgroundColor: styles.colors.background,\n color: styles.colors.text,\n cursor: 'pointer'\n }}\n >\n <option value=\"\">All Schemas</option>\n {schemas.map((schema) => (\n <option key={schema} value={schema}>\n {schema}\n </option>\n ))}\n </select>\n </div>\n \n {/* Table Filter */}\n <div>\n <label style={{\n display: 'block',\n marginBottom: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Base Table\n </label>\n <select\n value={filters?.table || ''}\n onChange={handleTableChange}\n style={{\n width: '100%',\n padding: styles.spacing.sm,\n fontSize: styles.typography.fontSize.md,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n backgroundColor: styles.colors.background,\n color: styles.colors.text,\n cursor: 'pointer'\n }}\n >\n <option value=\"\">All Tables</option>\n {tables.map((table) => (\n <option key={table} value={table}>\n {table}\n </option>\n ))}\n </select>\n </div>\n \n {/* Active Filters Display */}\n {activeFilterCount > 0 && (\n <div>\n <div style={{\n marginBottom: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Active Filters\n </div>\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n gap: styles.spacing.xs\n }}>\n {filters?.schema && (\n <div style={{\n padding: styles.spacing.sm,\n backgroundColor: styles.colors.primary + '15',\n borderRadius: getBorderRadius('sm'),\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.text\n }}>\n <span style={{\n color: styles.colors.textSecondary,\n marginRight: styles.spacing.xs\n }}>\n Schema:\n </span>\n <strong>{filters.schema}</strong>\n </div>\n <button\n onClick={() => handleSchemaChange({ target: { value: '' } })}\n style={{\n width: '20px',\n height: '20px',\n borderRadius: '50%',\n border: 'none',\n backgroundColor: 'transparent',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 0\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent';\n }}\n >\n ✕\n </button>\n </div>\n )}\n {filters?.table && (\n <div style={{\n padding: styles.spacing.sm,\n backgroundColor: styles.colors.primary + '15',\n borderRadius: getBorderRadius('sm'),\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.text\n }}>\n <span style={{\n color: styles.colors.textSecondary,\n marginRight: styles.spacing.xs\n }}>\n Table:\n </span>\n <strong>{filters.table}</strong>\n </div>\n <button\n onClick={() => handleTableChange({ target: { value: '' } })}\n style={{\n width: '20px',\n height: '20px',\n borderRadius: '50%',\n border: 'none',\n backgroundColor: 'transparent',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 0\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent';\n }}\n >\n ✕\n </button>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n \n {/* Clear All Button */}\n {activeFilterCount > 0 && (\n <div style={{\n marginTop: styles.spacing.xl,\n paddingTop: styles.spacing.lg,\n borderTop: `1px solid ${styles.colors.borderLight || styles.colors.border}`\n }}>\n <button\n onClick={handleClearFilters}\n style={{\n width: '100%',\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n color: styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('md'),\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n cursor: 'pointer',\n transition: 'background-color 0.2s'\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.error + '15';\n e.currentTarget.style.color = styles.colors.error;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surface;\n e.currentTarget.style.color = styles.colors.text;\n }}\n >\n Clear All Filters\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}",
1495
- "dependencies": [],
1496
- "libraries": []
1359
+ exampleUsage:
1360
+ '<EntityFilter\n filters={filters}\n onFilterChange={handleFilterChange}\n schemas={uniqueSchemas}\n tables={uniqueTables}\n isCollapsed={filterPanelCollapsed}\n onToggleCollapse={handleToggleFilter}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n/>',
1361
+ code: "function EntityFilter({ \n filters, \n onFilterChange, \n schemas, \n tables, \n isCollapsed, \n onToggleCollapse,\n utilities, \n styles, \n components, \n callbacks, \n savedUserSettings, \n onSaveUserSettings \n}) {\n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n \n // Calculate active filter count\n const activeFilterCount = Object.values(filters || {}).filter(Boolean).length;\n \n // Handle schema filter change\n const handleSchemaChange = useCallback((e) => {\n const newFilters = {\n ...filters,\n schema: e.target.value || undefined\n };\n onFilterChange?.(newFilters);\n }, [filters, onFilterChange]);\n \n // Handle table filter change\n const handleTableChange = useCallback((e) => {\n const newFilters = {\n ...filters,\n table: e.target.value || undefined\n };\n onFilterChange?.(newFilters);\n }, [filters, onFilterChange]);\n \n // Handle clear all filters\n const handleClearFilters = useCallback(() => {\n onFilterChange?.({});\n }, [onFilterChange]);\n \n // Handle toggle collapse\n const handleToggle = useCallback(() => {\n onToggleCollapse?.();\n }, [onToggleCollapse]);\n \n return (\n <div style={{\n width: isCollapsed ? '48px' : '280px',\n minWidth: isCollapsed ? '48px' : '280px',\n backgroundColor: styles.colors.surface,\n borderRight: `1px solid ${styles.colors.border}`,\n transition: 'width 0.3s ease-out',\n display: 'flex',\n flexDirection: 'column',\n position: 'relative',\n overflow: 'hidden'\n }}>\n {/* Toggle Button */}\n <button\n onClick={handleToggle}\n style={{\n position: 'absolute',\n top: styles.spacing.md,\n right: styles.spacing.md,\n width: '32px',\n height: '32px',\n borderRadius: getBorderRadius('sm'),\n border: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.background,\n color: styles.colors.text,\n fontSize: styles.typography.fontSize.md,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 1,\n transition: 'all 0.2s'\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.background;\n }}\n >\n {isCollapsed ? '→' : '←'}\n </button>\n \n {/* Filter Icon when collapsed */}\n {isCollapsed && (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n flex: 1,\n opacity: 1,\n transition: 'opacity 0.3s'\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xl,\n color: styles.colors.textSecondary,\n marginBottom: styles.spacing.sm\n }}>\n 🔍\n </div>\n {activeFilterCount > 0 && (\n <div style={{\n width: '24px',\n height: '24px',\n borderRadius: '50%',\n backgroundColor: styles.colors.primary,\n color: 'white',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: styles.typography.fontSize.xs,\n fontWeight: styles.typography.fontWeight?.bold || '700'\n }}>\n {activeFilterCount}\n </div>\n )}\n </div>\n )}\n \n {/* Filter Content */}\n <div style={{\n padding: styles.spacing.lg,\n opacity: isCollapsed ? 0 : 1,\n transition: 'opacity 0.3s',\n pointerEvents: isCollapsed ? 'none' : 'auto',\n flex: 1,\n display: 'flex',\n flexDirection: 'column'\n }}>\n {/* Header */}\n <div style={{\n marginBottom: styles.spacing.xl,\n paddingRight: '40px'\n }}>\n <h2 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.lg,\n fontWeight: styles.typography.fontWeight?.semibold || '600',\n color: styles.colors.text,\n marginBottom: styles.spacing.xs\n }}>\n Filters\n </h2>\n {activeFilterCount > 0 && (\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.textSecondary\n }}>\n {activeFilterCount} active filter{activeFilterCount !== 1 ? 's' : ''}\n </div>\n )}\n </div>\n \n {/* Filter Controls */}\n <div style={{\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n gap: styles.spacing.lg\n }}>\n {/* Schema Filter */}\n <div>\n <label style={{\n display: 'block',\n marginBottom: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Schema\n </label>\n <select\n value={filters?.schema || ''}\n onChange={handleSchemaChange}\n style={{\n width: '100%',\n padding: styles.spacing.sm,\n fontSize: styles.typography.fontSize.md,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n backgroundColor: styles.colors.background,\n color: styles.colors.text,\n cursor: 'pointer'\n }}\n >\n <option value=\"\">All Schemas</option>\n {schemas.map((schema) => (\n <option key={schema} value={schema}>\n {schema}\n </option>\n ))}\n </select>\n </div>\n \n {/* Table Filter */}\n <div>\n <label style={{\n display: 'block',\n marginBottom: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Base Table\n </label>\n <select\n value={filters?.table || ''}\n onChange={handleTableChange}\n style={{\n width: '100%',\n padding: styles.spacing.sm,\n fontSize: styles.typography.fontSize.md,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n backgroundColor: styles.colors.background,\n color: styles.colors.text,\n cursor: 'pointer'\n }}\n >\n <option value=\"\">All Tables</option>\n {tables.map((table) => (\n <option key={table} value={table}>\n {table}\n </option>\n ))}\n </select>\n </div>\n \n {/* Active Filters Display */}\n {activeFilterCount > 0 && (\n <div>\n <div style={{\n marginBottom: styles.spacing.sm,\n fontSize: styles.typography.fontSize.sm,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n color: styles.colors.textSecondary\n }}>\n Active Filters\n </div>\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n gap: styles.spacing.xs\n }}>\n {filters?.schema && (\n <div style={{\n padding: styles.spacing.sm,\n backgroundColor: styles.colors.primary + '15',\n borderRadius: getBorderRadius('sm'),\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.text\n }}>\n <span style={{\n color: styles.colors.textSecondary,\n marginRight: styles.spacing.xs\n }}>\n Schema:\n </span>\n <strong>{filters.schema}</strong>\n </div>\n <button\n onClick={() => handleSchemaChange({ target: { value: '' } })}\n style={{\n width: '20px',\n height: '20px',\n borderRadius: '50%',\n border: 'none',\n backgroundColor: 'transparent',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 0\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent';\n }}\n >\n ✕\n </button>\n </div>\n )}\n {filters?.table && (\n <div style={{\n padding: styles.spacing.sm,\n backgroundColor: styles.colors.primary + '15',\n borderRadius: getBorderRadius('sm'),\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.sm,\n color: styles.colors.text\n }}>\n <span style={{\n color: styles.colors.textSecondary,\n marginRight: styles.spacing.xs\n }}>\n Table:\n </span>\n <strong>{filters.table}</strong>\n </div>\n <button\n onClick={() => handleTableChange({ target: { value: '' } })}\n style={{\n width: '20px',\n height: '20px',\n borderRadius: '50%',\n border: 'none',\n backgroundColor: 'transparent',\n color: styles.colors.textSecondary,\n fontSize: styles.typography.fontSize.sm,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 0\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surfaceHover || styles.colors.surface;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent';\n }}\n >\n ✕\n </button>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n \n {/* Clear All Button */}\n {activeFilterCount > 0 && (\n <div style={{\n marginTop: styles.spacing.xl,\n paddingTop: styles.spacing.lg,\n borderTop: `1px solid ${styles.colors.borderLight || styles.colors.border}`\n }}>\n <button\n onClick={handleClearFilters}\n style={{\n width: '100%',\n padding: styles.spacing.md,\n backgroundColor: styles.colors.surface,\n color: styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('md'),\n fontSize: styles.typography.fontSize.md,\n fontWeight: styles.typography.fontWeight?.medium || '500',\n cursor: 'pointer',\n transition: 'background-color 0.2s'\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.error + '15';\n e.currentTarget.style.color = styles.colors.error;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = styles.colors.surface;\n e.currentTarget.style.color = styles.colors.text;\n }}\n >\n Clear All Filters\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}",
1362
+ dependencies: [],
1363
+ libraries: [],
1497
1364
  },
1498
1365
  {
1499
- "name": "OpenRecordButton",
1500
- "location": "registry",
1501
- "namespace": "Generic/Navigation",
1502
- "version": "^1.0.0"
1503
- }
1366
+ name: 'OpenRecordButton',
1367
+ location: 'registry',
1368
+ namespace: 'Generic/Navigation',
1369
+ version: '^1.0.0',
1370
+ },
1504
1371
  ],
1505
- "libraries": [],
1506
- "code": "function EntityBrowser({ utilities, styles, components, callbacks, savedUserSettings, onSaveUserSettings }) {\n // Extract child components\n const { EntityList, EntityDetails, EntityFilter } = components;\n \n // Initialize state from saved settings where appropriate\n const [selectedEntityId, setSelectedEntityId] = useState(savedUserSettings?.selectedEntityId);\n const [viewMode, setViewMode] = useState(savedUserSettings?.viewMode || 'grid');\n const [filters, setFilters] = useState(savedUserSettings?.filters || {});\n const [sortBy, setSortBy] = useState(savedUserSettings?.sortBy || 'Name');\n const [sortDirection, setSortDirection] = useState(savedUserSettings?.sortDirection || 'asc');\n const [filterPanelCollapsed, setFilterPanelCollapsed] = useState(savedUserSettings?.filterPanelCollapsed || false);\n \n // Runtime UI state (not persisted)\n const [entities, setEntities] = useState([]);\n const [entityFields, setEntityFields] = useState([]);\n const [entityRelationships, setEntityRelationships] = useState([]);\n const [loading, setLoading] = useState(true);\n const [detailsPanelOpen, setDetailsPanelOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [uniqueSchemas, setUniqueSchemas] = useState([]);\n const [uniqueTables, setUniqueTables] = useState([]);\n \n // Load entities on mount and when filters/sort change\n useEffect(() => {\n const loadEntities = async () => {\n setLoading(true);\n try {\n // Build filter string\n let filterParts = [];\n if (filters.schema) {\n filterParts.push(`SchemaName = '${filters.schema}'`);\n }\n if (filters.table) {\n filterParts.push(`BaseTable = '${filters.table}'`);\n }\n if (searchQuery) {\n filterParts.push(`(Name LIKE '%${searchQuery}%' OR DisplayName LIKE '%${searchQuery}%' OR Description LIKE '%${searchQuery}%')`);\n }\n \n const result = await utilities.rv.RunView({\n EntityName: 'Entities',\n Fields: ['ID', 'Name', 'DisplayName', 'NameSuffix', 'Description', 'SchemaName', 'BaseTable', 'BaseView'],\n OrderBy: `${sortBy} ${sortDirection.toUpperCase()}`,\n ExtraFilter: filterParts.length > 0 ? filterParts.join(' AND ') : undefined\n });\n \n if (result?.Success && result?.Results) {\n setEntities(result.Results);\n \n // Extract unique schemas and tables for filter dropdowns\n const schemas = [...new Set(result.Results.map(e => e.SchemaName).filter(Boolean))];\n const tables = [...new Set(result.Results.map(e => e.BaseTable).filter(Boolean))];\n setUniqueSchemas(schemas);\n setUniqueTables(tables);\n } else {\n console.error('Failed to load entities:', result?.ErrorMessage);\n setEntities([]);\n }\n } catch (error) {\n console.error('Error loading entities:', error);\n setEntities([]);\n } finally {\n setLoading(false);\n }\n };\n \n loadEntities();\n }, [filters, sortBy, sortDirection, searchQuery, utilities.rv]);\n \n // Load entity details when selection changes\n useEffect(() => {\n const loadEntityDetails = async () => {\n if (!selectedEntityId) {\n setEntityFields([]);\n setEntityRelationships([]);\n return;\n }\n \n try {\n // Load fields\n const fieldsResult = await utilities.rv.RunView({\n EntityName: 'Entity Fields',\n Fields: ['Name', 'DisplayName', 'Type', 'Length', 'AllowsNull', 'IsPrimaryKey', 'IsUnique', 'Sequence'],\n OrderBy: 'Sequence ASC, Name ASC',\n ExtraFilter: `EntityID = '${selectedEntityId}'`\n });\n \n if (fieldsResult?.Success && fieldsResult?.Results) {\n setEntityFields(fieldsResult.Results);\n } else {\n setEntityFields([]);\n }\n \n // Load relationships\n const relationshipsResult = await utilities.rv.RunView({\n EntityName: 'Entity Relationships',\n Fields: ['RelatedEntity', 'Type', 'DisplayName', 'RelatedEntityJoinField', 'Sequence'],\n OrderBy: 'Sequence ASC, RelatedEntity ASC',\n ExtraFilter: `EntityID = '${selectedEntityId}'`\n });\n \n if (relationshipsResult?.Success && relationshipsResult?.Results) {\n setEntityRelationships(relationshipsResult.Results);\n } else {\n setEntityRelationships([]);\n }\n } catch (error) {\n console.error('Error loading entity details:', error);\n setEntityFields([]);\n setEntityRelationships([]);\n }\n };\n \n loadEntityDetails();\n }, [selectedEntityId, utilities.rv]);\n \n // Handle entity selection\n const handleSelectEntity = useCallback((entityId) => {\n setSelectedEntityId(entityId);\n setDetailsPanelOpen(true);\n \n // Save user preference\n onSaveUserSettings?.({\n ...savedUserSettings,\n selectedEntityId: entityId\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle view mode change\n const handleViewModeChange = useCallback((mode) => {\n setViewMode(mode);\n \n // Save preference\n onSaveUserSettings?.({\n ...savedUserSettings,\n viewMode: mode\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle filter changes\n const handleFilterChange = useCallback((newFilters) => {\n setFilters(newFilters);\n \n // Save filter preferences\n onSaveUserSettings?.({\n ...savedUserSettings,\n filters: newFilters\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle sort changes\n const handleSortChange = useCallback((newSortBy, newSortDirection) => {\n setSortBy(newSortBy);\n setSortDirection(newSortDirection);\n \n // Save sort preferences\n onSaveUserSettings?.({\n ...savedUserSettings,\n sortBy: newSortBy,\n sortDirection: newSortDirection\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle filter panel toggle\n const handleToggleFilter = useCallback(() => {\n const newCollapsed = !filterPanelCollapsed;\n setFilterPanelCollapsed(newCollapsed);\n \n // Save collapsed state\n onSaveUserSettings?.({\n ...savedUserSettings,\n filterPanelCollapsed: newCollapsed\n });\n }, [filterPanelCollapsed, savedUserSettings, onSaveUserSettings]);\n \n // Handle opening entity record (kept for backward compatibility with details panel)\n const handleOpenRecord = useCallback((entityName) => {\n console.log('Root handleOpenRecord called with entityName:', entityName);\n console.log('Callbacks object:', callbacks);\n if (callbacks?.OpenEntityRecord && entityName) {\n console.log('Calling OpenEntityRecord callback with:', 'Entities', entityName);\n // Open the Entities entity record for the selected entity\n callbacks.OpenEntityRecord('Entities', [{ FieldName: 'Name', Value: entityName }]);\n } else {\n console.error('OpenEntityRecord callback not available or entityName missing');\n }\n }, [callbacks]);\n \n // Handle closing details panel\n const handleCloseDetails = useCallback(() => {\n setDetailsPanelOpen(false);\n }, []);\n \n // Handle search\n const handleSearch = useCallback((query) => {\n setSearchQuery(query);\n }, []);\n \n // Get selected entity object\n const selectedEntity = entities.find(e => e.ID === selectedEntityId);\n \n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n \n // Loading state\n if (loading && entities.length === 0) {\n return (\n <div style={{\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n height: '100vh',\n fontSize: styles.typography.fontSize.lg,\n color: styles.colors.textSecondary\n }}>\n Loading entities...\n </div>\n );\n }\n \n return (\n <div style={{\n display: 'flex',\n height: '100vh',\n backgroundColor: styles.colors.background,\n overflow: 'hidden'\n }}>\n {/* Filter Panel */}\n {EntityFilter && (\n <EntityFilter\n filters={filters}\n onFilterChange={handleFilterChange}\n schemas={uniqueSchemas}\n tables={uniqueTables}\n isCollapsed={filterPanelCollapsed}\n onToggleCollapse={handleToggleFilter}\n savedUserSettings={savedUserSettings?.filterPanel}\n onSaveUserSettings={(settings) => onSaveUserSettings?.({\n ...savedUserSettings,\n filterPanel: settings\n })}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n />\n )}\n \n {/* Main Content Area */}\n <div style={{\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden'\n }}>\n {/* Header */}\n <div style={{\n padding: styles.spacing.lg,\n borderBottom: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.surface\n }}>\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: styles.spacing.md\n }}>\n <h1 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.xxl || styles.typography.fontSize.xl,\n fontWeight: styles.typography.fontWeight?.bold || '700',\n color: styles.colors.text\n }}>\n Entity Browser\n </h1>\n \n {/* View Mode Toggle */}\n <div style={{\n display: 'flex',\n gap: styles.spacing.sm,\n alignItems: 'center'\n }}>\n <span style={{\n fontSize: styles.typography.fontSize.md,\n color: styles.colors.textSecondary\n }}>\n View:\n </span>\n <button\n onClick={() => handleViewModeChange('grid')}\n style={{\n padding: `${styles.spacing.sm} ${styles.spacing.md}`,\n backgroundColor: viewMode === 'grid' ? styles.colors.primary : styles.colors.background,\n color: viewMode === 'grid' ? 'white' : styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n cursor: 'pointer',\n fontSize: styles.typography.fontSize.md\n }}\n >\n Grid\n </button>\n <button\n onClick={() => handleViewModeChange('card')}\n style={{\n padding: `${styles.spacing.sm} ${styles.spacing.md}`,\n backgroundColor: viewMode === 'card' ? styles.colors.primary : styles.colors.background,\n color: viewMode === 'card' ? 'white' : styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n cursor: 'pointer',\n fontSize: styles.typography.fontSize.md\n }}\n >\n Cards\n </button>\n </div>\n </div>\n \n {/* Search Bar */}\n <div style={{\n display: 'flex',\n gap: styles.spacing.md\n }}>\n <input\n type=\"text\"\n placeholder=\"Search entities...\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n style={{\n flex: 1,\n padding: styles.spacing.md,\n fontSize: styles.typography.fontSize.md,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n backgroundColor: styles.colors.background\n }}\n />\n {searchQuery && (\n <button\n onClick={() => handleSearch('')}\n style={{\n padding: `${styles.spacing.sm} ${styles.spacing.md}`,\n backgroundColor: styles.colors.surfaceHover || styles.colors.surface,\n color: styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n cursor: 'pointer',\n fontSize: styles.typography.fontSize.md\n }}\n >\n Clear\n </button>\n )}\n </div>\n </div>\n \n {/* Entity List */}\n <div style={{\n flex: 1,\n overflow: 'auto',\n padding: styles.spacing.lg\n }}>\n {EntityList && (\n <EntityList\n entities={entities}\n viewMode={viewMode}\n selectedEntityId={selectedEntityId}\n onSelectEntity={handleSelectEntity}\n sortBy={sortBy}\n sortDirection={sortDirection}\n onSortChange={handleSortChange}\n savedUserSettings={savedUserSettings?.entityList}\n onSaveUserSettings={(settings) => onSaveUserSettings?.({\n ...savedUserSettings,\n entityList: settings\n })}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n />\n )}\n \n {/* Empty State */}\n {entities.length === 0 && !loading && (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: styles.spacing.xxl || styles.spacing.xl,\n color: styles.colors.textSecondary\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xl,\n marginBottom: styles.spacing.md\n }}>\n No entities found\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md\n }}>\n {searchQuery || Object.keys(filters).length > 0\n ? 'Try adjusting your filters or search query'\n : 'No entities are available'}\n </div>\n </div>\n )}\n </div>\n </div>\n \n {/* Details Panel */}\n {EntityDetails && (\n <EntityDetails\n entity={selectedEntity}\n fields={entityFields}\n relationships={entityRelationships}\n isOpen={detailsPanelOpen}\n onClose={handleCloseDetails}\n onOpenRecord={() => handleOpenRecord(selectedEntity?.Name)}\n savedUserSettings={savedUserSettings?.detailsPanel}\n onSaveUserSettings={(settings) => onSaveUserSettings?.({\n ...savedUserSettings,\n detailsPanel: settings\n })}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n />\n )}\n </div>\n );\n}"
1507
- }
1372
+ libraries: [],
1373
+ code: "function EntityBrowser({ utilities, styles, components, callbacks, savedUserSettings, onSaveUserSettings }) {\n // Extract child components\n const { EntityList, EntityDetails, EntityFilter } = components;\n \n // Initialize state from saved settings where appropriate\n const [selectedEntityId, setSelectedEntityId] = useState(savedUserSettings?.selectedEntityId);\n const [viewMode, setViewMode] = useState(savedUserSettings?.viewMode || 'grid');\n const [filters, setFilters] = useState(savedUserSettings?.filters || {});\n const [sortBy, setSortBy] = useState(savedUserSettings?.sortBy || 'Name');\n const [sortDirection, setSortDirection] = useState(savedUserSettings?.sortDirection || 'asc');\n const [filterPanelCollapsed, setFilterPanelCollapsed] = useState(savedUserSettings?.filterPanelCollapsed || false);\n \n // Runtime UI state (not persisted)\n const [entities, setEntities] = useState([]);\n const [entityFields, setEntityFields] = useState([]);\n const [entityRelationships, setEntityRelationships] = useState([]);\n const [loading, setLoading] = useState(true);\n const [detailsPanelOpen, setDetailsPanelOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [uniqueSchemas, setUniqueSchemas] = useState([]);\n const [uniqueTables, setUniqueTables] = useState([]);\n \n // Load entities on mount and when filters/sort change\n useEffect(() => {\n const loadEntities = async () => {\n setLoading(true);\n try {\n // Build filter string\n let filterParts = [];\n if (filters.schema) {\n filterParts.push(`SchemaName = '${filters.schema}'`);\n }\n if (filters.table) {\n filterParts.push(`BaseTable = '${filters.table}'`);\n }\n if (searchQuery) {\n filterParts.push(`(Name LIKE '%${searchQuery}%' OR DisplayName LIKE '%${searchQuery}%' OR Description LIKE '%${searchQuery}%')`);\n }\n \n const result = await utilities.rv.RunView({\n EntityName: 'Entities',\n Fields: ['ID', 'Name', 'DisplayName', 'NameSuffix', 'Description', 'SchemaName', 'BaseTable', 'BaseView'],\n OrderBy: `${sortBy} ${sortDirection.toUpperCase()}`,\n ExtraFilter: filterParts.length > 0 ? filterParts.join(' AND ') : undefined\n });\n \n if (result?.Success && result?.Results) {\n setEntities(result.Results);\n \n // Extract unique schemas and tables for filter dropdowns\n const schemas = [...new Set(result.Results.map(e => e.SchemaName).filter(Boolean))];\n const tables = [...new Set(result.Results.map(e => e.BaseTable).filter(Boolean))];\n setUniqueSchemas(schemas);\n setUniqueTables(tables);\n } else {\n console.error('Failed to load entities:', result?.ErrorMessage);\n setEntities([]);\n }\n } catch (error) {\n console.error('Error loading entities:', error);\n setEntities([]);\n } finally {\n setLoading(false);\n }\n };\n \n loadEntities();\n }, [filters, sortBy, sortDirection, searchQuery, utilities.rv]);\n \n // Load entity details when selection changes\n useEffect(() => {\n const loadEntityDetails = async () => {\n if (!selectedEntityId) {\n setEntityFields([]);\n setEntityRelationships([]);\n return;\n }\n \n try {\n // Load fields\n const fieldsResult = await utilities.rv.RunView({\n EntityName: 'Entity Fields',\n Fields: ['Name', 'DisplayName', 'Type', 'Length', 'AllowsNull', 'IsPrimaryKey', 'IsUnique', 'Sequence'],\n OrderBy: 'Sequence ASC, Name ASC',\n ExtraFilter: `EntityID = '${selectedEntityId}'`\n });\n \n if (fieldsResult?.Success && fieldsResult?.Results) {\n setEntityFields(fieldsResult.Results);\n } else {\n setEntityFields([]);\n }\n \n // Load relationships\n const relationshipsResult = await utilities.rv.RunView({\n EntityName: 'Entity Relationships',\n Fields: ['RelatedEntity', 'Type', 'DisplayName', 'RelatedEntityJoinField', 'Sequence'],\n OrderBy: 'Sequence ASC, RelatedEntity ASC',\n ExtraFilter: `EntityID = '${selectedEntityId}'`\n });\n \n if (relationshipsResult?.Success && relationshipsResult?.Results) {\n setEntityRelationships(relationshipsResult.Results);\n } else {\n setEntityRelationships([]);\n }\n } catch (error) {\n console.error('Error loading entity details:', error);\n setEntityFields([]);\n setEntityRelationships([]);\n }\n };\n \n loadEntityDetails();\n }, [selectedEntityId, utilities.rv]);\n \n // Handle entity selection\n const handleSelectEntity = useCallback((entityId) => {\n setSelectedEntityId(entityId);\n setDetailsPanelOpen(true);\n \n // Save user preference\n onSaveUserSettings?.({\n ...savedUserSettings,\n selectedEntityId: entityId\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle view mode change\n const handleViewModeChange = useCallback((mode) => {\n setViewMode(mode);\n \n // Save preference\n onSaveUserSettings?.({\n ...savedUserSettings,\n viewMode: mode\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle filter changes\n const handleFilterChange = useCallback((newFilters) => {\n setFilters(newFilters);\n \n // Save filter preferences\n onSaveUserSettings?.({\n ...savedUserSettings,\n filters: newFilters\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle sort changes\n const handleSortChange = useCallback((newSortBy, newSortDirection) => {\n setSortBy(newSortBy);\n setSortDirection(newSortDirection);\n \n // Save sort preferences\n onSaveUserSettings?.({\n ...savedUserSettings,\n sortBy: newSortBy,\n sortDirection: newSortDirection\n });\n }, [savedUserSettings, onSaveUserSettings]);\n \n // Handle filter panel toggle\n const handleToggleFilter = useCallback(() => {\n const newCollapsed = !filterPanelCollapsed;\n setFilterPanelCollapsed(newCollapsed);\n \n // Save collapsed state\n onSaveUserSettings?.({\n ...savedUserSettings,\n filterPanelCollapsed: newCollapsed\n });\n }, [filterPanelCollapsed, savedUserSettings, onSaveUserSettings]);\n \n // Handle opening entity record (kept for backward compatibility with details panel)\n const handleOpenRecord = useCallback((entityName) => {\n console.log('Root handleOpenRecord called with entityName:', entityName);\n console.log('Callbacks object:', callbacks);\n if (callbacks?.OpenEntityRecord && entityName) {\n console.log('Calling OpenEntityRecord callback with:', 'Entities', entityName);\n // Open the Entities entity record for the selected entity\n callbacks.OpenEntityRecord('Entities', [{ FieldName: 'Name', Value: entityName }]);\n } else {\n console.error('OpenEntityRecord callback not available or entityName missing');\n }\n }, [callbacks]);\n \n // Handle closing details panel\n const handleCloseDetails = useCallback(() => {\n setDetailsPanelOpen(false);\n }, []);\n \n // Handle search\n const handleSearch = useCallback((query) => {\n setSearchQuery(query);\n }, []);\n \n // Get selected entity object\n const selectedEntity = entities.find(e => e.ID === selectedEntityId);\n \n // Helper function to get border radius value\n const getBorderRadius = (size) => {\n return typeof styles.borders.radius === 'object' ? styles.borders.radius[size] : styles.borders.radius;\n };\n \n // Loading state\n if (loading && entities.length === 0) {\n return (\n <div style={{\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n height: '100vh',\n fontSize: styles.typography.fontSize.lg,\n color: styles.colors.textSecondary\n }}>\n Loading entities...\n </div>\n );\n }\n \n return (\n <div style={{\n display: 'flex',\n height: '100vh',\n backgroundColor: styles.colors.background,\n overflow: 'hidden'\n }}>\n {/* Filter Panel */}\n {EntityFilter && (\n <EntityFilter\n filters={filters}\n onFilterChange={handleFilterChange}\n schemas={uniqueSchemas}\n tables={uniqueTables}\n isCollapsed={filterPanelCollapsed}\n onToggleCollapse={handleToggleFilter}\n savedUserSettings={savedUserSettings?.filterPanel}\n onSaveUserSettings={(settings) => onSaveUserSettings?.({\n ...savedUserSettings,\n filterPanel: settings\n })}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n />\n )}\n \n {/* Main Content Area */}\n <div style={{\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden'\n }}>\n {/* Header */}\n <div style={{\n padding: styles.spacing.lg,\n borderBottom: `1px solid ${styles.colors.border}`,\n backgroundColor: styles.colors.surface\n }}>\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: styles.spacing.md\n }}>\n <h1 style={{\n margin: 0,\n fontSize: styles.typography.fontSize.xxl || styles.typography.fontSize.xl,\n fontWeight: styles.typography.fontWeight?.bold || '700',\n color: styles.colors.text\n }}>\n Entity Browser\n </h1>\n \n {/* View Mode Toggle */}\n <div style={{\n display: 'flex',\n gap: styles.spacing.sm,\n alignItems: 'center'\n }}>\n <span style={{\n fontSize: styles.typography.fontSize.md,\n color: styles.colors.textSecondary\n }}>\n View:\n </span>\n <button\n onClick={() => handleViewModeChange('grid')}\n style={{\n padding: `${styles.spacing.sm} ${styles.spacing.md}`,\n backgroundColor: viewMode === 'grid' ? styles.colors.primary : styles.colors.background,\n color: viewMode === 'grid' ? 'white' : styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n cursor: 'pointer',\n fontSize: styles.typography.fontSize.md\n }}\n >\n Grid\n </button>\n <button\n onClick={() => handleViewModeChange('card')}\n style={{\n padding: `${styles.spacing.sm} ${styles.spacing.md}`,\n backgroundColor: viewMode === 'card' ? styles.colors.primary : styles.colors.background,\n color: viewMode === 'card' ? 'white' : styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n cursor: 'pointer',\n fontSize: styles.typography.fontSize.md\n }}\n >\n Cards\n </button>\n </div>\n </div>\n \n {/* Search Bar */}\n <div style={{\n display: 'flex',\n gap: styles.spacing.md\n }}>\n <input\n type=\"text\"\n placeholder=\"Search entities...\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n style={{\n flex: 1,\n padding: styles.spacing.md,\n fontSize: styles.typography.fontSize.md,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n backgroundColor: styles.colors.background\n }}\n />\n {searchQuery && (\n <button\n onClick={() => handleSearch('')}\n style={{\n padding: `${styles.spacing.sm} ${styles.spacing.md}`,\n backgroundColor: styles.colors.surfaceHover || styles.colors.surface,\n color: styles.colors.text,\n border: `1px solid ${styles.colors.border}`,\n borderRadius: getBorderRadius('sm'),\n cursor: 'pointer',\n fontSize: styles.typography.fontSize.md\n }}\n >\n Clear\n </button>\n )}\n </div>\n </div>\n \n {/* Entity List */}\n <div style={{\n flex: 1,\n overflow: 'auto',\n padding: styles.spacing.lg\n }}>\n {EntityList && (\n <EntityList\n entities={entities}\n viewMode={viewMode}\n selectedEntityId={selectedEntityId}\n onSelectEntity={handleSelectEntity}\n sortBy={sortBy}\n sortDirection={sortDirection}\n onSortChange={handleSortChange}\n savedUserSettings={savedUserSettings?.entityList}\n onSaveUserSettings={(settings) => onSaveUserSettings?.({\n ...savedUserSettings,\n entityList: settings\n })}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n />\n )}\n \n {/* Empty State */}\n {entities.length === 0 && !loading && (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: styles.spacing.xxl || styles.spacing.xl,\n color: styles.colors.textSecondary\n }}>\n <div style={{\n fontSize: styles.typography.fontSize.xl,\n marginBottom: styles.spacing.md\n }}>\n No entities found\n </div>\n <div style={{\n fontSize: styles.typography.fontSize.md\n }}>\n {searchQuery || Object.keys(filters).length > 0\n ? 'Try adjusting your filters or search query'\n : 'No entities are available'}\n </div>\n </div>\n )}\n </div>\n </div>\n \n {/* Details Panel */}\n {EntityDetails && (\n <EntityDetails\n entity={selectedEntity}\n fields={entityFields}\n relationships={entityRelationships}\n isOpen={detailsPanelOpen}\n onClose={handleCloseDetails}\n onOpenRecord={() => handleOpenRecord(selectedEntity?.Name)}\n savedUserSettings={savedUserSettings?.detailsPanel}\n onSaveUserSettings={(settings) => onSaveUserSettings?.({\n ...savedUserSettings,\n detailsPanel: settings\n })}\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n />\n )}\n </div>\n );\n}",
1374
+ };