@khester/create-dynamics-app 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +74 -0
  2. package/dist/artifacts/registry.d.ts +18 -0
  3. package/dist/artifacts/registry.d.ts.map +1 -0
  4. package/dist/artifacts/registry.js +340 -0
  5. package/dist/artifacts/registry.js.map +1 -0
  6. package/dist/artifacts/types.d.ts +122 -0
  7. package/dist/artifacts/types.d.ts.map +1 -0
  8. package/dist/artifacts/types.js +7 -0
  9. package/dist/artifacts/types.js.map +1 -0
  10. package/dist/artifacts/validators.d.ts +16 -0
  11. package/dist/artifacts/validators.d.ts.map +1 -0
  12. package/dist/artifacts/validators.js +45 -0
  13. package/dist/artifacts/validators.js.map +1 -0
  14. package/dist/fromDesign.d.ts +5 -0
  15. package/dist/fromDesign.d.ts.map +1 -0
  16. package/dist/fromDesign.js +98 -0
  17. package/dist/fromDesign.js.map +1 -0
  18. package/dist/index.js +129 -177
  19. package/dist/index.js.map +1 -1
  20. package/dist/injectDevTools.d.ts +28 -0
  21. package/dist/injectDevTools.d.ts.map +1 -0
  22. package/dist/injectDevTools.js +148 -0
  23. package/dist/injectDevTools.js.map +1 -0
  24. package/dist/scaffold.d.ts +48 -0
  25. package/dist/scaffold.d.ts.map +1 -0
  26. package/dist/scaffold.js +180 -0
  27. package/dist/scaffold.js.map +1 -0
  28. package/dist/templatePlan.d.ts +3 -0
  29. package/dist/templatePlan.d.ts.map +1 -0
  30. package/dist/templatePlan.js +43 -0
  31. package/dist/templatePlan.js.map +1 -0
  32. package/dist/utils/copyTemplate.d.ts +13 -1
  33. package/dist/utils/copyTemplate.d.ts.map +1 -1
  34. package/dist/utils/copyTemplate.js +98 -4
  35. package/dist/utils/copyTemplate.js.map +1 -1
  36. package/dist/utils/updatePackageJson.d.ts +11 -1
  37. package/dist/utils/updatePackageJson.d.ts.map +1 -1
  38. package/dist/utils/updatePackageJson.js +12 -10
  39. package/dist/utils/updatePackageJson.js.map +1 -1
  40. package/package.json +10 -7
  41. package/templates/_shared/dev-tools/auth/get-token.js +72 -0
  42. package/templates/_shared/dev-tools/dev/mock-xrm.js +42 -0
  43. package/templates/_shared/dev-tools/metadata-sync/index.js +152 -0
  44. package/templates/_shared/dev-tools/smoke/test-retrieve.js +44 -0
  45. package/templates/dialog-form/README.md +27 -0
  46. package/templates/dialog-form/_variants/App.v8.tsx +39 -0
  47. package/templates/dialog-form/_variants/App.v9.tsx +41 -0
  48. package/templates/dialog-form/gitignore +5 -0
  49. package/templates/dialog-form/package.json +27 -0
  50. package/templates/dialog-form/public/index.html +11 -0
  51. package/templates/dialog-form/src/index.tsx +10 -0
  52. package/templates/dialog-form/src/services/dataverse.ts +30 -0
  53. package/templates/dialog-form/tsconfig.json +15 -0
  54. package/templates/dialog-form/webpack.config.js +17 -0
  55. package/templates/grid-customizer/README.md +28 -0
  56. package/templates/grid-customizer/gitignore +4 -0
  57. package/templates/grid-customizer/package.json +25 -0
  58. package/templates/grid-customizer/src/GridCustomizer.ts +28 -0
  59. package/templates/grid-customizer/src/cell-renderers.tsx +35 -0
  60. package/templates/grid-customizer/src/index.ts +4 -0
  61. package/templates/grid-customizer/src/types/grid-types.ts +30 -0
  62. package/templates/grid-customizer/src/utils/color-utils.ts +24 -0
  63. package/templates/grid-customizer/tsconfig.json +15 -0
  64. package/templates/grid-customizer/webpack.config.js +17 -0
  65. package/templates/pcf-dataset/ControlManifest.Input.xml +16 -0
  66. package/templates/pcf-dataset/README.md +21 -0
  67. package/templates/pcf-dataset/gitignore +5 -0
  68. package/templates/pcf-dataset/index.ts +39 -0
  69. package/templates/pcf-dataset/package.json +30 -0
  70. package/templates/pcf-dataset/strings/{{componentName}}.1033.resx +47 -0
  71. package/templates/pcf-dataset/tsconfig.json +8 -0
  72. package/templates/pcf-dataset/{{componentName}}Component.tsx +39 -0
  73. package/templates/pcf-field/ControlManifest.Input.xml +17 -0
  74. package/templates/pcf-field/README.md +95 -0
  75. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +24 -0
  76. package/templates/pcf-field/_variants/ValueInput.date.tsx +27 -0
  77. package/templates/pcf-field/_variants/ValueInput.number.tsx +35 -0
  78. package/templates/pcf-field/_variants/ValueInput.text.tsx +27 -0
  79. package/templates/pcf-field/gitignore +5 -0
  80. package/templates/pcf-field/index.ts +61 -0
  81. package/templates/pcf-field/package.json +30 -0
  82. package/templates/pcf-field/strings/{{componentName}}.1033.resx +47 -0
  83. package/templates/pcf-field/tsconfig.json +8 -0
  84. package/templates/pcf-field/{{componentName}}Component.tsx +35 -0
  85. package/templates/power-pages-starter/gitignore +5 -0
  86. package/templates/react-custom-page/gitignore +5 -0
  87. package/templates/{dynamics-365-starter → react-custom-page}/package.json +3 -3
  88. package/templates/react-custom-page/tools/metadata-sync/index.js +152 -0
  89. package/templates/static-web-app/README.md +36 -0
  90. package/templates/static-web-app/_variants/App.v8.tsx +32 -0
  91. package/templates/static-web-app/_variants/App.v9.tsx +31 -0
  92. package/templates/static-web-app/api/host.json +12 -0
  93. package/templates/static-web-app/api/package.json +19 -0
  94. package/templates/static-web-app/api/src/functions/hello.ts +16 -0
  95. package/templates/static-web-app/api/tsconfig.json +14 -0
  96. package/templates/static-web-app/frontend/index.html +12 -0
  97. package/templates/static-web-app/frontend/package.json +23 -0
  98. package/templates/static-web-app/frontend/src/index.tsx +8 -0
  99. package/templates/static-web-app/frontend/tsconfig.json +16 -0
  100. package/templates/static-web-app/frontend/vite.config.ts +13 -0
  101. package/templates/static-web-app/gitignore +8 -0
  102. package/templates/static-web-app/package.json +15 -0
  103. package/templates/static-web-app/staticwebapp.config.json +7 -0
  104. package/templates/teams-app/README.md +27 -0
  105. package/templates/teams-app/_variants/graph.off.ts +7 -0
  106. package/templates/teams-app/_variants/graph.on.ts +22 -0
  107. package/templates/teams-app/appPackage/manifest.json +26 -0
  108. package/templates/teams-app/gitignore +5 -0
  109. package/templates/teams-app/index.html +12 -0
  110. package/templates/teams-app/package.json +26 -0
  111. package/templates/teams-app/src/App.tsx +25 -0
  112. package/templates/teams-app/src/index.tsx +8 -0
  113. package/templates/teams-app/tsconfig.json +16 -0
  114. package/templates/teams-app/vite.config.ts +9 -0
  115. package/templates/web-resource/README.md +39 -0
  116. package/templates/web-resource/_variants/App.v8.tsx +29 -0
  117. package/templates/web-resource/_variants/App.v9.tsx +28 -0
  118. package/templates/web-resource/gitignore +5 -0
  119. package/templates/web-resource/package.json +27 -0
  120. package/templates/web-resource/public/index.html +11 -0
  121. package/templates/web-resource/src/index.tsx +10 -0
  122. package/templates/web-resource/src/services/dataverse.ts +30 -0
  123. package/templates/web-resource/tsconfig.json +15 -0
  124. package/templates/web-resource/webpack.config.js +17 -0
  125. package/dist/utils/consultingHelpers.d.ts +0 -13
  126. package/dist/utils/consultingHelpers.d.ts.map +0 -1
  127. package/dist/utils/consultingHelpers.js +0 -569
  128. package/dist/utils/consultingHelpers.js.map +0 -1
  129. package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +0 -302
  130. package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +0 -305
  131. package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +0 -507
  132. package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +0 -372
  133. package/templates/dynamics-365-starter/deployment/pipelines/README.md +0 -375
  134. package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +0 -330
  135. package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +0 -422
  136. package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +0 -636
  137. package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +0 -417
  138. package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +0 -582
  139. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +0 -486
  140. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +0 -567
  141. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +0 -703
  142. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +0 -671
  143. package/templates/dynamics-365-starter/docs/team-standards/README.md +0 -273
  144. package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +0 -577
  145. package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +0 -359
  146. package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +0 -700
  147. package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +0 -736
  148. package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +0 -727
  149. package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +0 -758
  150. package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +0 -878
  151. package/templates/dynamics-365-starter/src/client-project-template/README.md +0 -234
  152. package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +0 -114
  153. package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +0 -186
  154. package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +0 -667
  155. package/templates/dynamics-365-starter/src/examples/README.md +0 -52
  156. package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +0 -625
  157. package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +0 -545
  158. package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +0 -722
  159. package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +0 -662
  160. package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +0 -519
  161. package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +0 -456
  162. package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +0 -406
  163. package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +0 -578
  164. package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +0 -629
  165. package/templates/dynamics-365-starter/tools/entity-generator/index.js +0 -168
  166. package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +0 -124
  167. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +0 -283
  168. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +0 -275
  169. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +0 -204
  170. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +0 -413
  171. package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +0 -250
  172. package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +0 -410
  173. package/templates/dynamics-365-starter/tools/metadata-sync/index.js +0 -512
  174. package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +0 -675
  175. /package/templates/{dynamics-365-starter → react-custom-page}/README.md +0 -0
  176. /package/templates/{dynamics-365-starter → react-custom-page}/deployment/README.md +0 -0
  177. /package/templates/{dynamics-365-starter → react-custom-page}/docs/ARCHITECTURE_OVERVIEW.md +0 -0
  178. /package/templates/{dynamics-365-starter → react-custom-page}/docs/BEST_PRACTICES.md +0 -0
  179. /package/templates/{dynamics-365-starter → react-custom-page}/docs/MIGRATION_GUIDE.md +0 -0
  180. /package/templates/{dynamics-365-starter → react-custom-page}/public/index.html +0 -0
  181. /package/templates/{dynamics-365-starter → react-custom-page}/scripts/custom-build.js +0 -0
  182. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.css +0 -0
  183. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.tsx +0 -0
  184. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.css +0 -0
  185. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.tsx +0 -0
  186. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.css +0 -0
  187. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.tsx +0 -0
  188. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.css +0 -0
  189. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.tsx +0 -0
  190. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LogDialog.tsx +0 -0
  191. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingContext.tsx +0 -0
  192. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.css +0 -0
  193. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.tsx +0 -0
  194. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingProvider.tsx +0 -0
  195. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/logger.ts +0 -0
  196. /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/account.ts +0 -0
  197. /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/contact.ts +0 -0
  198. /package/templates/{dynamics-365-starter → react-custom-page}/src/index.tsx +0 -0
  199. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Account.ts +0 -0
  200. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/BaseEntity.ts +0 -0
  201. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Contact.ts +0 -0
  202. /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/ContactControlWrapper.tsx +0 -0
  203. /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/MultiEntityControlWrapper.tsx +0 -0
  204. /package/templates/{dynamics-365-starter → react-custom-page}/src/providers/DynamicsProvider.tsx +0 -0
  205. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/MockApiService.ts +0 -0
  206. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/ServiceFactory.ts +0 -0
  207. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/XrmApiService.ts +0 -0
  208. /package/templates/{dynamics-365-starter → react-custom-page}/src/styles/index.css +0 -0
  209. /package/templates/{dynamics-365-starter → react-custom-page}/tsconfig.json +0 -0
  210. /package/templates/{dynamics-365-starter → react-custom-page}/webpack.config.js +0 -0
