@khester/create-dynamics-app 2.1.0 → 2.3.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 (122) hide show
  1. package/dist/artifacts/registry.d.ts +4 -3
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +122 -12
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/artifacts/types.d.ts +1 -1
  6. package/dist/artifacts/types.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/injectDevTools.d.ts.map +1 -1
  10. package/dist/injectDevTools.js +4 -2
  11. package/dist/injectDevTools.js.map +1 -1
  12. package/dist/scaffold.d.ts +1 -0
  13. package/dist/scaffold.d.ts.map +1 -1
  14. package/dist/scaffold.js +3 -1
  15. package/dist/scaffold.js.map +1 -1
  16. package/package.json +3 -2
  17. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  18. package/templates/grid-starter/README.md +122 -0
  19. package/templates/grid-starter/env.example +16 -0
  20. package/templates/grid-starter/gitignore +6 -0
  21. package/templates/grid-starter/index.html +16 -0
  22. package/templates/grid-starter/package.json +39 -0
  23. package/templates/grid-starter/src/App.tsx +23 -0
  24. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  25. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  26. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  27. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  28. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  29. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  30. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  31. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  32. package/templates/grid-starter/src/index.tsx +18 -0
  33. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  34. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  35. package/templates/grid-starter/tsconfig.json +19 -0
  36. package/templates/grid-starter/vite.config.ts +76 -0
  37. package/templates/pcf-dataset/package.json +3 -1
  38. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  41. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  42. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  43. package/templates/pcf-field/index.ts +1 -1
  44. package/templates/pcf-field/package.json +3 -1
  45. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  46. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  47. package/templates/react-custom-page/README.md +74 -568
  48. package/templates/react-custom-page/env.example +16 -0
  49. package/templates/react-custom-page/gitignore +1 -0
  50. package/templates/react-custom-page/index.html +16 -0
  51. package/templates/react-custom-page/package.json +21 -49
  52. package/templates/react-custom-page/src/App.tsx +26 -0
  53. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  54. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  55. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  56. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  57. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  58. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  59. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  60. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  61. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  62. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  63. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  64. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  65. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  67. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  69. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  70. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  71. package/templates/react-custom-page/src/index.tsx +18 -128
  72. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  73. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  74. package/templates/react-custom-page/tsconfig.json +12 -22
  75. package/templates/react-custom-page/vite.config.ts +76 -0
  76. package/templates/starter-page/README.md +38 -0
  77. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  78. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  79. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  80. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  81. package/templates/starter-page/gitignore +5 -0
  82. package/templates/starter-page/package.json +27 -0
  83. package/templates/starter-page/public/index.html +11 -0
  84. package/templates/starter-page/src/index.tsx +10 -0
  85. package/templates/starter-page/src/services/dataverse.ts +30 -0
  86. package/templates/starter-page/tsconfig.json +15 -0
  87. package/templates/starter-page/webpack.config.js +17 -0
  88. package/templates/react-custom-page/deployment/README.md +0 -484
  89. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  90. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  91. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  92. package/templates/react-custom-page/public/index.html +0 -15
  93. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  94. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  95. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  96. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  97. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  98. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  99. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  100. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  101. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  102. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  103. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  105. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  106. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  107. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  108. package/templates/react-custom-page/src/constants/account.ts +0 -410
  109. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  110. package/templates/react-custom-page/src/models/Account.ts +0 -480
  111. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  112. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  113. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  114. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  115. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  116. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  117. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  118. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  119. package/templates/react-custom-page/src/styles/index.css +0 -171
  120. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  121. package/templates/react-custom-page/webpack.config.js +0 -57
  122. /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
