@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,456 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import {
3
- Stack,
4
- Text,
5
- CommandBar,
6
- ICommandBarItemProps,
7
- MessageBar,
8
- MessageBarType,
9
- Spinner,
10
- SpinnerSize,
11
- Breadcrumb,
12
- IBreadcrumbItem,
13
- Panel,
14
- PanelType,
15
- Dialog,
16
- DialogType,
17
- DialogFooter,
18
- PrimaryButton,
19
- DefaultButton,
20
- Pivot,
21
- PivotItem,
22
- } from '@fluentui/react';
23
- import { BaseEntity } from '../models/BaseEntity';
24
- import { useDynamicsApi } from '../providers/DynamicsProvider';
25
- import { Logger } from '../components/Logging/logger';
26
-
27
- interface EntityDetailPageProps<T extends BaseEntity> {
28
- entityId: string;
29
- title: string;
30
- entityLogicalName: string;
31
- entityClass: any; // Constructor for the entity
32
- fetchXmlTemplate: string;
33
- renderForm: (
34
- entity: T | null,
35
- onSave: (entity: T) => void,
36
- readOnly?: boolean
37
- ) => React.ReactNode;
38
- renderRelatedEntities?: (entity: T) => React.ReactNode;
39
- renderAuditHistory?: (entity: T) => React.ReactNode;
40
- onBack?: () => void;
41
- onEdit?: (entity: T) => void;
42
- onDelete?: (entity: T) => void;
43
- customCommands?: ICommandBarItemProps[];
44
- breadcrumbItems?: IBreadcrumbItem[];
45
- enableTabs?: boolean;
46
- readOnly?: boolean;
47
- }
48
-
49
- /**
50
- * Generic entity detail page template for Dynamics 365
51
- * Supports viewing, editing, related entities, and audit history
52
- */
53
- export function EntityDetailPage<T extends BaseEntity>({
54
- entityId,
55
- title,
56
- entityLogicalName,
57
- entityClass,
58
- fetchXmlTemplate,
59
- renderForm,
60
- renderRelatedEntities,
61
- renderAuditHistory,
62
- onBack,
63
- onEdit,
64
- onDelete,
65
- customCommands = [],
66
- breadcrumbItems = [],
67
- enableTabs = true,
68
- readOnly = false,
69
- }: EntityDetailPageProps<T>) {
70
- const { apiService } = useDynamicsApi();
71
- const [entity, setEntity] = useState<T | null>(null);
72
- const [loading, setLoading] = useState(true);
73
- const [error, setError] = useState<string | null>(null);
74
- const [saving, setSaving] = useState(false);
75
- const [isEditing, setIsEditing] = useState(false);
76
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
77
- const [showEditPanel, setShowEditPanel] = useState(false);
78
- const [selectedTab, setSelectedTab] = useState('details');
79
-
80
- // Load entity
81
- const loadEntity = async () => {
82
- if (!apiService) {
83
- setError('API service not available');
84
- setLoading(false);
85
- return;
86
- }
87
-
88
- try {
89
- setLoading(true);
90
- setError(null);
91
- Logger.log(`Loading ${title} with ID: ${entityId}`);
92
-
93
- const fetchXml = fetchXmlTemplate.replace('{{entityId}}', entityId);
94
- const result = await apiService.retrieveMultipleRecords(
95
- entityLogicalName,
96
- fetchXml
97
- );
98
-
99
- if (result.entities && result.entities.length > 0) {
100
- const entityData = new entityClass(result.entities[0]);
101
- setEntity(entityData);
102
- Logger.log(`${title} loaded successfully`);
103
- } else {
104
- setError(`${title} not found`);
105
- Logger.warn(`${title} not found with ID: ${entityId}`);
106
- }
107
- } catch (err) {
108
- const errorMessage =
109
- err instanceof Error ? err.message : `Failed to load ${title}`;
110
- setError(errorMessage);
111
- Logger.error(`Error loading ${title}:`, 'EntityDetailPage', err);
112
- } finally {
113
- setLoading(false);
114
- }
115
- };
116
-
117
- useEffect(() => {
118
- if (entityId) {
119
- loadEntity();
120
- }
121
- }, [entityId]);
122
-
123
- // Handle save
124
- const handleSave = async (updatedEntity: T) => {
125
- if (!apiService) {
126
- setError('API service not available');
127
- return;
128
- }
129
-
130
- try {
131
- setSaving(true);
132
- Logger.log(`Saving ${title}...`);
133
-
134
- // Use the entity class static method or apiService directly
135
- if (typeof (updatedEntity as any).update === 'function') {
136
- await (updatedEntity as any).update(apiService);
137
- } else {
138
- // Fallback to direct API call
139
- const entityData = { ...updatedEntity };
140
- delete (entityData as any).id; // Remove id from update data
141
- await apiService.updateRecord(entityLogicalName, entityId, entityData);
142
- }
143
-
144
- setEntity(updatedEntity);
145
- setIsEditing(false);
146
- setShowEditPanel(false);
147
-
148
- Logger.log(`${title} saved successfully`);
149
- } catch (err) {
150
- const errorMessage =
151
- err instanceof Error ? err.message : `Failed to save ${title}`;
152
- setError(errorMessage);
153
- Logger.error(`Error saving ${title}:`, 'EntityDetailPage', err);
154
- } finally {
155
- setSaving(false);
156
- }
157
- };
158
-
159
- // Handle edit
160
- const handleEdit = () => {
161
- if (entity) {
162
- if (onEdit) {
163
- onEdit(entity);
164
- } else {
165
- setIsEditing(true);
166
- setShowEditPanel(true);
167
- }
168
- }
169
- };
170
-
171
- // Handle delete
172
- const handleDelete = async () => {
173
- if (!entity || !apiService) {
174
- setError('Entity or API service not available');
175
- return;
176
- }
177
-
178
- if (onDelete) {
179
- onDelete(entity);
180
- } else {
181
- try {
182
- // Use the entity class static method or apiService directly
183
- if (typeof (entity as any).delete === 'function') {
184
- await (entity as any).delete(apiService);
185
- } else {
186
- // Fallback to direct API call
187
- await apiService.deleteRecord(entityLogicalName, entityId);
188
- }
189
-
190
- setShowDeleteDialog(false);
191
- if (onBack) {
192
- onBack();
193
- }
194
- Logger.log(`${title} deleted successfully`);
195
- } catch (err) {
196
- const errorMessage =
197
- err instanceof Error ? err.message : `Failed to delete ${title}`;
198
- setError(errorMessage);
199
- Logger.error(`Error deleting ${title}:`, 'EntityDetailPage', err);
200
- }
201
- }
202
- };
203
-
204
- // Handle refresh
205
- const handleRefresh = () => {
206
- loadEntity();
207
- };
208
-
209
- // Command bar items
210
- const commandBarItems: ICommandBarItemProps[] = [
211
- {
212
- key: 'edit',
213
- text: 'Edit',
214
- iconProps: { iconName: 'Edit' },
215
- disabled: !entity || readOnly,
216
- onClick: handleEdit,
217
- },
218
- {
219
- key: 'delete',
220
- text: 'Delete',
221
- iconProps: { iconName: 'Delete' },
222
- disabled: !entity || readOnly,
223
- onClick: () => setShowDeleteDialog(true),
224
- },
225
- {
226
- key: 'refresh',
227
- text: 'Refresh',
228
- iconProps: { iconName: 'Refresh' },
229
- onClick: handleRefresh,
230
- },
231
- ...customCommands,
232
- ];
233
-
234
- // Far command bar items
235
- const farCommandBarItems: ICommandBarItemProps[] = onBack
236
- ? [
237
- {
238
- key: 'back',
239
- text: 'Back',
240
- iconProps: { iconName: 'Back' },
241
- onClick: onBack,
242
- },
243
- ]
244
- : [];
245
-
246
- // Default breadcrumb
247
- const defaultBreadcrumbItems: IBreadcrumbItem[] = [
248
- { text: title, key: 'list', onClick: onBack },
249
- {
250
- text: entity ? (entity as any).name || 'Details' : 'Loading...',
251
- key: 'details',
252
- isCurrentItem: true,
253
- },
254
- ];
255
-
256
- const finalBreadcrumbItems =
257
- breadcrumbItems.length > 0 ? breadcrumbItems : defaultBreadcrumbItems;
258
-
259
- if (loading) {
260
- return (
261
- <div
262
- style={{
263
- display: 'flex',
264
- justifyContent: 'center',
265
- alignItems: 'center',
266
- minHeight: 400,
267
- flexDirection: 'column',
268
- padding: '20px',
269
- }}
270
- >
271
- <Spinner size={SpinnerSize.large} label={`Loading ${title}...`} />
272
- </div>
273
- );
274
- }
275
-
276
- if (error || !entity) {
277
- return (
278
- <div style={{ padding: '20px' }}>
279
- <Stack tokens={{ childrenGap: 20 }}>
280
- {onBack && <Breadcrumb items={finalBreadcrumbItems} />}
281
-
282
- <MessageBar messageBarType={MessageBarType.error}>
283
- {error || `${title} not found`}
284
- </MessageBar>
285
-
286
- <div
287
- style={{
288
- textAlign: 'center',
289
- padding: '40px 20px',
290
- color: '#605e5c',
291
- }}
292
- >
293
- <div style={{ fontSize: 48, marginBottom: 16, color: '#d13438' }}>
294
- ⚠️
295
- </div>
296
- <Text
297
- variant="large"
298
- style={{ fontWeight: 600, marginBottom: 8, color: '#323130' }}
299
- >
300
- {title} Not Found
301
- </Text>
302
- <Text variant="medium">
303
- The requested {title.toLowerCase()} could not be found or you may
304
- not have permission to view it.
305
- </Text>
306
- {onBack && (
307
- <DefaultButton
308
- text="Go Back"
309
- onClick={onBack}
310
- style={{ marginTop: 20 }}
311
- />
312
- )}
313
- </div>
314
- </Stack>
315
- </div>
316
- );
317
- }
318
-
319
- return (
320
- <div style={{ padding: '20px', maxWidth: '100%' }}>
321
- <Stack tokens={{ childrenGap: 20 }}>
322
- {/* Breadcrumb */}
323
- {onBack && <Breadcrumb items={finalBreadcrumbItems} />}
324
-
325
- {/* Header */}
326
- <Stack
327
- horizontal
328
- horizontalAlign="space-between"
329
- verticalAlign="center"
330
- >
331
- <Stack>
332
- <Text
333
- variant="xxLarge"
334
- style={{ fontWeight: 600, color: '#323130' }}
335
- >
336
- {(entity as any).name || title}
337
- </Text>
338
- <Text variant="medium" style={{ color: '#605e5c' }}>
339
- {title} • {entityId}
340
- </Text>
341
- </Stack>
342
- </Stack>
343
-
344
- {/* Error Message */}
345
- {error && (
346
- <MessageBar
347
- messageBarType={MessageBarType.error}
348
- onDismiss={() => setError(null)}
349
- >
350
- {error}
351
- </MessageBar>
352
- )}
353
-
354
- {/* Command Bar */}
355
- <CommandBar items={commandBarItems} farItems={farCommandBarItems} />
356
-
357
- {/* Content */}
358
- {enableTabs && (renderRelatedEntities || renderAuditHistory) ? (
359
- <Pivot
360
- selectedKey={selectedTab}
361
- onLinkClick={(item) =>
362
- setSelectedTab(item?.props.itemKey || 'details')
363
- }
364
- >
365
- <PivotItem headerText="Details" itemKey="details">
366
- <div style={{ paddingTop: 20 }}>
367
- {renderForm(entity, handleSave, !isEditing)}
368
- </div>
369
- </PivotItem>
370
-
371
- {renderRelatedEntities && (
372
- <PivotItem headerText="Related" itemKey="related">
373
- <div style={{ paddingTop: 20 }}>
374
- {renderRelatedEntities(entity)}
375
- </div>
376
- </PivotItem>
377
- )}
378
-
379
- {renderAuditHistory && (
380
- <PivotItem headerText="History" itemKey="history">
381
- <div style={{ paddingTop: 20 }}>
382
- {renderAuditHistory(entity)}
383
- </div>
384
- </PivotItem>
385
- )}
386
- </Pivot>
387
- ) : (
388
- <div style={{ paddingTop: 20 }}>
389
- {renderForm(entity, handleSave, !isEditing)}
390
- </div>
391
- )}
392
-
393
- {/* Edit Panel */}
394
- <Panel
395
- headerText={`Edit ${title}`}
396
- isOpen={showEditPanel}
397
- onDismiss={() => {
398
- setShowEditPanel(false);
399
- setIsEditing(false);
400
- }}
401
- type={PanelType.medium}
402
- closeButtonAriaLabel="Close"
403
- >
404
- <div style={{ paddingTop: 20 }}>
405
- {renderForm(entity, handleSave, false)}
406
- </div>
407
- </Panel>
408
-
409
- {/* Delete Confirmation Dialog */}
410
- <Dialog
411
- hidden={!showDeleteDialog}
412
- onDismiss={() => setShowDeleteDialog(false)}
413
- dialogContentProps={{
414
- type: DialogType.normal,
415
- title: 'Confirm Delete',
416
- subText: `Are you sure you want to delete "${(entity as any).name || title}"? This action cannot be undone.`,
417
- }}
418
- >
419
- <DialogFooter>
420
- <PrimaryButton
421
- onClick={handleDelete}
422
- text="Delete"
423
- disabled={saving}
424
- />
425
- <DefaultButton
426
- onClick={() => setShowDeleteDialog(false)}
427
- text="Cancel"
428
- disabled={saving}
429
- />
430
- </DialogFooter>
431
- </Dialog>
432
- </Stack>
433
- </div>
434
- );
435
- }
436
-
437
- // Helper function to create detail FetchXML template
438
- export function createDetailFetchXml(
439
- entityName: string,
440
- attributes: string[],
441
- primaryIdAttribute: string = `${entityName}id`
442
- ): string {
443
- const attributeElements = attributes
444
- .map((attr) => `<attribute name="${attr}" />`)
445
- .join('\n ');
446
-
447
- return `
448
- <fetch top="1">
449
- <entity name="${entityName}">
450
- ${attributeElements}
451
- <filter>
452
- <condition attribute="${primaryIdAttribute}" operator="eq" value="{{entityId}}" />
453
- </filter>
454
- </entity>
455
- </fetch>`;
456
- }