@@ -1,406 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
- import {
3
- DetailsList,
4
- IColumn,
5
- SelectionMode,
6
- CommandBar,
7
- ICommandBarItemProps,
8
- MessageBar,
9
- MessageBarType,
10
- Spinner,
11
- SpinnerSize,
12
- SearchBox,
13
- Stack,
14
- Text,
15
- Panel,
16
- PanelType,
17
- DefaultButton,
18
- PrimaryButton,
19
- } from '@fluentui/react';
20
- import { BaseEntity } from '../models/BaseEntity';
21
- import { useDynamicsApi } from '../providers/DynamicsProvider';
22
- import { Logger } from '../components/Logging/logger';
23
-
24
- interface EntityListPageProps<T extends BaseEntity> {
25
- title: string;
26
- entityLogicalName: string;
27
- entityClass: any; // Constructor for the entity
28
- columns: IColumn[];
29
- fetchXmlTemplate: string;
30
- maxRecords?: number;
31
- enableSearch?: boolean;
32
- searchFields?: string[];
33
- onItemSelected?: (item: T) => void;
34
- onNew?: () => void;
35
- onEdit?: (item: T) => void;
36
- onDelete?: (item: T) => void;
37
- customCommands?: ICommandBarItemProps[];
38
- renderCustomPanel?: (
39
- item: T | null,
40
- onDismiss: () => void
41
- ) => React.ReactNode;
42
- }
43
-
44
- /**
45
- * Generic entity list page template for Dynamics 365
46
- * Supports any entity with CRUD operations, search, and custom actions
47
- */
48
- export function EntityListPage<T extends BaseEntity>({
49
- title,
50
- entityLogicalName,
51
- entityClass,
52
- columns,
53
- fetchXmlTemplate,
54
- maxRecords = 100,
55
- enableSearch = true,
56
- searchFields = [],
57
- onItemSelected,
58
- onNew,
59
- onEdit,
60
- onDelete,
61
- customCommands = [],
62
- renderCustomPanel,
63
- }: EntityListPageProps<T>) {
64
- const { apiService } = useDynamicsApi();
65
- const [entities, setEntities] = useState<T[]>([]);
66
- const [filteredEntities, setFilteredEntities] = useState<T[]>([]);
67
- const [loading, setLoading] = useState(true);
68
- const [error, setError] = useState<string | null>(null);
69
- const [selectedItem, setSelectedItem] = useState<T | null>(null);
70
- const [searchQuery, setSearchQuery] = useState('');
71
- const [showPanel, setShowPanel] = useState(false);
72
- const [refreshTrigger, setRefreshTrigger] = useState(0);
73
-
74
- // Load entities
75
- const loadEntities = useCallback(async () => {
76
- if (!apiService) {
77
- setError('API service not available');
78
- setLoading(false);
79
- return;
80
- }
81
-
82
- try {
83
- setLoading(true);
84
- setError(null);
85
- Logger.log(`Loading ${title}...`);
86
-
87
- const topClause = maxRecords ? `top="${maxRecords}"` : '';
88
- const fetchXml = fetchXmlTemplate.replace('{{topClause}}', topClause);
89
-
90
- const result = await apiService.retrieveMultipleRecords(
91
- entityLogicalName,
92
- fetchXml
93
- );
94
- const entityInstances = (result.entities || []).map(
95
- (data: any) => new entityClass(data)
96
- );
97
-
98
- setEntities(entityInstances);
99
- setFilteredEntities(entityInstances);
100
- Logger.log(`Loaded ${entityInstances.length} ${title}`);
101
- } catch (err) {
102
- const errorMessage =
103
- err instanceof Error ? err.message : `Failed to load ${title}`;
104
- setError(errorMessage);
105
- Logger.error(`Error loading ${title}:`, 'EntityListPage', err);
106
- } finally {
107
- setLoading(false);
108
- }
109
- }, [
110
- apiService,
111
- title,
112
- entityLogicalName,
113
- fetchXmlTemplate,
114
- maxRecords,
115
- entityClass,
116
- ]);
117
-
118
- useEffect(() => {
119
- loadEntities();
120
- }, [loadEntities, refreshTrigger]);
121
-
122
- // Handle search
123
- useEffect(() => {
124
- if (!enableSearch || !searchQuery.trim()) {
125
- setFilteredEntities(entities);
126
- return;
127
- }
128
-
129
- const filtered = entities.filter((entity) => {
130
- if (searchFields.length === 0) {
131
- // Search all string fields if no specific fields specified
132
- return Object.values(entity).some(
133
- (value) =>
134
- typeof value === 'string' &&
135
- value.toLowerCase().includes(searchQuery.toLowerCase())
136
- );
137
- }
138
-
139
- // Search specific fields
140
- return searchFields.some((field) => {
141
- const value = (entity as any)[field];
142
- return (
143
- typeof value === 'string' &&
144
- value.toLowerCase().includes(searchQuery.toLowerCase())
145
- );
146
- });
147
- });
148
-
149
- setFilteredEntities(filtered);
150
- }, [entities, searchQuery, enableSearch, searchFields]);
151
-
152
- // Handle item selection
153
- const handleItemClick = (item: T) => {
154
- setSelectedItem(item);
155
- if (onItemSelected) {
156
- onItemSelected(item);
157
- }
158
- };
159
-
160
- // Handle refresh
161
- const handleRefresh = () => {
162
- setRefreshTrigger((prev) => prev + 1);
163
- };
164
-
165
- // Handle new entity
166
- const handleNew = () => {
167
- if (onNew) {
168
- onNew();
169
- } else if (renderCustomPanel) {
170
- setSelectedItem(null);
171
- setShowPanel(true);
172
- }
173
- };
174
-
175
- // Handle edit entity
176
- const handleEdit = () => {
177
- if (selectedItem) {
178
- if (onEdit) {
179
- onEdit(selectedItem);
180
- } else if (renderCustomPanel) {
181
- setShowPanel(true);
182
- }
183
- }
184
- };
185
-
186
- // Handle delete entity
187
- const handleDelete = async () => {
188
- if (!selectedItem || !apiService) {
189
- setError('Selected item or API service not available');
190
- return;
191
- }
192
-
193
- if (onDelete) {
194
- onDelete(selectedItem);
195
- } else {
196
- try {
197
- // Use the entity class static method or apiService directly
198
- if (typeof (selectedItem as any).delete === 'function') {
199
- await (selectedItem as any).delete(apiService);
200
- } else {
201
- // Fallback to direct API call - need to get the entity ID
202
- const entityId =
203
- (selectedItem as any).id ||
204
- (selectedItem as any)[`${entityLogicalName}id`];
205
- if (entityId) {
206
- await apiService.deleteRecord(entityLogicalName, entityId);
207
- } else {
208
- throw new Error('Could not determine entity ID for deletion');
209
- }
210
- }
211
-
212
- setSelectedItem(null);
213
- handleRefresh();
214
- Logger.log(`${title} item deleted successfully`);
215
- } catch (err) {
216
- const errorMessage =
217
- err instanceof Error ? err.message : `Failed to delete ${title} item`;
218
- setError(errorMessage);
219
- Logger.error(`Error deleting ${title} item:`, 'EntityListPage', err);
220
- }
221
- }
222
- };
223
-
224
- // Command bar items
225
- const commandBarItems: ICommandBarItemProps[] = [
226
- {
227
- key: 'new',
228
- text: `New ${title}`,
229
- iconProps: { iconName: 'Add' },
230
- onClick: handleNew,
231
- },
232
- {
233
- key: 'edit',
234
- text: 'Edit',
235
- iconProps: { iconName: 'Edit' },
236
- disabled: !selectedItem,
237
- onClick: handleEdit,
238
- },
239
- {
240
- key: 'delete',
241
- text: 'Delete',
242
- iconProps: { iconName: 'Delete' },
243
- disabled: !selectedItem,
244
- onClick: handleDelete,
245
- },
246
- {
247
- key: 'refresh',
248
- text: 'Refresh',
249
- iconProps: { iconName: 'Refresh' },
250
- onClick: handleRefresh,
251
- },
252
- ...customCommands,
253
- ];
254
-
255
- // Enhanced columns with click handlers
256
- const enhancedColumns = columns.map((column) => ({
257
- ...column,
258
- onRender:
259
- column.onRender ||
260
- ((item: T) => {
261
- const value = (item as any)[column.fieldName || ''];
262
- if (column.key === columns[0].key) {
263
- // Make first column clickable
264
- return (
265
- <span
266
- style={{ color: '#0078d4', cursor: 'pointer', fontWeight: 500 }}
267
- onClick={() => handleItemClick(item)}
268
- >
269
- {value || ''}
270
- </span>
271
- );
272
- }
273
- return <span>{value || ''}</span>;
274
- }),
275
- }));
276
-
277
- return (
278
- <div style={{ padding: '20px', maxWidth: '100%' }}>
279
- <Stack tokens={{ childrenGap: 20 }}>
280
- {/* Header */}
281
- <Stack
282
- horizontal
283
- horizontalAlign="space-between"
284
- verticalAlign="center"
285
- >
286
- <Text variant="xxLarge" style={{ fontWeight: 600, color: '#323130' }}>
287
- {title}
288
- </Text>
289
- <Text variant="medium" style={{ color: '#605e5c' }}>
290
- {filteredEntities.length} items
291
- </Text>
292
- </Stack>
293
-
294
- {/* Error Message */}
295
- {error && (
296
- <MessageBar
297
- messageBarType={MessageBarType.error}
298
- onDismiss={() => setError(null)}
299
- >
300
- {error}
301
- </MessageBar>
302
- )}
303
-
304
- {/* Search and Commands */}
305
- <Stack horizontal tokens={{ childrenGap: 16 }}>
306
- {enableSearch && (
307
- <SearchBox
308
- placeholder={`Search ${title}...`}
309
- value={searchQuery}
310
- onChange={(_, newValue) => setSearchQuery(newValue || '')}
311
- style={{ maxWidth: 300 }}
312
- />
313
- )}
314
- <CommandBar items={commandBarItems} style={{ flex: 1 }} />
315
- </Stack>
316
-
317
- {/* Content */}
318
- {loading ? (
319
- <div
320
- style={{
321
- display: 'flex',
322
- justifyContent: 'center',
323
- alignItems: 'center',
324
- minHeight: 200,
325
- flexDirection: 'column',
326
- }}
327
- >
328
- <Spinner size={SpinnerSize.large} label={`Loading ${title}...`} />
329
- </div>
330
- ) : filteredEntities.length === 0 ? (
331
- <div
332
- style={{
333
- textAlign: 'center',
334
- padding: '40px 20px',
335
- color: '#605e5c',
336
- }}
337
- >
338
- <div style={{ fontSize: 48, marginBottom: 16, color: '#a19f9d' }}>
339
- 📋
340
- </div>
341
- <Text
342
- variant="large"
343
- style={{ fontWeight: 600, marginBottom: 8, color: '#323130' }}
344
- >
345
- {searchQuery ? 'No matching items found' : `No ${title} found`}
346
- </Text>
347
- <Text variant="medium">
348
- {searchQuery
349
- ? `Try adjusting your search criteria`
350
- : `Click "New ${title}" to get started`}
351
- </Text>
352
- </div>
353
- ) : (
354
- <DetailsList
355
- items={filteredEntities}
356
- columns={enhancedColumns}
357
- setKey="set"
358
- layoutMode={0}
359
- selectionMode={SelectionMode.single}
360
- onActiveItemChanged={setSelectedItem}
361
- isHeaderVisible={true}
362
- />
363
- )}
364
-
365
- {/* Custom Panel */}
366
- {renderCustomPanel && (
367
- <Panel
368
- headerText={selectedItem ? `Edit ${title}` : `New ${title}`}
369
- isOpen={showPanel}
370
- onDismiss={() => setShowPanel(false)}
371
- type={PanelType.medium}
372
- closeButtonAriaLabel="Close"
373
- >
374
- {renderCustomPanel(selectedItem, () => {
375
- setShowPanel(false);
376
- handleRefresh();
377
- })}
378
- </Panel>
379
- )}
380
- </Stack>
381
- </div>
382
- );
383
- }
384
-
385
- // Helper function to create standard FetchXML template
386
- export function createStandardFetchXml(
387
- entityName: string,
388
- attributes: string[],
389
- orderBy?: string,
390
- filter?: string
391
- ): string {
392
- const attributeElements = attributes
393
- .map((attr) => `<attribute name="${attr}" />`)
394
- .join('\n ');
395
- const orderElement = orderBy ? `<order attribute="${orderBy}" />` : '';
396
- const filterElement = filter ? `<filter>${filter}</filter>` : '';
397
-
398
- return `
399
- <fetch {{topClause}}>
400
- <entity name="${entityName}">
401
- ${attributeElements}
402
- ${orderElement}
403
- ${filterElement}
404
- </entity>
405
- </fetch>`;
406
- }