@@ -1,205 +0,0 @@
1
- import React, { useState, useCallback, useEffect } from 'react';
2
- import { Pivot, DynamicsPivotItemProps } from '@khester/dynamics-ui-components';
3
- import { ContactManagement } from '../components/ContactManagement';
4
- import { AccountManagement } from '../components/AccountManagement';
5
- import { DynamicsProvider } from '../providers/DynamicsProvider';
6
- import { ServiceFactory } from '../services/ServiceFactory';
7
- import { Logger } from '../components/Logging/logger';
8
-
9
- interface PCFContextType {
10
- // Define PCF context properties based on your needs
11
- webAPI: any;
12
- utils: any;
13
- parameters: any;
14
- }
15
-
16
- interface MultiEntityControlWrapperProps {
17
- context: PCFContextType;
18
- defaultEntity?: 'contact' | 'account';
19
- showTabs?: boolean;
20
- }
21
-
22
- /**
23
- * Multi-entity wrapper component for integrating both ContactManagement and AccountManagement
24
- * with PCF (PowerApps Component Framework). This allows switching between entity management
25
- * within a single custom control in Dynamics 365.
26
- */
27
- export const MultiEntityControlWrapper: React.FC<
28
- MultiEntityControlWrapperProps
29
- > = ({ context, defaultEntity = 'contact', showTabs = true }) => {
30
- const [selectedEntity, setSelectedEntity] = useState<'contact' | 'account'>(
31
- defaultEntity
32
- );
33
-
34
- // Extract configuration from PCF context
35
- const baseUrl = context.parameters?.baseUrl?.raw || '';
36
-
37
- useEffect(() => {
38
- Logger.userAction(
39
- 'MultiEntityControlWrapper initialized',
40
- { defaultEntity, showTabs, selectedEntity },
41
- 'MultiEntityControlWrapper'
42
- );
43
- }, [defaultEntity, showTabs, selectedEntity]);
44
-
45
- // Create API service using ServiceFactory for environment detection
46
- const createPCFApiService = () => {
47
- try {
48
- // Check if we're in PCF context with webAPI available
49
- if (context.webAPI) {
50
- Logger.log(
51
- 'PCF WebAPI available, creating custom PCF API service',
52
- 'MultiEntityControlWrapper'
53
- );
54
-
55
- // Create a custom API service that uses PCF's webAPI
56
- return {
57
- createRecord: async (entityName: string, data: any) => {
58
- Logger.debug(
59
- `Creating ${entityName} record`,
60
- 'MultiEntityControlWrapper.createRecord',
61
- data
62
- );
63
- return await context.webAPI.createRecord(entityName, data);
64
- },
65
- retrieveRecord: async (
66
- entityName: string,
67
- id: string,
68
- select?: string
69
- ) => {
70
- Logger.debug(
71
- `Retrieving ${entityName} record: ${id}`,
72
- 'MultiEntityControlWrapper.retrieveRecord'
73
- );
74
- return await context.webAPI.retrieveRecord(entityName, id, select);
75
- },
76
- updateRecord: async (entityName: string, id: string, data: any) => {
77
- Logger.debug(
78
- `Updating ${entityName} record: ${id}`,
79
- 'MultiEntityControlWrapper.updateRecord',
80
- data
81
- );
82
- return await context.webAPI.updateRecord(entityName, id, data);
83
- },
84
- deleteRecord: async (entityName: string, id: string) => {
85
- Logger.debug(
86
- `Deleting ${entityName} record: ${id}`,
87
- 'MultiEntityControlWrapper.deleteRecord'
88
- );
89
- return await context.webAPI.deleteRecord(entityName, id);
90
- },
91
- retrieveMultipleRecords: async (
92
- entityName: string,
93
- fetchXml: string
94
- ) => {
95
- Logger.debug(
96
- `Retrieving multiple ${entityName} records`,
97
- 'MultiEntityControlWrapper.retrieveMultipleRecords'
98
- );
99
- return await context.webAPI.retrieveMultipleRecords(
100
- entityName,
101
- `?fetchXml=${encodeURIComponent(fetchXml)}`
102
- );
103
- },
104
- executeRequest: async (requestName: string, _requestData: any) => {
105
- Logger.log(
106
- `Executing request: ${requestName}`,
107
- 'MultiEntityControlWrapper.executeRequest'
108
- );
109
- throw new Error('Custom requests not supported in PCF WebAPI');
110
- },
111
- uploadFile: async (_file: File) => {
112
- Logger.log(
113
- 'File upload requested',
114
- 'MultiEntityControlWrapper.uploadFile'
115
- );
116
- throw new Error('File upload not supported in PCF WebAPI');
117
- },
118
- };
119
- }
120
-
121
- // Fallback to ServiceFactory for environment detection
122
- const xrmGlobal = ServiceFactory.isDynamics365Context()
123
- ? (window as any).Xrm
124
- : undefined;
125
- return ServiceFactory.createApiService(xrmGlobal);
126
- } catch (error) {
127
- Logger.error(
128
- 'Failed to create API service in PCF context',
129
- 'MultiEntityControlWrapper',
130
- error
131
- );
132
- throw error;
133
- }
134
- };
135
-
136
- const handleEntityChange = useCallback(
137
- (item: DynamicsPivotItemProps) => {
138
- const newEntity = item.itemKey as 'contact' | 'account';
139
- Logger.userAction(
140
- 'Entity tab changed',
141
- { from: selectedEntity, to: newEntity },
142
- 'MultiEntityControlWrapper.handleEntityChange'
143
- );
144
- setSelectedEntity(newEntity);
145
- },
146
- [selectedEntity]
147
- );
148
-
149
- const renderEntityManagement = () => {
150
- switch (selectedEntity) {
151
- case 'contact':
152
- return <ContactManagement />;
153
- case 'account':
154
- return <AccountManagement />;
155
- default:
156
- return <ContactManagement />;
157
- }
158
- };
159
-
160
- const containerStyle: React.CSSProperties = {
161
- width: '100%',
162
- height: '100%',
163
- display: 'flex',
164
- flexDirection: 'column',
165
- };
166
-
167
- const contentStyle: React.CSSProperties = {
168
- flex: 1,
169
- overflow: 'auto',
170
- };
171
-
172
- return (
173
- <DynamicsProvider customApiService={createPCFApiService()}>
174
- <div style={containerStyle}>
175
- {showTabs ? (
176
- <>
177
- <Pivot
178
- items={[
179
- {
180
- headerText: 'Contacts',
181
- itemKey: 'contact',
182
- itemIcon: 'Contact',
183
- },
184
- {
185
- headerText: 'Accounts',
186
- itemKey: 'account',
187
- itemIcon: 'Building',
188
- },
189
- ]}
190
- selectedKey={selectedEntity}
191
- onItemClick={handleEntityChange}
192
- linkFormat="tabs"
193
- />
194
- <div style={contentStyle}>{renderEntityManagement()}</div>
195
- </>
196
- ) : (
197
- <div style={contentStyle}>{renderEntityManagement()}</div>
198
- )}
199
- </div>
200
- </DynamicsProvider>
201
- );
202
- };
203
-
204
- // Export for PCF integration
205
- export default MultiEntityControlWrapper;
@@ -1,353 +0,0 @@
1
- import React, { createContext, useContext, useState, useEffect } from 'react';
2
- import { IApiService } from '@khester/dynamics-ui-api-client';
3
- import { ServiceFactory } from '../services/ServiceFactory';
4
- import { Logger } from '../components/Logging/logger';
5
-
6
- interface DynamicsContextType {
7
- apiService: IApiService | null;
8
- createRecord: (entityName: string, data: any) => Promise<any>;
9
- retrieveRecord: (
10
- entityName: string,
11
- id: string,
12
- select?: string
13
- ) => Promise<any>;
14
- updateRecord: (entityName: string, id: string, data: any) => Promise<any>;
15
- deleteRecord: (entityName: string, id: string) => Promise<void>;
16
- retrieveMultiple: (entityName: string, query?: string) => Promise<any>;
17
- isEnvironmentMock: boolean;
18
- environmentType: 'mock' | 'production';
19
- }
20
-
21
- const DynamicsContext = createContext<DynamicsContextType | undefined>(
22
- undefined
23
- );
24
-
25
- interface DynamicsProviderProps {
26
- children: React.ReactNode;
27
- /** Optional Xrm object for Dynamics 365 environment (will be auto-detected if not provided) */
28
- xrm?: any;
29
- /** Optional custom API service for PCF or other integrations */
30
- customApiService?: IApiService;
31
- }
32
-
33
- export const DynamicsProvider: React.FC<DynamicsProviderProps> = ({
34
- children,
35
- xrm,
36
- customApiService,
37
- }) => {
38
- const [apiService, setApiService] = useState<IApiService | null>(null);
39
- const [environmentType, setEnvironmentType] = useState<'mock' | 'production'>(
40
- 'mock'
41
- );
42
-
43
- useEffect(() => {
44
- try {
45
- Logger.log(
46
- 'DynamicsProvider: Initializing API service',
47
- 'DynamicsProvider'
48
- );
49
-
50
- // Use custom API service if provided (for PCF integration)
51
- if (customApiService) {
52
- Logger.log(
53
- 'Using custom API service provided via props',
54
- 'DynamicsProvider'
55
- );
56
- setApiService(customApiService);
57
- setEnvironmentType('production'); // Assume production for custom services
58
- return;
59
- }
60
-
61
- // Detect Xrm object if not provided
62
- const xrmObject =
63
- xrm || (typeof window !== 'undefined' && (window as any).Xrm);
64
-
65
- // Use ServiceFactory to create the appropriate service
66
- const service = ServiceFactory.createApiService(xrmObject);
67
- const envType = ServiceFactory.getEnvironmentType();
68
-
69
- setApiService(service);
70
- setEnvironmentType(envType);
71
-
72
- Logger.log(
73
- `DynamicsProvider: Initialized with ${envType} environment`,
74
- 'DynamicsProvider'
75
- );
76
-
77
- if (envType === 'production' && !ServiceFactory.isDynamics365Context()) {
78
- Logger.warn(
79
- 'Running in production mode but Xrm object may not be available',
80
- 'DynamicsProvider'
81
- );
82
- }
83
- } catch (error) {
84
- Logger.error(
85
- 'Failed to initialize API service',
86
- 'DynamicsProvider',
87
- error
88
- );
89
-
90
- // Fallback to mock service for development
91
- try {
92
- const mockService = ServiceFactory.createApiService();
93
- setApiService(mockService);
94
- setEnvironmentType('mock');
95
- Logger.warn(
96
- 'Falling back to mock service due to initialization error',
97
- 'DynamicsProvider'
98
- );
99
- } catch (fallbackError) {
100
- Logger.error(
101
- 'Failed to initialize fallback mock service',
102
- 'DynamicsProvider',
103
- fallbackError
104
- );
105
- }
106
- }
107
- }, [xrm, customApiService]);
108
-
109
- const createRecord = async (entityName: string, data: any) => {
110
- if (!apiService) {
111
- const error = 'API service not initialized';
112
- Logger.error(error, 'DynamicsProvider.createRecord');
113
- throw new Error(error);
114
- }
115
-
116
- try {
117
- Logger.apiOperation(
118
- 'CREATE',
119
- entityName,
120
- data,
121
- 'DynamicsProvider.createRecord'
122
- );
123
- const result = await apiService.createRecord(entityName, data);
124
- Logger.log(
125
- `Successfully created ${entityName} record`,
126
- 'DynamicsProvider.createRecord'
127
- );
128
- return result;
129
- } catch (error) {
130
- Logger.error(
131
- `Failed to create ${entityName} record`,
132
- 'DynamicsProvider.createRecord',
133
- error
134
- );
135
- throw error;
136
- }
137
- };
138
-
139
- const retrieveRecord = async (
140
- entityName: string,
141
- id: string,
142
- select?: string
143
- ) => {
144
- if (!apiService) {
145
- const error = 'API service not initialized';
146
- Logger.error(error, 'DynamicsProvider.retrieveRecord');
147
- throw new Error(error);
148
- }
149
-
150
- try {
151
- Logger.apiOperation(
152
- 'READ',
153
- entityName,
154
- { id, select },
155
- 'DynamicsProvider.retrieveRecord'
156
- );
157
-
158
- // Use retrieveMultipleRecords with FetchXML to get a single record
159
- const selectAttributes = select
160
- ? select.split(',').map((attr) => attr.trim())
161
- : ['*'];
162
- const attributes = selectAttributes
163
- .map((attr) => (attr === '*' ? '' : `<attribute name="${attr}" />`))
164
- .join('');
165
- const fetchXml = `
166
- <fetch top="1">
167
- <entity name="${entityName}">
168
- ${attributes}
169
- <filter>
170
- <condition attribute="${entityName}id" operator="eq" value="${id}" />
171
- </filter>
172
- </entity>
173
- </fetch>
174
- `;
175
-
176
- Logger.fetchXml(fetchXml, 'DynamicsProvider.retrieveRecord');
177
- const result = await apiService.retrieveMultipleRecords(
178
- entityName,
179
- fetchXml
180
- );
181
- const record = result.entities.length > 0 ? result.entities[0] : null;
182
-
183
- Logger.log(
184
- `Retrieved ${entityName} record: ${record ? 'found' : 'not found'}`,
185
- 'DynamicsProvider.retrieveRecord'
186
- );
187
-
188
- return record;
189
- } catch (error) {
190
- Logger.error(
191
- `Failed to retrieve ${entityName} record`,
192
- 'DynamicsProvider.retrieveRecord',
193
- error
194
- );
195
- throw error;
196
- }
197
- };
198
-
199
- const updateRecord = async (entityName: string, id: string, data: any) => {
200
- if (!apiService) {
201
- const error = 'API service not initialized';
202
- Logger.error(error, 'DynamicsProvider.updateRecord');
203
- throw new Error(error);
204
- }
205
-
206
- try {
207
- Logger.apiOperation(
208
- 'UPDATE',
209
- entityName,
210
- { id, data },
211
- 'DynamicsProvider.updateRecord'
212
- );
213
- const result = await apiService.updateRecord(entityName, id, data);
214
- Logger.log(
215
- `Successfully updated ${entityName} record`,
216
- 'DynamicsProvider.updateRecord'
217
- );
218
- return result;
219
- } catch (error) {
220
- Logger.error(
221
- `Failed to update ${entityName} record`,
222
- 'DynamicsProvider.updateRecord',
223
- error
224
- );
225
- throw error;
226
- }
227
- };
228
-
229
- const deleteRecord = async (entityName: string, id: string) => {
230
- if (!apiService) {
231
- const error = 'API service not initialized';
232
- Logger.error(error, 'DynamicsProvider.deleteRecord');
233
- throw new Error(error);
234
- }
235
-
236
- try {
237
- Logger.apiOperation(
238
- 'DELETE',
239
- entityName,
240
- { id },
241
- 'DynamicsProvider.deleteRecord'
242
- );
243
- await apiService.deleteRecord(entityName, id);
244
- Logger.log(
245
- `Successfully deleted ${entityName} record`,
246
- 'DynamicsProvider.deleteRecord'
247
- );
248
- } catch (error) {
249
- Logger.error(
250
- `Failed to delete ${entityName} record`,
251
- 'DynamicsProvider.deleteRecord',
252
- error
253
- );
254
- throw error;
255
- }
256
- };
257
-
258
- const retrieveMultiple = async (entityName: string, query?: string) => {
259
- if (!apiService) {
260
- const error = 'API service not initialized';
261
- Logger.error(error, 'DynamicsProvider.retrieveMultiple');
262
- throw new Error(error);
263
- }
264
-
265
- try {
266
- Logger.apiOperation(
267
- 'QUERY',
268
- entityName,
269
- query,
270
- 'DynamicsProvider.retrieveMultiple'
271
- );
272
-
273
- // Convert OData-style query to FetchXML
274
- let fetchXml = `<fetch>`;
275
-
276
- if (query) {
277
- // Parse basic OData query parameters
278
- const selectMatch = query.match(/\$select=([^&]*)/i);
279
- const orderByMatch = query.match(/\$orderby=([^&]*)/i);
280
- const topMatch = query.match(/\$top=(\d+)/i);
281
-
282
- if (topMatch) {
283
- fetchXml = `<fetch top="${topMatch[1]}">`;
284
- }
285
-
286
- fetchXml += `<entity name="${entityName}">`;
287
-
288
- if (selectMatch) {
289
- const attributes = selectMatch[1]
290
- .split(',')
291
- .map((attr) => attr.trim());
292
- attributes.forEach((attr) => {
293
- fetchXml += `<attribute name="${attr}" />`;
294
- });
295
- }
296
-
297
- if (orderByMatch) {
298
- const [field, direction] = orderByMatch[1].split(' ');
299
- fetchXml += `<order attribute="${field.trim()}" descending="${direction?.toLowerCase() === 'desc'}" />`;
300
- }
301
-
302
- fetchXml += `</entity></fetch>`;
303
- } else {
304
- fetchXml = `<fetch><entity name="${entityName}"></entity></fetch>`;
305
- }
306
-
307
- Logger.fetchXml(fetchXml, 'DynamicsProvider.retrieveMultiple');
308
- const result = await apiService.retrieveMultipleRecords(
309
- entityName,
310
- fetchXml
311
- );
312
-
313
- Logger.log(
314
- `Retrieved ${result.entities.length} ${entityName} records`,
315
- 'DynamicsProvider.retrieveMultiple'
316
- );
317
-
318
- return result;
319
- } catch (error) {
320
- Logger.error(
321
- `Failed to retrieve ${entityName} records`,
322
- 'DynamicsProvider.retrieveMultiple',
323
- error
324
- );
325
- throw error;
326
- }
327
- };
328
-
329
- const value: DynamicsContextType = {
330
- apiService,
331
- createRecord,
332
- retrieveRecord,
333
- updateRecord,
334
- deleteRecord,
335
- retrieveMultiple,
336
- isEnvironmentMock: environmentType === 'mock',
337
- environmentType,
338
- };
339
-
340
- return (
341
- <DynamicsContext.Provider value={value}>
342
- {children}
343
- </DynamicsContext.Provider>
344
- );
345
- };
346
-
347
- export const useDynamicsApi = (): DynamicsContextType => {
348
- const context = useContext(DynamicsContext);
349
- if (context === undefined) {
350
- throw new Error('useDynamicsApi must be used within a DynamicsProvider');
351
- }
352
- return context;
353
- };