@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,722 +0,0 @@
1
- /**
2
- * Example: Creating a custom PCF wrapper
3
- *
4
- * This example demonstrates how to create a sophisticated PCF wrapper
5
- * that can be used in various Dynamics 365 contexts, with smart API
6
- * service detection and comprehensive error handling.
7
- *
8
- * @example
9
- * ```typescript
10
- * // Use in your PCF component's init method
11
- * export class CustomPCFControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
12
- * public init(context: ComponentFramework.Context<IInputs>): void {
13
- * ReactDOM.render(
14
- * React.createElement(CustomPCFWrapper, {
15
- * context,
16
- * entityType: 'contact',
17
- * enableLogging: true
18
- * }),
19
- * this.container
20
- * );
21
- * }
22
- * }
23
- * ```
24
- */
25
-
26
- import React, { useState, useCallback, useEffect, useMemo } from 'react';
27
- import { ContactManagement } from '../../components/ContactManagement';
28
- import { AccountManagement } from '../../components/AccountManagement';
29
- import { DynamicsProvider } from '../../providers/DynamicsProvider';
30
- import { ServiceFactory } from '../../services/ServiceFactory';
31
- import { Logger } from '../../components/Logging/logger';
32
- import { LoggingProvider } from '../../components/Logging/LoggingProvider';
33
-
34
- /**
35
- * PCF Context interface that matches ComponentFramework.Context
36
- */
37
- interface PCFContextType {
38
- /** PCF WebAPI object for Dynamics 365 operations */
39
- webAPI: {
40
- createRecord: (entityName: string, data: any) => Promise<any>;
41
- retrieveRecord: (
42
- entityName: string,
43
- id: string,
44
- select?: string
45
- ) => Promise<any>;
46
- updateRecord: (entityName: string, id: string, data: any) => Promise<any>;
47
- deleteRecord: (entityName: string, id: string) => Promise<void>;
48
- retrieveMultipleRecords: (
49
- entityName: string,
50
- query?: string
51
- ) => Promise<any>;
52
- };
53
- /** PCF utility functions */
54
- utils: {
55
- lookupObjects: (lookupOptions: any) => Promise<any>;
56
- openAlertDialog: (
57
- alertStrings: any,
58
- useShowModal?: boolean
59
- ) => Promise<any>;
60
- openConfirmDialog: (
61
- confirmStrings: any,
62
- useShowModal?: boolean
63
- ) => Promise<any>;
64
- openErrorDialog: (errorOptions: any) => Promise<any>;
65
- openFileDialog: (fileOptions: any) => Promise<any>;
66
- openUrl: (url: string, options?: any) => void;
67
- };
68
- /** PCF parameters */
69
- parameters: {
70
- [key: string]: any;
71
- };
72
- /** PCF mode information */
73
- mode: {
74
- isControlDisabled: boolean;
75
- isVisible: boolean;
76
- label: string;
77
- };
78
- /** PCF resources */
79
- resources: {
80
- getString: (id: string) => string;
81
- getResource: (
82
- id: string,
83
- success: (data: string) => void,
84
- failure?: () => void
85
- ) => void;
86
- };
87
- }
88
-
89
- /**
90
- * Props for the CustomPCFWrapper component
91
- */
92
- interface CustomPCFWrapperProps {
93
- /** PCF context from the framework */
94
- context: PCFContextType;
95
- /** Entity type to manage ('contact' | 'account' | 'auto') */
96
- entityType?: 'contact' | 'account' | 'auto';
97
- /** Whether to enable comprehensive logging */
98
- enableLogging?: boolean;
99
- /** Whether to show tabs for multi-entity support */
100
- showTabs?: boolean;
101
- /** Custom title for the component */
102
- title?: string;
103
- /** Whether to enable debug mode */
104
- debugMode?: boolean;
105
- }
106
-
107
- /**
108
- * Custom PCF Wrapper Component
109
- *
110
- * A sophisticated wrapper that provides:
111
- * - Smart API service creation with fallback handling
112
- * - Environment detection and logging integration
113
- * - Multi-entity support with dynamic switching
114
- * - Error boundary and recovery mechanisms
115
- * - Performance monitoring and optimization
116
- *
117
- * @param props - Component properties
118
- * @returns JSX.Element
119
- */
120
- export const CustomPCFWrapper: React.FC<CustomPCFWrapperProps> = ({
121
- context,
122
- entityType = 'auto',
123
- enableLogging = true,
124
- showTabs = true,
125
- title,
126
- debugMode = false,
127
- }) => {
128
- const [selectedEntity, setSelectedEntity] = useState<'contact' | 'account'>(
129
- 'contact'
130
- );
131
- const [apiServiceReady, setApiServiceReady] = useState(false);
132
- const [error, setError] = useState<string | null>(null);
133
-
134
- /**
135
- * Initialize logging if enabled
136
- */
137
- useEffect(() => {
138
- if (enableLogging) {
139
- Logger.log('CustomPCFWrapper initialized', 'CustomPCFWrapper');
140
- Logger.userAction(
141
- 'PCF component loaded',
142
- {
143
- entityType,
144
- showTabs,
145
- debugMode,
146
- hasWebAPI: !!context.webAPI,
147
- },
148
- 'CustomPCFWrapper'
149
- );
150
- }
151
- }, [enableLogging, entityType, showTabs, debugMode, context.webAPI]);
152
-
153
- /**
154
- * Create API service with comprehensive error handling and fallbacks
155
- */
156
- const apiService = useMemo(() => {
157
- try {
158
- // Check if we're in PCF context with webAPI available
159
- if (context.webAPI) {
160
- if (enableLogging) {
161
- Logger.log(
162
- 'PCF WebAPI available, creating custom PCF API service',
163
- 'CustomPCFWrapper'
164
- );
165
- }
166
-
167
- // Create a comprehensive API service that uses PCF's webAPI
168
- return {
169
- /**
170
- * Create a new record using PCF WebAPI
171
- */
172
- createRecord: async (entityName: string, data: any) => {
173
- try {
174
- if (enableLogging) {
175
- Logger.apiOperation(
176
- 'CREATE',
177
- entityName,
178
- data,
179
- 'CustomPCFWrapper.createRecord'
180
- );
181
- }
182
- const result = await context.webAPI.createRecord(
183
- entityName,
184
- data
185
- );
186
- if (enableLogging) {
187
- Logger.log(
188
- `Successfully created ${entityName} record`,
189
- 'CustomPCFWrapper.createRecord'
190
- );
191
- }
192
- return result;
193
- } catch (error) {
194
- if (enableLogging) {
195
- Logger.error(
196
- `Failed to create ${entityName} record`,
197
- 'CustomPCFWrapper.createRecord',
198
- error
199
- );
200
- }
201
- throw error;
202
- }
203
- },
204
-
205
- /**
206
- * Retrieve a single record by ID
207
- */
208
- retrieveRecord: async (
209
- entityName: string,
210
- id: string,
211
- select?: string
212
- ) => {
213
- try {
214
- if (enableLogging) {
215
- Logger.apiOperation(
216
- 'READ',
217
- entityName,
218
- { id, select },
219
- 'CustomPCFWrapper.retrieveRecord'
220
- );
221
- }
222
- const result = await context.webAPI.retrieveRecord(
223
- entityName,
224
- id,
225
- select
226
- );
227
- if (enableLogging) {
228
- Logger.log(
229
- `Successfully retrieved ${entityName} record`,
230
- 'CustomPCFWrapper.retrieveRecord'
231
- );
232
- }
233
- return result;
234
- } catch (error) {
235
- if (enableLogging) {
236
- Logger.error(
237
- `Failed to retrieve ${entityName} record`,
238
- 'CustomPCFWrapper.retrieveRecord',
239
- error
240
- );
241
- }
242
- throw error;
243
- }
244
- },
245
-
246
- /**
247
- * Update an existing record
248
- */
249
- updateRecord: async (entityName: string, id: string, data: any) => {
250
- try {
251
- if (enableLogging) {
252
- Logger.apiOperation(
253
- 'UPDATE',
254
- entityName,
255
- { id, data },
256
- 'CustomPCFWrapper.updateRecord'
257
- );
258
- }
259
- const result = await context.webAPI.updateRecord(
260
- entityName,
261
- id,
262
- data
263
- );
264
- if (enableLogging) {
265
- Logger.log(
266
- `Successfully updated ${entityName} record`,
267
- 'CustomPCFWrapper.updateRecord'
268
- );
269
- }
270
- return result;
271
- } catch (error) {
272
- if (enableLogging) {
273
- Logger.error(
274
- `Failed to update ${entityName} record`,
275
- 'CustomPCFWrapper.updateRecord',
276
- error
277
- );
278
- }
279
- throw error;
280
- }
281
- },
282
-
283
- /**
284
- * Delete a record
285
- */
286
- deleteRecord: async (entityName: string, id: string) => {
287
- try {
288
- if (enableLogging) {
289
- Logger.apiOperation(
290
- 'DELETE',
291
- entityName,
292
- { id },
293
- 'CustomPCFWrapper.deleteRecord'
294
- );
295
- }
296
- await context.webAPI.deleteRecord(entityName, id);
297
- if (enableLogging) {
298
- Logger.log(
299
- `Successfully deleted ${entityName} record`,
300
- 'CustomPCFWrapper.deleteRecord'
301
- );
302
- }
303
- } catch (error) {
304
- if (enableLogging) {
305
- Logger.error(
306
- `Failed to delete ${entityName} record`,
307
- 'CustomPCFWrapper.deleteRecord',
308
- error
309
- );
310
- }
311
- throw error;
312
- }
313
- },
314
-
315
- /**
316
- * Retrieve multiple records using FetchXML
317
- */
318
- retrieveMultipleRecords: async (
319
- entityName: string,
320
- fetchXml: string
321
- ) => {
322
- try {
323
- if (enableLogging) {
324
- Logger.fetchXml(
325
- fetchXml,
326
- 'CustomPCFWrapper.retrieveMultipleRecords'
327
- );
328
- }
329
- const encodedFetchXml = encodeURIComponent(fetchXml);
330
- const result = await context.webAPI.retrieveMultipleRecords(
331
- entityName,
332
- `?fetchXml=${encodedFetchXml}`
333
- );
334
- if (enableLogging) {
335
- Logger.log(
336
- `Successfully retrieved ${result.entities?.length || 0} ${entityName} records`,
337
- 'CustomPCFWrapper.retrieveMultipleRecords'
338
- );
339
- }
340
- return result;
341
- } catch (error) {
342
- if (enableLogging) {
343
- Logger.error(
344
- `Failed to retrieve ${entityName} records`,
345
- 'CustomPCFWrapper.retrieveMultipleRecords',
346
- error
347
- );
348
- }
349
- throw error;
350
- }
351
- },
352
-
353
- /**
354
- * Execute custom request (not supported in PCF WebAPI)
355
- */
356
- executeRequest: async (requestName: string, requestData: any) => {
357
- const errorMessage = `Custom requests not supported in PCF WebAPI: ${requestName}`;
358
- if (enableLogging) {
359
- Logger.warn(errorMessage, 'CustomPCFWrapper.executeRequest');
360
- }
361
- throw new Error(errorMessage);
362
- },
363
-
364
- /**
365
- * Upload file (not supported in PCF WebAPI)
366
- */
367
- uploadFile: async (file: File) => {
368
- const errorMessage = 'File upload not supported in PCF WebAPI';
369
- if (enableLogging) {
370
- Logger.warn(errorMessage, 'CustomPCFWrapper.uploadFile');
371
- }
372
- throw new Error(errorMessage);
373
- },
374
- };
375
- }
376
-
377
- // Fallback to ServiceFactory for environment detection
378
- if (enableLogging) {
379
- Logger.log(
380
- 'PCF WebAPI not available, falling back to ServiceFactory',
381
- 'CustomPCFWrapper'
382
- );
383
- }
384
-
385
- const xrmGlobal = ServiceFactory.isDynamics365Context()
386
- ? (window as any).Xrm
387
- : undefined;
388
- return ServiceFactory.createApiService(xrmGlobal);
389
- } catch (error) {
390
- const errorMessage = 'Failed to create API service in PCF context';
391
- if (enableLogging) {
392
- Logger.error(errorMessage, 'CustomPCFWrapper', error);
393
- }
394
- setError(errorMessage);
395
- return null;
396
- }
397
- }, [context.webAPI, enableLogging]);
398
-
399
- /**
400
- * Set API service ready state
401
- */
402
- useEffect(() => {
403
- setApiServiceReady(!!apiService);
404
- }, [apiService]);
405
-
406
- /**
407
- * Determine which entity to show based on entityType prop
408
- */
409
- useEffect(() => {
410
- if (entityType === 'auto') {
411
- // Auto-detect based on form context if available
412
- // This is a simplified example - in practice, you'd check context.parameters
413
- setSelectedEntity('contact');
414
- } else {
415
- setSelectedEntity(entityType);
416
- }
417
- }, [entityType]);
418
-
419
- /**
420
- * Handle entity tab change
421
- */
422
- const handleEntityChange = useCallback(
423
- (newEntity: 'contact' | 'account') => {
424
- if (enableLogging) {
425
- Logger.userAction(
426
- 'Entity tab changed in PCF wrapper',
427
- { from: selectedEntity, to: newEntity },
428
- 'CustomPCFWrapper.handleEntityChange'
429
- );
430
- }
431
- setSelectedEntity(newEntity);
432
- },
433
- [selectedEntity, enableLogging]
434
- );
435
-
436
- /**
437
- * Render the appropriate management component
438
- */
439
- const renderManagementComponent = () => {
440
- if (!apiServiceReady) {
441
- return (
442
- <div style={{ padding: '20px', textAlign: 'center' }}>
443
- <div>Loading...</div>
444
- {debugMode && (
445
- <div style={{ marginTop: '10px', fontSize: '12px', color: '#666' }}>
446
- Initializing API service...
447
- </div>
448
- )}
449
- </div>
450
- );
451
- }
452
-
453
- switch (selectedEntity) {
454
- case 'contact':
455
- return <ContactManagement />;
456
- case 'account':
457
- return <AccountManagement />;
458
- default:
459
- return <ContactManagement />;
460
- }
461
- };
462
-
463
- /**
464
- * Render error boundary
465
- */
466
- if (error) {
467
- return (
468
- <div
469
- style={{
470
- padding: '20px',
471
- border: '1px solid #d13438',
472
- borderRadius: '4px',
473
- backgroundColor: '#fef7f1',
474
- color: '#d13438',
475
- }}
476
- >
477
- <h3>PCF Integration Error</h3>
478
- <p>{error}</p>
479
- {debugMode && (
480
- <details style={{ marginTop: '10px' }}>
481
- <summary>Debug Information</summary>
482
- <pre style={{ fontSize: '12px', marginTop: '10px' }}>
483
- {JSON.stringify(
484
- {
485
- hasWebAPI: !!context.webAPI,
486
- hasUtils: !!context.utils,
487
- hasParameters: !!context.parameters,
488
- entityType,
489
- enableLogging,
490
- showTabs,
491
- },
492
- null,
493
- 2
494
- )}
495
- </pre>
496
- </details>
497
- )}
498
- </div>
499
- );
500
- }
501
-
502
- /**
503
- * Container styles
504
- */
505
- const containerStyle: React.CSSProperties = {
506
- width: '100%',
507
- height: '100%',
508
- display: 'flex',
509
- flexDirection: 'column',
510
- fontFamily: '"Segoe UI", Tahoma, Arial, sans-serif',
511
- };
512
-
513
- const tabContainerStyle: React.CSSProperties = {
514
- borderBottom: '1px solid #edebe9',
515
- backgroundColor: '#faf9f8',
516
- };
517
-
518
- const tabStyle: React.CSSProperties = {
519
- display: 'inline-block',
520
- padding: '8px 16px',
521
- cursor: 'pointer',
522
- borderBottom: '2px solid transparent',
523
- fontSize: '14px',
524
- };
525
-
526
- const activeTabStyle: React.CSSProperties = {
527
- ...tabStyle,
528
- borderBottomColor: '#0078d4',
529
- backgroundColor: 'white',
530
- fontWeight: '600',
531
- };
532
-
533
- const contentStyle: React.CSSProperties = {
534
- flex: 1,
535
- overflow: 'auto',
536
- backgroundColor: 'white',
537
- };
538
-
539
- return (
540
- <DynamicsProvider customApiService={apiService || undefined}>
541
- {enableLogging ? (
542
- <LoggingProvider>
543
- <div style={containerStyle}>
544
- {/* Title */}
545
- {title && (
546
- <div
547
- style={{
548
- padding: '12px 16px',
549
- borderBottom: '1px solid #edebe9',
550
- }}
551
- >
552
- <h3 style={{ margin: 0, fontSize: '16px', fontWeight: '600' }}>
553
- {title}
554
- </h3>
555
- </div>
556
- )}
557
-
558
- {/* Tabs */}
559
- {showTabs && (entityType === 'auto' || showTabs) && (
560
- <div style={tabContainerStyle}>
561
- <div
562
- style={
563
- selectedEntity === 'contact' ? activeTabStyle : tabStyle
564
- }
565
- onClick={() => handleEntityChange('contact')}
566
- >
567
- Contacts
568
- </div>
569
- <div
570
- style={
571
- selectedEntity === 'account' ? activeTabStyle : tabStyle
572
- }
573
- onClick={() => handleEntityChange('account')}
574
- >
575
- Accounts
576
- </div>
577
- </div>
578
- )}
579
-
580
- {/* Content */}
581
- <div style={contentStyle}>{renderManagementComponent()}</div>
582
-
583
- {/* Debug Info */}
584
- {debugMode && (
585
- <div
586
- style={{
587
- padding: '8px',
588
- borderTop: '1px solid #edebe9',
589
- backgroundColor: '#f3f2f1',
590
- fontSize: '11px',
591
- color: '#666',
592
- }}
593
- >
594
- Debug: {ServiceFactory.getEnvironmentType()} | API Ready:{' '}
595
- {apiServiceReady ? 'Yes' : 'No'}
596
- </div>
597
- )}
598
- </div>
599
- </LoggingProvider>
600
- ) : (
601
- <div style={containerStyle}>
602
- {/* Same structure but without LoggingProvider */}
603
- {title && (
604
- <div
605
- style={{
606
- padding: '12px 16px',
607
- borderBottom: '1px solid #edebe9',
608
- }}
609
- >
610
- <h3 style={{ margin: 0, fontSize: '16px', fontWeight: '600' }}>
611
- {title}
612
- </h3>
613
- </div>
614
- )}
615
-
616
- {showTabs && (
617
- <div style={tabContainerStyle}>
618
- <div
619
- style={selectedEntity === 'contact' ? activeTabStyle : tabStyle}
620
- onClick={() => handleEntityChange('contact')}
621
- >
622
- Contacts
623
- </div>
624
- <div
625
- style={selectedEntity === 'account' ? activeTabStyle : tabStyle}
626
- onClick={() => handleEntityChange('account')}
627
- >
628
- Accounts
629
- </div>
630
- </div>
631
- )}
632
-
633
- <div style={contentStyle}>{renderManagementComponent()}</div>
634
-
635
- {debugMode && (
636
- <div
637
- style={{
638
- padding: '8px',
639
- borderTop: '1px solid #edebe9',
640
- backgroundColor: '#f3f2f1',
641
- fontSize: '11px',
642
- color: '#666',
643
- }}
644
- >
645
- Debug: {ServiceFactory.getEnvironmentType()} | API Ready:{' '}
646
- {apiServiceReady ? 'Yes' : 'No'}
647
- </div>
648
- )}
649
- </div>
650
- )}
651
- </DynamicsProvider>
652
- );
653
- };
654
-
655
- /**
656
- * Export for PCF integration
657
- * This is the main export that should be used in PCF components
658
- */
659
- export default CustomPCFWrapper;
660
-
661
- /**
662
- * Example PCF Control Implementation
663
- *
664
- * This shows how to use the CustomPCFWrapper in a real PCF component.
665
- * Copy this pattern to your PCF project's index.ts file.
666
- */
667
- export const examplePCFImplementation = `
668
- import * as React from 'react';
669
- import * as ReactDOM from 'react-dom';
670
- import { CustomPCFWrapper } from './CustomPCFWrapper';
671
-
672
- export class CustomPCFControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
673
- private container: HTMLDivElement;
674
- private notifyOutputChanged: () => void;
675
-
676
- public init(
677
- context: ComponentFramework.Context<IInputs>,
678
- notifyOutputChanged: () => void,
679
- state: ComponentFramework.Dictionary,
680
- container: HTMLDivElement
681
- ): void {
682
- this.container = container;
683
- this.notifyOutputChanged = notifyOutputChanged;
684
-
685
- // Render the CustomPCFWrapper
686
- ReactDOM.render(
687
- React.createElement(CustomPCFWrapper, {
688
- context,
689
- entityType: 'auto', // or 'contact', 'account'
690
- enableLogging: true,
691
- showTabs: true,
692
- title: 'Customer Management',
693
- debugMode: false // Set to true for development
694
- }),
695
- this.container
696
- );
697
- }
698
-
699
- public updateView(context: ComponentFramework.Context<IInputs>): void {
700
- // Re-render if needed
701
- ReactDOM.render(
702
- React.createElement(CustomPCFWrapper, {
703
- context,
704
- entityType: 'auto',
705
- enableLogging: true,
706
- showTabs: true,
707
- title: 'Customer Management',
708
- debugMode: false
709
- }),
710
- this.container
711
- );
712
- }
713
-
714
- public getOutputs(): IOutputs {
715
- return {};
716
- }
717
-
718
- public destroy(): void {
719
- ReactDOM.unmountComponentAtNode(this.container);
720
- }
721
- }
722
- `;