@memberjunction/server 2.103.0 → 2.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skip-agent.d.ts +29 -0
- package/dist/agents/skip-agent.d.ts.map +1 -0
- package/dist/agents/skip-agent.js +1308 -0
- package/dist/agents/skip-agent.js.map +1 -0
- package/dist/agents/skip-sdk.d.ts +47 -0
- package/dist/agents/skip-sdk.d.ts.map +1 -0
- package/dist/agents/skip-sdk.js +269 -0
- package/dist/agents/skip-sdk.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +3660 -3386
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +22009 -20223
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -18
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +24 -9
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts +19 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +140 -2
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +2 -2
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +12 -12
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/EntityResolver.d.ts +2 -2
- package/dist/resolvers/EntityResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityResolver.js +4 -4
- package/dist/resolvers/EntityResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +2 -2
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +6 -6
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +14 -14
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +0 -2
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +3 -3
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +28 -21
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts +18 -0
- package/dist/resolvers/TaskResolver.d.ts.map +1 -0
- package/dist/resolvers/TaskResolver.js +138 -0
- package/dist/resolvers/TaskResolver.js.map +1 -0
- package/dist/resolvers/UserFavoriteResolver.d.ts +2 -2
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +5 -5
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserResolver.d.ts +2 -2
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +7 -7
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +2 -2
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +8 -8
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +52 -0
- package/dist/services/TaskOrchestrator.d.ts.map +1 -0
- package/dist/services/TaskOrchestrator.js +486 -0
- package/dist/services/TaskOrchestrator.js.map +1 -0
- package/package.json +30 -38
- package/src/agents/skip-agent.ts +1433 -0
- package/src/agents/skip-sdk.ts +541 -0
- package/src/config.ts +3 -2
- package/src/generated/generated.ts +7948 -6811
- package/src/index.ts +7 -21
- package/src/resolvers/AskSkipResolver.ts +32 -10
- package/src/resolvers/ComponentRegistryResolver.ts +133 -4
- package/src/resolvers/CreateQueryResolver.ts +6 -6
- package/src/resolvers/EntityResolver.ts +4 -4
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +12 -12
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +2 -3
- package/src/resolvers/RunAIAgentResolver.ts +23 -10
- package/src/resolvers/RunTemplateResolver.ts +1 -2
- package/src/resolvers/TaskResolver.ts +142 -0
- package/src/resolvers/UserFavoriteResolver.ts +5 -5
- package/src/resolvers/UserResolver.ts +7 -7
- package/src/resolvers/UserViewResolver.ts +8 -8
- package/src/services/TaskOrchestration-Integration.md +188 -0
- package/src/services/TaskOrchestrator.ts +756 -0
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { BaseAgent } from "@memberjunction/ai-agents";
|
|
11
|
+
import { SkipSDK } from "./skip-sdk.js";
|
|
12
|
+
import { LogStatus, LogError } from "@memberjunction/core";
|
|
13
|
+
import { RegisterClass } from "@memberjunction/global";
|
|
14
|
+
let SkipProxyAgent = class SkipProxyAgent extends BaseAgent {
|
|
15
|
+
skipSDK;
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.skipSDK = new SkipSDK();
|
|
19
|
+
}
|
|
20
|
+
async executeAgentInternal(params, config) {
|
|
21
|
+
LogStatus(`[SkipProxyAgent] Starting Skip agent execution`);
|
|
22
|
+
const context = params.context || {};
|
|
23
|
+
const conversationId = context.conversationId;
|
|
24
|
+
if (!params.contextUser) {
|
|
25
|
+
LogError('[SkipProxyAgent] contextUser is required');
|
|
26
|
+
return {
|
|
27
|
+
finalStep: {
|
|
28
|
+
terminate: true,
|
|
29
|
+
step: 'Failed',
|
|
30
|
+
message: 'Missing required contextUser',
|
|
31
|
+
errorMessage: 'Missing required contextUser'
|
|
32
|
+
},
|
|
33
|
+
stepCount: 1
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const skipMessages = this.convertMessagesToSkipFormat(params.conversationMessages || []);
|
|
37
|
+
const skipOptions = {
|
|
38
|
+
messages: skipMessages,
|
|
39
|
+
conversationId,
|
|
40
|
+
dataContext: context.dataContext,
|
|
41
|
+
requestPhase: 'initial_request',
|
|
42
|
+
contextUser: params.contextUser,
|
|
43
|
+
dataSource: context.dataSource,
|
|
44
|
+
includeEntities: true,
|
|
45
|
+
includeQueries: true,
|
|
46
|
+
includeNotes: true,
|
|
47
|
+
includeRequests: false,
|
|
48
|
+
forceEntityRefresh: context.forceEntityRefresh || false,
|
|
49
|
+
includeCallbackAuth: true,
|
|
50
|
+
onStatusUpdate: (message, responsePhase) => {
|
|
51
|
+
if (params.onProgress) {
|
|
52
|
+
params.onProgress({
|
|
53
|
+
step: 'prompt_execution',
|
|
54
|
+
message,
|
|
55
|
+
percentage: 0,
|
|
56
|
+
metadata: {
|
|
57
|
+
conversationId,
|
|
58
|
+
responsePhase
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const result = await this.skipSDK.chat(skipOptions);
|
|
65
|
+
if (!result.success || !result.response) {
|
|
66
|
+
LogError(`[SkipProxyAgent] Skip API call failed: ${result.error}`);
|
|
67
|
+
return {
|
|
68
|
+
finalStep: {
|
|
69
|
+
terminate: true,
|
|
70
|
+
step: 'Failed',
|
|
71
|
+
message: 'Skip API call failed',
|
|
72
|
+
errorMessage: result.error
|
|
73
|
+
},
|
|
74
|
+
stepCount: 1
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const nextStep = this.mapSkipResponseToNextStep(result.response, conversationId);
|
|
78
|
+
LogStatus(`[SkipProxyAgent] Skip execution completed with phase: ${result.responsePhase}`);
|
|
79
|
+
return {
|
|
80
|
+
finalStep: nextStep,
|
|
81
|
+
stepCount: 1
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
convertMessagesToSkipFormat(messages) {
|
|
85
|
+
return messages.map((msg, index) => ({
|
|
86
|
+
role: (msg.role === 'assistant' ? 'system' : msg.role),
|
|
87
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
|
88
|
+
conversationDetailID: `temp-${index}`
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
mapSkipResponseToNextStep(apiResponse, conversationId) {
|
|
92
|
+
const payload = {
|
|
93
|
+
skipResponse: apiResponse,
|
|
94
|
+
responsePhase: apiResponse.responsePhase,
|
|
95
|
+
conversationId
|
|
96
|
+
};
|
|
97
|
+
return this.tempHack();
|
|
98
|
+
switch (apiResponse.responsePhase) {
|
|
99
|
+
case 'analysis_complete': {
|
|
100
|
+
const completeResponse = apiResponse;
|
|
101
|
+
payload.message = completeResponse.title || 'Analysis complete';
|
|
102
|
+
return {
|
|
103
|
+
terminate: true,
|
|
104
|
+
step: 'Success',
|
|
105
|
+
message: payload.message,
|
|
106
|
+
newPayload: payload
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
case 'clarifying_question': {
|
|
110
|
+
const clarifyResponse = apiResponse;
|
|
111
|
+
payload.message = clarifyResponse.clarifyingQuestion;
|
|
112
|
+
return {
|
|
113
|
+
terminate: true,
|
|
114
|
+
step: 'Chat',
|
|
115
|
+
message: clarifyResponse.clarifyingQuestion,
|
|
116
|
+
newPayload: payload
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
default: {
|
|
120
|
+
LogError(`[SkipProxyAgent] Unknown Skip response phase: ${apiResponse.responsePhase}`);
|
|
121
|
+
return {
|
|
122
|
+
terminate: true,
|
|
123
|
+
step: 'Failed',
|
|
124
|
+
message: `Unknown Skip response phase: ${apiResponse.responsePhase}`,
|
|
125
|
+
errorMessage: `Unknown Skip response phase: ${apiResponse.responsePhase}`,
|
|
126
|
+
newPayload: payload
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
tempHack() {
|
|
132
|
+
return {
|
|
133
|
+
terminate: true,
|
|
134
|
+
step: 'Success',
|
|
135
|
+
message: "Demo Report (not real)",
|
|
136
|
+
newPayload: demoSpecJson
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
SkipProxyAgent = __decorate([
|
|
141
|
+
RegisterClass(BaseAgent, 'SkipProxyAgent'),
|
|
142
|
+
__metadata("design:paramtypes", [])
|
|
143
|
+
], SkipProxyAgent);
|
|
144
|
+
export { SkipProxyAgent };
|
|
145
|
+
const demoSpecJson = {
|
|
146
|
+
"name": "EntityBrowser",
|
|
147
|
+
"title": "Entity Browser",
|
|
148
|
+
"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.",
|
|
149
|
+
"type": "dashboard",
|
|
150
|
+
"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",
|
|
151
|
+
"dataRequirements": {
|
|
152
|
+
"mode": "views",
|
|
153
|
+
"entities": [
|
|
154
|
+
{
|
|
155
|
+
"name": "Entities",
|
|
156
|
+
"description": "Metadata about all entities in the system",
|
|
157
|
+
"displayFields": [
|
|
158
|
+
"ID",
|
|
159
|
+
"Name",
|
|
160
|
+
"DisplayName",
|
|
161
|
+
"NameSuffix",
|
|
162
|
+
"Description",
|
|
163
|
+
"SchemaName",
|
|
164
|
+
"BaseTable",
|
|
165
|
+
"BaseView"
|
|
166
|
+
],
|
|
167
|
+
"filterFields": [
|
|
168
|
+
"SchemaName",
|
|
169
|
+
"BaseTable"
|
|
170
|
+
],
|
|
171
|
+
"sortFields": [
|
|
172
|
+
"Name",
|
|
173
|
+
"DisplayName"
|
|
174
|
+
],
|
|
175
|
+
"fieldMetadata": [
|
|
176
|
+
{
|
|
177
|
+
"name": "ID",
|
|
178
|
+
"sequence": 1,
|
|
179
|
+
"defaultInView": false,
|
|
180
|
+
"type": "uniqueidentifier",
|
|
181
|
+
"allowsNull": false,
|
|
182
|
+
"isPrimaryKey": true,
|
|
183
|
+
"description": "Unique identifier for the entity"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "Name",
|
|
187
|
+
"sequence": 2,
|
|
188
|
+
"defaultInView": true,
|
|
189
|
+
"type": "nvarchar",
|
|
190
|
+
"allowsNull": false,
|
|
191
|
+
"isPrimaryKey": false,
|
|
192
|
+
"description": "System name of the entity"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "DisplayName",
|
|
196
|
+
"sequence": 3,
|
|
197
|
+
"defaultInView": true,
|
|
198
|
+
"type": "nvarchar",
|
|
199
|
+
"allowsNull": true,
|
|
200
|
+
"isPrimaryKey": false,
|
|
201
|
+
"description": "User-friendly display name for the entity"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "NameSuffix",
|
|
205
|
+
"sequence": 4,
|
|
206
|
+
"defaultInView": true,
|
|
207
|
+
"type": "nvarchar",
|
|
208
|
+
"allowsNull": true,
|
|
209
|
+
"isPrimaryKey": false,
|
|
210
|
+
"description": "Optional suffix appended to entity names for display purposes"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "Description",
|
|
214
|
+
"sequence": 5,
|
|
215
|
+
"defaultInView": true,
|
|
216
|
+
"type": "nvarchar",
|
|
217
|
+
"allowsNull": true,
|
|
218
|
+
"isPrimaryKey": false,
|
|
219
|
+
"description": "Description of the entity"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "SchemaName",
|
|
223
|
+
"sequence": 6,
|
|
224
|
+
"defaultInView": true,
|
|
225
|
+
"type": "nvarchar",
|
|
226
|
+
"allowsNull": true,
|
|
227
|
+
"isPrimaryKey": false,
|
|
228
|
+
"description": "Database schema name"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"name": "BaseTable",
|
|
232
|
+
"sequence": 7,
|
|
233
|
+
"defaultInView": true,
|
|
234
|
+
"type": "nvarchar",
|
|
235
|
+
"allowsNull": true,
|
|
236
|
+
"isPrimaryKey": false,
|
|
237
|
+
"description": "Base table in the database"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"name": "BaseView",
|
|
241
|
+
"sequence": 8,
|
|
242
|
+
"defaultInView": true,
|
|
243
|
+
"type": "nvarchar",
|
|
244
|
+
"allowsNull": false,
|
|
245
|
+
"isPrimaryKey": false,
|
|
246
|
+
"description": "Base view used for the entity"
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
"permissionLevelNeeded": [
|
|
250
|
+
"read"
|
|
251
|
+
],
|
|
252
|
+
"usageContext": "Main entity list display and filtering"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "Entity Fields",
|
|
256
|
+
"description": "Fields belonging to each entity",
|
|
257
|
+
"displayFields": [
|
|
258
|
+
"Name",
|
|
259
|
+
"DisplayName",
|
|
260
|
+
"Type",
|
|
261
|
+
"Length",
|
|
262
|
+
"AllowsNull",
|
|
263
|
+
"IsPrimaryKey",
|
|
264
|
+
"IsUnique"
|
|
265
|
+
],
|
|
266
|
+
"filterFields": [
|
|
267
|
+
"EntityID"
|
|
268
|
+
],
|
|
269
|
+
"sortFields": [
|
|
270
|
+
"Sequence",
|
|
271
|
+
"Name"
|
|
272
|
+
],
|
|
273
|
+
"fieldMetadata": [
|
|
274
|
+
{
|
|
275
|
+
"name": "EntityID",
|
|
276
|
+
"sequence": 1,
|
|
277
|
+
"defaultInView": false,
|
|
278
|
+
"type": "uniqueidentifier",
|
|
279
|
+
"allowsNull": false,
|
|
280
|
+
"isPrimaryKey": false,
|
|
281
|
+
"description": "Reference to parent entity"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"name": "Name",
|
|
285
|
+
"sequence": 2,
|
|
286
|
+
"defaultInView": true,
|
|
287
|
+
"type": "nvarchar",
|
|
288
|
+
"allowsNull": false,
|
|
289
|
+
"isPrimaryKey": false,
|
|
290
|
+
"description": "Field name"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "DisplayName",
|
|
294
|
+
"sequence": 3,
|
|
295
|
+
"defaultInView": true,
|
|
296
|
+
"type": "nvarchar",
|
|
297
|
+
"allowsNull": true,
|
|
298
|
+
"isPrimaryKey": false,
|
|
299
|
+
"description": "User-friendly field name"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"name": "Type",
|
|
303
|
+
"sequence": 4,
|
|
304
|
+
"defaultInView": true,
|
|
305
|
+
"type": "nvarchar",
|
|
306
|
+
"allowsNull": false,
|
|
307
|
+
"isPrimaryKey": false,
|
|
308
|
+
"description": "Data type of the field"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "Length",
|
|
312
|
+
"sequence": 5,
|
|
313
|
+
"defaultInView": true,
|
|
314
|
+
"type": "int",
|
|
315
|
+
"allowsNull": true,
|
|
316
|
+
"isPrimaryKey": false,
|
|
317
|
+
"description": "Maximum length for string fields"
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"name": "AllowsNull",
|
|
321
|
+
"sequence": 6,
|
|
322
|
+
"defaultInView": true,
|
|
323
|
+
"type": "bit",
|
|
324
|
+
"allowsNull": false,
|
|
325
|
+
"isPrimaryKey": false,
|
|
326
|
+
"description": "Whether field allows null values"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"name": "IsPrimaryKey",
|
|
330
|
+
"sequence": 7,
|
|
331
|
+
"defaultInView": true,
|
|
332
|
+
"type": "bit",
|
|
333
|
+
"allowsNull": false,
|
|
334
|
+
"isPrimaryKey": false,
|
|
335
|
+
"description": "Whether field is part of primary key"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"name": "IsUnique",
|
|
339
|
+
"sequence": 8,
|
|
340
|
+
"defaultInView": true,
|
|
341
|
+
"type": "bit",
|
|
342
|
+
"allowsNull": false,
|
|
343
|
+
"isPrimaryKey": false,
|
|
344
|
+
"description": "Whether field must be unique"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"name": "Sequence",
|
|
348
|
+
"sequence": 9,
|
|
349
|
+
"defaultInView": false,
|
|
350
|
+
"type": "int",
|
|
351
|
+
"allowsNull": false,
|
|
352
|
+
"isPrimaryKey": false,
|
|
353
|
+
"description": "Display order of the field"
|
|
354
|
+
}
|
|
355
|
+
],
|
|
356
|
+
"permissionLevelNeeded": [
|
|
357
|
+
"read"
|
|
358
|
+
],
|
|
359
|
+
"usageContext": "Details panel to show entity fields"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"name": "Entity Relationships",
|
|
363
|
+
"description": "Relationships between entities",
|
|
364
|
+
"displayFields": [
|
|
365
|
+
"RelatedEntity",
|
|
366
|
+
"Type",
|
|
367
|
+
"DisplayName",
|
|
368
|
+
"RelatedEntityJoinField"
|
|
369
|
+
],
|
|
370
|
+
"filterFields": [
|
|
371
|
+
"EntityID"
|
|
372
|
+
],
|
|
373
|
+
"sortFields": [
|
|
374
|
+
"Sequence",
|
|
375
|
+
"RelatedEntity"
|
|
376
|
+
],
|
|
377
|
+
"fieldMetadata": [
|
|
378
|
+
{
|
|
379
|
+
"name": "EntityID",
|
|
380
|
+
"sequence": 1,
|
|
381
|
+
"defaultInView": false,
|
|
382
|
+
"type": "uniqueidentifier",
|
|
383
|
+
"allowsNull": false,
|
|
384
|
+
"isPrimaryKey": false,
|
|
385
|
+
"description": "Reference to parent entity"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"name": "RelatedEntity",
|
|
389
|
+
"sequence": 2,
|
|
390
|
+
"defaultInView": true,
|
|
391
|
+
"type": "nvarchar",
|
|
392
|
+
"allowsNull": false,
|
|
393
|
+
"isPrimaryKey": false,
|
|
394
|
+
"description": "The related entity in the relationship"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"name": "Type",
|
|
398
|
+
"sequence": 3,
|
|
399
|
+
"defaultInView": true,
|
|
400
|
+
"type": "nvarchar",
|
|
401
|
+
"allowsNull": false,
|
|
402
|
+
"isPrimaryKey": false,
|
|
403
|
+
"description": "Type of relationship (One to Many, Many to One, etc.)"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"name": "DisplayName",
|
|
407
|
+
"sequence": 4,
|
|
408
|
+
"defaultInView": true,
|
|
409
|
+
"type": "nvarchar",
|
|
410
|
+
"allowsNull": true,
|
|
411
|
+
"isPrimaryKey": false,
|
|
412
|
+
"description": "User-friendly name for the relationship"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"name": "RelatedEntityJoinField",
|
|
416
|
+
"sequence": 5,
|
|
417
|
+
"defaultInView": true,
|
|
418
|
+
"type": "nvarchar",
|
|
419
|
+
"allowsNull": true,
|
|
420
|
+
"isPrimaryKey": false,
|
|
421
|
+
"description": "The field in the related entity that joins to this entity"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"name": "Sequence",
|
|
425
|
+
"sequence": 6,
|
|
426
|
+
"defaultInView": false,
|
|
427
|
+
"type": "int",
|
|
428
|
+
"allowsNull": false,
|
|
429
|
+
"isPrimaryKey": false,
|
|
430
|
+
"description": "Display order"
|
|
431
|
+
}
|
|
432
|
+
],
|
|
433
|
+
"permissionLevelNeeded": [
|
|
434
|
+
"read"
|
|
435
|
+
],
|
|
436
|
+
"usageContext": "Details panel to show entity relationships"
|
|
437
|
+
}
|
|
438
|
+
],
|
|
439
|
+
"queries": [],
|
|
440
|
+
"description": "This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience"
|
|
441
|
+
},
|
|
442
|
+
"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",
|
|
443
|
+
"properties": [],
|
|
444
|
+
"events": [],
|
|
445
|
+
"exampleUsage": "<EntityBrowser\n utilities={utilities}\n styles={styles}\n components={components}\n callbacks={callbacks}\n savedUserSettings={savedUserSettings}\n onSaveUserSettings={onSaveUserSettings}\n/>",
|
|
446
|
+
"dependencies": [
|
|
447
|
+
{
|
|
448
|
+
"name": "EntityList",
|
|
449
|
+
"title": "Entity List",
|
|
450
|
+
"description": "Displays entities in a grid or card layout with sorting capabilities",
|
|
451
|
+
"type": "table",
|
|
452
|
+
"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",
|
|
453
|
+
"dataRequirements": {
|
|
454
|
+
"mode": "views",
|
|
455
|
+
"entities": [
|
|
456
|
+
{
|
|
457
|
+
"name": "Entities",
|
|
458
|
+
"description": "Metadata about all entities in the system",
|
|
459
|
+
"displayFields": [
|
|
460
|
+
"ID",
|
|
461
|
+
"Name",
|
|
462
|
+
"DisplayName",
|
|
463
|
+
"NameSuffix",
|
|
464
|
+
"Description",
|
|
465
|
+
"SchemaName",
|
|
466
|
+
"BaseTable",
|
|
467
|
+
"BaseView"
|
|
468
|
+
],
|
|
469
|
+
"filterFields": [
|
|
470
|
+
"SchemaName",
|
|
471
|
+
"BaseTable"
|
|
472
|
+
],
|
|
473
|
+
"sortFields": [
|
|
474
|
+
"Name",
|
|
475
|
+
"DisplayName"
|
|
476
|
+
],
|
|
477
|
+
"fieldMetadata": [
|
|
478
|
+
{
|
|
479
|
+
"name": "ID",
|
|
480
|
+
"sequence": 1,
|
|
481
|
+
"defaultInView": false,
|
|
482
|
+
"type": "uniqueidentifier",
|
|
483
|
+
"allowsNull": false,
|
|
484
|
+
"isPrimaryKey": true,
|
|
485
|
+
"description": "Unique identifier for the entity"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
"name": "Name",
|
|
489
|
+
"sequence": 2,
|
|
490
|
+
"defaultInView": true,
|
|
491
|
+
"type": "nvarchar",
|
|
492
|
+
"allowsNull": false,
|
|
493
|
+
"isPrimaryKey": false,
|
|
494
|
+
"description": "System name of the entity"
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"name": "DisplayName",
|
|
498
|
+
"sequence": 3,
|
|
499
|
+
"defaultInView": true,
|
|
500
|
+
"type": "nvarchar",
|
|
501
|
+
"allowsNull": true,
|
|
502
|
+
"isPrimaryKey": false,
|
|
503
|
+
"description": "User-friendly display name for the entity"
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"name": "NameSuffix",
|
|
507
|
+
"sequence": 4,
|
|
508
|
+
"defaultInView": true,
|
|
509
|
+
"type": "nvarchar",
|
|
510
|
+
"allowsNull": true,
|
|
511
|
+
"isPrimaryKey": false,
|
|
512
|
+
"description": "Optional suffix appended to entity names for display purposes"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"name": "Description",
|
|
516
|
+
"sequence": 5,
|
|
517
|
+
"defaultInView": true,
|
|
518
|
+
"type": "nvarchar",
|
|
519
|
+
"allowsNull": true,
|
|
520
|
+
"isPrimaryKey": false,
|
|
521
|
+
"description": "Description of the entity"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"name": "SchemaName",
|
|
525
|
+
"sequence": 6,
|
|
526
|
+
"defaultInView": true,
|
|
527
|
+
"type": "nvarchar",
|
|
528
|
+
"allowsNull": true,
|
|
529
|
+
"isPrimaryKey": false,
|
|
530
|
+
"description": "Database schema name"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
"name": "BaseTable",
|
|
534
|
+
"sequence": 7,
|
|
535
|
+
"defaultInView": true,
|
|
536
|
+
"type": "nvarchar",
|
|
537
|
+
"allowsNull": true,
|
|
538
|
+
"isPrimaryKey": false,
|
|
539
|
+
"description": "Base table in the database"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
"name": "BaseView",
|
|
543
|
+
"sequence": 8,
|
|
544
|
+
"defaultInView": true,
|
|
545
|
+
"type": "nvarchar",
|
|
546
|
+
"allowsNull": false,
|
|
547
|
+
"isPrimaryKey": false,
|
|
548
|
+
"description": "Base view used for the entity"
|
|
549
|
+
}
|
|
550
|
+
],
|
|
551
|
+
"permissionLevelNeeded": [
|
|
552
|
+
"read"
|
|
553
|
+
],
|
|
554
|
+
"usageContext": "Main entity list display and filtering"
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
"name": "Entity Fields",
|
|
558
|
+
"description": "Fields belonging to each entity",
|
|
559
|
+
"displayFields": [
|
|
560
|
+
"Name",
|
|
561
|
+
"DisplayName",
|
|
562
|
+
"Type",
|
|
563
|
+
"Length",
|
|
564
|
+
"AllowsNull",
|
|
565
|
+
"IsPrimaryKey",
|
|
566
|
+
"IsUnique"
|
|
567
|
+
],
|
|
568
|
+
"filterFields": [
|
|
569
|
+
"EntityID"
|
|
570
|
+
],
|
|
571
|
+
"sortFields": [
|
|
572
|
+
"Sequence",
|
|
573
|
+
"Name"
|
|
574
|
+
],
|
|
575
|
+
"fieldMetadata": [
|
|
576
|
+
{
|
|
577
|
+
"name": "EntityID",
|
|
578
|
+
"sequence": 1,
|
|
579
|
+
"defaultInView": false,
|
|
580
|
+
"type": "uniqueidentifier",
|
|
581
|
+
"allowsNull": false,
|
|
582
|
+
"isPrimaryKey": false,
|
|
583
|
+
"description": "Reference to parent entity"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
"name": "Name",
|
|
587
|
+
"sequence": 2,
|
|
588
|
+
"defaultInView": true,
|
|
589
|
+
"type": "nvarchar",
|
|
590
|
+
"allowsNull": false,
|
|
591
|
+
"isPrimaryKey": false,
|
|
592
|
+
"description": "Field name"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"name": "DisplayName",
|
|
596
|
+
"sequence": 3,
|
|
597
|
+
"defaultInView": true,
|
|
598
|
+
"type": "nvarchar",
|
|
599
|
+
"allowsNull": true,
|
|
600
|
+
"isPrimaryKey": false,
|
|
601
|
+
"description": "User-friendly field name"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
"name": "Type",
|
|
605
|
+
"sequence": 4,
|
|
606
|
+
"defaultInView": true,
|
|
607
|
+
"type": "nvarchar",
|
|
608
|
+
"allowsNull": false,
|
|
609
|
+
"isPrimaryKey": false,
|
|
610
|
+
"description": "Data type of the field"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
"name": "Length",
|
|
614
|
+
"sequence": 5,
|
|
615
|
+
"defaultInView": true,
|
|
616
|
+
"type": "int",
|
|
617
|
+
"allowsNull": true,
|
|
618
|
+
"isPrimaryKey": false,
|
|
619
|
+
"description": "Maximum length for string fields"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
"name": "AllowsNull",
|
|
623
|
+
"sequence": 6,
|
|
624
|
+
"defaultInView": true,
|
|
625
|
+
"type": "bit",
|
|
626
|
+
"allowsNull": false,
|
|
627
|
+
"isPrimaryKey": false,
|
|
628
|
+
"description": "Whether field allows null values"
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
"name": "IsPrimaryKey",
|
|
632
|
+
"sequence": 7,
|
|
633
|
+
"defaultInView": true,
|
|
634
|
+
"type": "bit",
|
|
635
|
+
"allowsNull": false,
|
|
636
|
+
"isPrimaryKey": false,
|
|
637
|
+
"description": "Whether field is part of primary key"
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
"name": "IsUnique",
|
|
641
|
+
"sequence": 8,
|
|
642
|
+
"defaultInView": true,
|
|
643
|
+
"type": "bit",
|
|
644
|
+
"allowsNull": false,
|
|
645
|
+
"isPrimaryKey": false,
|
|
646
|
+
"description": "Whether field must be unique"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"name": "Sequence",
|
|
650
|
+
"sequence": 9,
|
|
651
|
+
"defaultInView": false,
|
|
652
|
+
"type": "int",
|
|
653
|
+
"allowsNull": false,
|
|
654
|
+
"isPrimaryKey": false,
|
|
655
|
+
"description": "Display order of the field"
|
|
656
|
+
}
|
|
657
|
+
],
|
|
658
|
+
"permissionLevelNeeded": [
|
|
659
|
+
"read"
|
|
660
|
+
],
|
|
661
|
+
"usageContext": "Details panel to show entity fields"
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
"name": "Entity Relationships",
|
|
665
|
+
"description": "Relationships between entities",
|
|
666
|
+
"displayFields": [
|
|
667
|
+
"RelatedEntity",
|
|
668
|
+
"Type",
|
|
669
|
+
"DisplayName",
|
|
670
|
+
"RelatedEntityJoinField"
|
|
671
|
+
],
|
|
672
|
+
"filterFields": [
|
|
673
|
+
"EntityID"
|
|
674
|
+
],
|
|
675
|
+
"sortFields": [
|
|
676
|
+
"Sequence",
|
|
677
|
+
"RelatedEntity"
|
|
678
|
+
],
|
|
679
|
+
"fieldMetadata": [
|
|
680
|
+
{
|
|
681
|
+
"name": "EntityID",
|
|
682
|
+
"sequence": 1,
|
|
683
|
+
"defaultInView": false,
|
|
684
|
+
"type": "uniqueidentifier",
|
|
685
|
+
"allowsNull": false,
|
|
686
|
+
"isPrimaryKey": false,
|
|
687
|
+
"description": "Reference to parent entity"
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
"name": "RelatedEntity",
|
|
691
|
+
"sequence": 2,
|
|
692
|
+
"defaultInView": true,
|
|
693
|
+
"type": "nvarchar",
|
|
694
|
+
"allowsNull": false,
|
|
695
|
+
"isPrimaryKey": false,
|
|
696
|
+
"description": "The related entity in the relationship"
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
"name": "Type",
|
|
700
|
+
"sequence": 3,
|
|
701
|
+
"defaultInView": true,
|
|
702
|
+
"type": "nvarchar",
|
|
703
|
+
"allowsNull": false,
|
|
704
|
+
"isPrimaryKey": false,
|
|
705
|
+
"description": "Type of relationship (One to Many, Many to One, etc.)"
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
"name": "DisplayName",
|
|
709
|
+
"sequence": 4,
|
|
710
|
+
"defaultInView": true,
|
|
711
|
+
"type": "nvarchar",
|
|
712
|
+
"allowsNull": true,
|
|
713
|
+
"isPrimaryKey": false,
|
|
714
|
+
"description": "User-friendly name for the relationship"
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
"name": "RelatedEntityJoinField",
|
|
718
|
+
"sequence": 5,
|
|
719
|
+
"defaultInView": true,
|
|
720
|
+
"type": "nvarchar",
|
|
721
|
+
"allowsNull": true,
|
|
722
|
+
"isPrimaryKey": false,
|
|
723
|
+
"description": "The field in the related entity that joins to this entity"
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
"name": "Sequence",
|
|
727
|
+
"sequence": 6,
|
|
728
|
+
"defaultInView": false,
|
|
729
|
+
"type": "int",
|
|
730
|
+
"allowsNull": false,
|
|
731
|
+
"isPrimaryKey": false,
|
|
732
|
+
"description": "Display order"
|
|
733
|
+
}
|
|
734
|
+
],
|
|
735
|
+
"permissionLevelNeeded": [
|
|
736
|
+
"read"
|
|
737
|
+
],
|
|
738
|
+
"usageContext": "Details panel to show entity relationships"
|
|
739
|
+
}
|
|
740
|
+
],
|
|
741
|
+
"queries": [],
|
|
742
|
+
"description": "This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience"
|
|
743
|
+
},
|
|
744
|
+
"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",
|
|
745
|
+
"properties": [
|
|
746
|
+
{
|
|
747
|
+
"name": "entities",
|
|
748
|
+
"description": "Array of entity objects to display",
|
|
749
|
+
"type": "array",
|
|
750
|
+
"required": true
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
"name": "viewMode",
|
|
754
|
+
"description": "Display mode - grid or card view",
|
|
755
|
+
"type": "string",
|
|
756
|
+
"required": true,
|
|
757
|
+
"possibleValues": [
|
|
758
|
+
"grid",
|
|
759
|
+
"card"
|
|
760
|
+
]
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"name": "selectedEntityId",
|
|
764
|
+
"description": "ID of the currently selected entity",
|
|
765
|
+
"type": "string",
|
|
766
|
+
"required": true
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
"name": "onSelectEntity",
|
|
770
|
+
"description": "Callback when an entity is selected",
|
|
771
|
+
"type": "function",
|
|
772
|
+
"required": true
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
"name": "sortBy",
|
|
776
|
+
"description": "Field to sort by",
|
|
777
|
+
"type": "string",
|
|
778
|
+
"required": true,
|
|
779
|
+
"defaultValue": "Name"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"name": "sortDirection",
|
|
783
|
+
"description": "Sort direction",
|
|
784
|
+
"type": "string",
|
|
785
|
+
"required": true,
|
|
786
|
+
"defaultValue": "asc",
|
|
787
|
+
"possibleValues": [
|
|
788
|
+
"asc",
|
|
789
|
+
"desc"
|
|
790
|
+
]
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"name": "onSortChange",
|
|
794
|
+
"description": "Callback when sort changes",
|
|
795
|
+
"type": "function",
|
|
796
|
+
"required": true
|
|
797
|
+
}
|
|
798
|
+
],
|
|
799
|
+
"events": [
|
|
800
|
+
{
|
|
801
|
+
"name": "onSelectEntity",
|
|
802
|
+
"description": "Fired when an entity is selected",
|
|
803
|
+
"parameters": [
|
|
804
|
+
{
|
|
805
|
+
"name": "entityId",
|
|
806
|
+
"description": "ID of the selected entity",
|
|
807
|
+
"type": "string"
|
|
808
|
+
}
|
|
809
|
+
]
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"name": "onSortChange",
|
|
813
|
+
"description": "Fired when sort configuration changes",
|
|
814
|
+
"parameters": [
|
|
815
|
+
{
|
|
816
|
+
"name": "sortBy",
|
|
817
|
+
"description": "Field to sort by",
|
|
818
|
+
"type": "string"
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
"name": "sortDirection",
|
|
822
|
+
"description": "Sort direction",
|
|
823
|
+
"type": "string"
|
|
824
|
+
}
|
|
825
|
+
]
|
|
826
|
+
}
|
|
827
|
+
],
|
|
828
|
+
"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/>",
|
|
829
|
+
"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}",
|
|
830
|
+
"dependencies": [
|
|
831
|
+
{
|
|
832
|
+
"name": "DataGrid",
|
|
833
|
+
"location": "registry",
|
|
834
|
+
"namespace": "Generic/UI/Table",
|
|
835
|
+
"version": "^1.0.0"
|
|
836
|
+
}
|
|
837
|
+
],
|
|
838
|
+
"libraries": []
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
"name": "EntityDetails",
|
|
842
|
+
"title": "Entity Details Panel",
|
|
843
|
+
"description": "Sliding panel that displays detailed information about a selected entity including fields and relationships",
|
|
844
|
+
"type": "form",
|
|
845
|
+
"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",
|
|
846
|
+
"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",
|
|
847
|
+
"dataRequirements": {
|
|
848
|
+
"mode": "views",
|
|
849
|
+
"entities": [
|
|
850
|
+
{
|
|
851
|
+
"name": "Entities",
|
|
852
|
+
"description": "Metadata about all entities in the system",
|
|
853
|
+
"displayFields": [
|
|
854
|
+
"ID",
|
|
855
|
+
"Name",
|
|
856
|
+
"DisplayName",
|
|
857
|
+
"NameSuffix",
|
|
858
|
+
"Description",
|
|
859
|
+
"SchemaName",
|
|
860
|
+
"BaseTable",
|
|
861
|
+
"BaseView"
|
|
862
|
+
],
|
|
863
|
+
"filterFields": [
|
|
864
|
+
"SchemaName",
|
|
865
|
+
"BaseTable"
|
|
866
|
+
],
|
|
867
|
+
"sortFields": [
|
|
868
|
+
"Name",
|
|
869
|
+
"DisplayName"
|
|
870
|
+
],
|
|
871
|
+
"fieldMetadata": [
|
|
872
|
+
{
|
|
873
|
+
"name": "ID",
|
|
874
|
+
"sequence": 1,
|
|
875
|
+
"defaultInView": false,
|
|
876
|
+
"type": "uniqueidentifier",
|
|
877
|
+
"allowsNull": false,
|
|
878
|
+
"isPrimaryKey": true,
|
|
879
|
+
"description": "Unique identifier for the entity"
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
"name": "Name",
|
|
883
|
+
"sequence": 2,
|
|
884
|
+
"defaultInView": true,
|
|
885
|
+
"type": "nvarchar",
|
|
886
|
+
"allowsNull": false,
|
|
887
|
+
"isPrimaryKey": false,
|
|
888
|
+
"description": "System name of the entity"
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
"name": "DisplayName",
|
|
892
|
+
"sequence": 3,
|
|
893
|
+
"defaultInView": true,
|
|
894
|
+
"type": "nvarchar",
|
|
895
|
+
"allowsNull": true,
|
|
896
|
+
"isPrimaryKey": false,
|
|
897
|
+
"description": "User-friendly display name for the entity"
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
"name": "NameSuffix",
|
|
901
|
+
"sequence": 4,
|
|
902
|
+
"defaultInView": true,
|
|
903
|
+
"type": "nvarchar",
|
|
904
|
+
"allowsNull": true,
|
|
905
|
+
"isPrimaryKey": false,
|
|
906
|
+
"description": "Optional suffix appended to entity names for display purposes"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
"name": "Description",
|
|
910
|
+
"sequence": 5,
|
|
911
|
+
"defaultInView": true,
|
|
912
|
+
"type": "nvarchar",
|
|
913
|
+
"allowsNull": true,
|
|
914
|
+
"isPrimaryKey": false,
|
|
915
|
+
"description": "Description of the entity"
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
"name": "SchemaName",
|
|
919
|
+
"sequence": 6,
|
|
920
|
+
"defaultInView": true,
|
|
921
|
+
"type": "nvarchar",
|
|
922
|
+
"allowsNull": true,
|
|
923
|
+
"isPrimaryKey": false,
|
|
924
|
+
"description": "Database schema name"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
"name": "BaseTable",
|
|
928
|
+
"sequence": 7,
|
|
929
|
+
"defaultInView": true,
|
|
930
|
+
"type": "nvarchar",
|
|
931
|
+
"allowsNull": true,
|
|
932
|
+
"isPrimaryKey": false,
|
|
933
|
+
"description": "Base table in the database"
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"name": "BaseView",
|
|
937
|
+
"sequence": 8,
|
|
938
|
+
"defaultInView": true,
|
|
939
|
+
"type": "nvarchar",
|
|
940
|
+
"allowsNull": false,
|
|
941
|
+
"isPrimaryKey": false,
|
|
942
|
+
"description": "Base view used for the entity"
|
|
943
|
+
}
|
|
944
|
+
],
|
|
945
|
+
"permissionLevelNeeded": [
|
|
946
|
+
"read"
|
|
947
|
+
],
|
|
948
|
+
"usageContext": "Main entity list display and filtering"
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
"name": "Entity Fields",
|
|
952
|
+
"description": "Fields belonging to each entity",
|
|
953
|
+
"displayFields": [
|
|
954
|
+
"Name",
|
|
955
|
+
"DisplayName",
|
|
956
|
+
"Type",
|
|
957
|
+
"Length",
|
|
958
|
+
"AllowsNull",
|
|
959
|
+
"IsPrimaryKey",
|
|
960
|
+
"IsUnique"
|
|
961
|
+
],
|
|
962
|
+
"filterFields": [
|
|
963
|
+
"EntityID"
|
|
964
|
+
],
|
|
965
|
+
"sortFields": [
|
|
966
|
+
"Sequence",
|
|
967
|
+
"Name"
|
|
968
|
+
],
|
|
969
|
+
"fieldMetadata": [
|
|
970
|
+
{
|
|
971
|
+
"name": "EntityID",
|
|
972
|
+
"sequence": 1,
|
|
973
|
+
"defaultInView": false,
|
|
974
|
+
"type": "uniqueidentifier",
|
|
975
|
+
"allowsNull": false,
|
|
976
|
+
"isPrimaryKey": false,
|
|
977
|
+
"description": "Reference to parent entity"
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
"name": "Name",
|
|
981
|
+
"sequence": 2,
|
|
982
|
+
"defaultInView": true,
|
|
983
|
+
"type": "nvarchar",
|
|
984
|
+
"allowsNull": false,
|
|
985
|
+
"isPrimaryKey": false,
|
|
986
|
+
"description": "Field name"
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
"name": "DisplayName",
|
|
990
|
+
"sequence": 3,
|
|
991
|
+
"defaultInView": true,
|
|
992
|
+
"type": "nvarchar",
|
|
993
|
+
"allowsNull": true,
|
|
994
|
+
"isPrimaryKey": false,
|
|
995
|
+
"description": "User-friendly field name"
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
"name": "Type",
|
|
999
|
+
"sequence": 4,
|
|
1000
|
+
"defaultInView": true,
|
|
1001
|
+
"type": "nvarchar",
|
|
1002
|
+
"allowsNull": false,
|
|
1003
|
+
"isPrimaryKey": false,
|
|
1004
|
+
"description": "Data type of the field"
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
"name": "Length",
|
|
1008
|
+
"sequence": 5,
|
|
1009
|
+
"defaultInView": true,
|
|
1010
|
+
"type": "int",
|
|
1011
|
+
"allowsNull": true,
|
|
1012
|
+
"isPrimaryKey": false,
|
|
1013
|
+
"description": "Maximum length for string fields"
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
"name": "AllowsNull",
|
|
1017
|
+
"sequence": 6,
|
|
1018
|
+
"defaultInView": true,
|
|
1019
|
+
"type": "bit",
|
|
1020
|
+
"allowsNull": false,
|
|
1021
|
+
"isPrimaryKey": false,
|
|
1022
|
+
"description": "Whether field allows null values"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
"name": "IsPrimaryKey",
|
|
1026
|
+
"sequence": 7,
|
|
1027
|
+
"defaultInView": true,
|
|
1028
|
+
"type": "bit",
|
|
1029
|
+
"allowsNull": false,
|
|
1030
|
+
"isPrimaryKey": false,
|
|
1031
|
+
"description": "Whether field is part of primary key"
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
"name": "IsUnique",
|
|
1035
|
+
"sequence": 8,
|
|
1036
|
+
"defaultInView": true,
|
|
1037
|
+
"type": "bit",
|
|
1038
|
+
"allowsNull": false,
|
|
1039
|
+
"isPrimaryKey": false,
|
|
1040
|
+
"description": "Whether field must be unique"
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
"name": "Sequence",
|
|
1044
|
+
"sequence": 9,
|
|
1045
|
+
"defaultInView": false,
|
|
1046
|
+
"type": "int",
|
|
1047
|
+
"allowsNull": false,
|
|
1048
|
+
"isPrimaryKey": false,
|
|
1049
|
+
"description": "Display order of the field"
|
|
1050
|
+
}
|
|
1051
|
+
],
|
|
1052
|
+
"permissionLevelNeeded": [
|
|
1053
|
+
"read"
|
|
1054
|
+
],
|
|
1055
|
+
"usageContext": "Details panel to show entity fields"
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
"name": "Entity Relationships",
|
|
1059
|
+
"description": "Relationships between entities",
|
|
1060
|
+
"displayFields": [
|
|
1061
|
+
"RelatedEntity",
|
|
1062
|
+
"Type",
|
|
1063
|
+
"DisplayName",
|
|
1064
|
+
"RelatedEntityJoinField"
|
|
1065
|
+
],
|
|
1066
|
+
"filterFields": [
|
|
1067
|
+
"EntityID"
|
|
1068
|
+
],
|
|
1069
|
+
"sortFields": [
|
|
1070
|
+
"Sequence",
|
|
1071
|
+
"RelatedEntity"
|
|
1072
|
+
],
|
|
1073
|
+
"fieldMetadata": [
|
|
1074
|
+
{
|
|
1075
|
+
"name": "EntityID",
|
|
1076
|
+
"sequence": 1,
|
|
1077
|
+
"defaultInView": false,
|
|
1078
|
+
"type": "uniqueidentifier",
|
|
1079
|
+
"allowsNull": false,
|
|
1080
|
+
"isPrimaryKey": false,
|
|
1081
|
+
"description": "Reference to parent entity"
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
"name": "RelatedEntity",
|
|
1085
|
+
"sequence": 2,
|
|
1086
|
+
"defaultInView": true,
|
|
1087
|
+
"type": "nvarchar",
|
|
1088
|
+
"allowsNull": false,
|
|
1089
|
+
"isPrimaryKey": false,
|
|
1090
|
+
"description": "The related entity in the relationship"
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
"name": "Type",
|
|
1094
|
+
"sequence": 3,
|
|
1095
|
+
"defaultInView": true,
|
|
1096
|
+
"type": "nvarchar",
|
|
1097
|
+
"allowsNull": false,
|
|
1098
|
+
"isPrimaryKey": false,
|
|
1099
|
+
"description": "Type of relationship (One to Many, Many to One, etc.)"
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
"name": "DisplayName",
|
|
1103
|
+
"sequence": 4,
|
|
1104
|
+
"defaultInView": true,
|
|
1105
|
+
"type": "nvarchar",
|
|
1106
|
+
"allowsNull": true,
|
|
1107
|
+
"isPrimaryKey": false,
|
|
1108
|
+
"description": "User-friendly name for the relationship"
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
"name": "RelatedEntityJoinField",
|
|
1112
|
+
"sequence": 5,
|
|
1113
|
+
"defaultInView": true,
|
|
1114
|
+
"type": "nvarchar",
|
|
1115
|
+
"allowsNull": true,
|
|
1116
|
+
"isPrimaryKey": false,
|
|
1117
|
+
"description": "The field in the related entity that joins to this entity"
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
"name": "Sequence",
|
|
1121
|
+
"sequence": 6,
|
|
1122
|
+
"defaultInView": false,
|
|
1123
|
+
"type": "int",
|
|
1124
|
+
"allowsNull": false,
|
|
1125
|
+
"isPrimaryKey": false,
|
|
1126
|
+
"description": "Display order"
|
|
1127
|
+
}
|
|
1128
|
+
],
|
|
1129
|
+
"permissionLevelNeeded": [
|
|
1130
|
+
"read"
|
|
1131
|
+
],
|
|
1132
|
+
"usageContext": "Details panel to show entity relationships"
|
|
1133
|
+
}
|
|
1134
|
+
],
|
|
1135
|
+
"queries": [],
|
|
1136
|
+
"description": "This component requires access to entity metadata including entities, their fields, and relationships to provide a comprehensive entity browsing experience"
|
|
1137
|
+
},
|
|
1138
|
+
"properties": [
|
|
1139
|
+
{
|
|
1140
|
+
"name": "entity",
|
|
1141
|
+
"description": "The selected entity object",
|
|
1142
|
+
"type": "object",
|
|
1143
|
+
"required": true
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
"name": "fields",
|
|
1147
|
+
"description": "Array of fields for the entity",
|
|
1148
|
+
"type": "array",
|
|
1149
|
+
"required": true,
|
|
1150
|
+
"defaultValue": []
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
"name": "relationships",
|
|
1154
|
+
"description": "Array of relationships for the entity",
|
|
1155
|
+
"type": "array",
|
|
1156
|
+
"required": true,
|
|
1157
|
+
"defaultValue": []
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
"name": "isOpen",
|
|
1161
|
+
"description": "Whether the panel is open",
|
|
1162
|
+
"type": "boolean",
|
|
1163
|
+
"required": true
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
"name": "onClose",
|
|
1167
|
+
"description": "Callback to close the panel",
|
|
1168
|
+
"type": "function",
|
|
1169
|
+
"required": true
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
"name": "onOpenRecord",
|
|
1173
|
+
"description": "Callback to open the entity record",
|
|
1174
|
+
"type": "function",
|
|
1175
|
+
"required": true
|
|
1176
|
+
}
|
|
1177
|
+
],
|
|
1178
|
+
"events": [
|
|
1179
|
+
{
|
|
1180
|
+
"name": "onClose",
|
|
1181
|
+
"description": "Fired when the panel should close",
|
|
1182
|
+
"parameters": []
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
"name": "onOpenRecord",
|
|
1186
|
+
"description": "Fired when the open record button is clicked",
|
|
1187
|
+
"parameters": [
|
|
1188
|
+
{
|
|
1189
|
+
"name": "entityName",
|
|
1190
|
+
"description": "Name of the entity to open",
|
|
1191
|
+
"type": "string"
|
|
1192
|
+
}
|
|
1193
|
+
]
|
|
1194
|
+
}
|
|
1195
|
+
],
|
|
1196
|
+
"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/>",
|
|
1197
|
+
"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}",
|
|
1198
|
+
"dependencies": [
|
|
1199
|
+
{
|
|
1200
|
+
"name": "OpenRecordButton",
|
|
1201
|
+
"location": "registry",
|
|
1202
|
+
"namespace": "Generic/Navigation",
|
|
1203
|
+
"version": "^1.0.0"
|
|
1204
|
+
}
|
|
1205
|
+
],
|
|
1206
|
+
"libraries": []
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
"name": "EntityFilter",
|
|
1210
|
+
"title": "Entity Filter Panel",
|
|
1211
|
+
"description": "Collapsible filter panel for filtering entities by various criteria",
|
|
1212
|
+
"type": "form",
|
|
1213
|
+
"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",
|
|
1214
|
+
"dataRequirements": {
|
|
1215
|
+
"mode": "views",
|
|
1216
|
+
"description": "Receives filter options derived from Entities metadata",
|
|
1217
|
+
"entities": [
|
|
1218
|
+
{
|
|
1219
|
+
"name": "Entities",
|
|
1220
|
+
"description": "Source of schema and table filter options",
|
|
1221
|
+
"displayFields": [
|
|
1222
|
+
"SchemaName",
|
|
1223
|
+
"BaseTable"
|
|
1224
|
+
],
|
|
1225
|
+
"filterFields": [],
|
|
1226
|
+
"sortFields": [],
|
|
1227
|
+
"fieldMetadata": [],
|
|
1228
|
+
"permissionLevelNeeded": [
|
|
1229
|
+
"read"
|
|
1230
|
+
],
|
|
1231
|
+
"usageContext": "Extracts unique schema names and base tables for filter dropdowns"
|
|
1232
|
+
}
|
|
1233
|
+
],
|
|
1234
|
+
"queries": []
|
|
1235
|
+
},
|
|
1236
|
+
"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",
|
|
1237
|
+
"properties": [
|
|
1238
|
+
{
|
|
1239
|
+
"name": "filters",
|
|
1240
|
+
"description": "Current filter values",
|
|
1241
|
+
"type": "{schema?: string, table?: string, search?: string}",
|
|
1242
|
+
"required": true
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
"name": "onFilterChange",
|
|
1246
|
+
"description": "Callback when filters change",
|
|
1247
|
+
"type": "(filters: {schema?: string, table?: string, search?: string}) => void",
|
|
1248
|
+
"required": true
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
"name": "schemas",
|
|
1252
|
+
"description": "Available schema options",
|
|
1253
|
+
"type": "Array<string>",
|
|
1254
|
+
"required": true
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
"name": "tables",
|
|
1258
|
+
"description": "Available table options",
|
|
1259
|
+
"type": "Array<string>",
|
|
1260
|
+
"required": true
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
"name": "isCollapsed",
|
|
1264
|
+
"description": "Whether the panel is collapsed",
|
|
1265
|
+
"type": "boolean",
|
|
1266
|
+
"required": true
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
"name": "onToggleCollapse",
|
|
1270
|
+
"description": "Callback to toggle collapse state",
|
|
1271
|
+
"type": "() => void",
|
|
1272
|
+
"required": true
|
|
1273
|
+
}
|
|
1274
|
+
],
|
|
1275
|
+
"events": [
|
|
1276
|
+
{
|
|
1277
|
+
"name": "onFilterChange",
|
|
1278
|
+
"description": "Fired when filter values change",
|
|
1279
|
+
"parameters": [
|
|
1280
|
+
{
|
|
1281
|
+
"name": "filters",
|
|
1282
|
+
"description": "Updated filter object",
|
|
1283
|
+
"type": "object"
|
|
1284
|
+
}
|
|
1285
|
+
]
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
"name": "onToggleCollapse",
|
|
1289
|
+
"description": "Fired when collapse state should toggle",
|
|
1290
|
+
"parameters": []
|
|
1291
|
+
}
|
|
1292
|
+
],
|
|
1293
|
+
"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/>",
|
|
1294
|
+
"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}",
|
|
1295
|
+
"dependencies": [],
|
|
1296
|
+
"libraries": []
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
"name": "OpenRecordButton",
|
|
1300
|
+
"location": "registry",
|
|
1301
|
+
"namespace": "Generic/Navigation",
|
|
1302
|
+
"version": "^1.0.0"
|
|
1303
|
+
}
|
|
1304
|
+
],
|
|
1305
|
+
"libraries": [],
|
|
1306
|
+
"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}"
|
|
1307
|
+
};
|
|
1308
|
+
//# sourceMappingURL=skip-agent.js.map
|