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