@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.
- package/dist/artifacts/registry.d.ts +4 -3
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +122 -12
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/artifacts/types.d.ts +1 -1
- package/dist/artifacts/types.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/injectDevTools.d.ts.map +1 -1
- package/dist/injectDevTools.js +4 -2
- package/dist/injectDevTools.js.map +1 -1
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +3 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +3 -2
- package/templates/grid-starter/ARCHITECTURE.md +66 -0
- package/templates/grid-starter/README.md +122 -0
- package/templates/grid-starter/env.example +16 -0
- package/templates/grid-starter/gitignore +6 -0
- package/templates/grid-starter/index.html +16 -0
- package/templates/grid-starter/package.json +39 -0
- package/templates/grid-starter/src/App.tsx +23 -0
- package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
- package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
- package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
- package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
- package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
- package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
- package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
- package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
- package/templates/grid-starter/src/index.tsx +18 -0
- package/templates/grid-starter/src/vite-env.d.ts +15 -0
- package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/grid-starter/tsconfig.json +19 -0
- package/templates/grid-starter/vite.config.ts +76 -0
- package/templates/pcf-dataset/package.json +3 -1
- package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
- package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
- package/templates/pcf-field/index.ts +1 -1
- package/templates/pcf-field/package.json +3 -1
- package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
- package/templates/react-custom-page/ARCHITECTURE.md +75 -0
- package/templates/react-custom-page/README.md +74 -568
- package/templates/react-custom-page/env.example +16 -0
- package/templates/react-custom-page/gitignore +1 -0
- package/templates/react-custom-page/index.html +16 -0
- package/templates/react-custom-page/package.json +21 -49
- package/templates/react-custom-page/src/App.tsx +26 -0
- package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
- package/templates/react-custom-page/src/core/recordContext.ts +51 -0
- package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
- package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
- package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
- package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
- package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
- package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
- package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
- package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
- package/templates/react-custom-page/src/domain/diff.ts +38 -0
- package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
- package/templates/react-custom-page/src/example/exampleError.ts +36 -0
- package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
- package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
- package/templates/react-custom-page/src/example/models/Account.ts +74 -0
- package/templates/react-custom-page/src/index.tsx +18 -128
- package/templates/react-custom-page/src/vite-env.d.ts +15 -0
- package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/react-custom-page/tsconfig.json +12 -22
- package/templates/react-custom-page/vite.config.ts +76 -0
- package/templates/starter-page/README.md +38 -0
- package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
- package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
- package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
- package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
- package/templates/starter-page/gitignore +5 -0
- package/templates/starter-page/package.json +27 -0
- package/templates/starter-page/public/index.html +11 -0
- package/templates/starter-page/src/index.tsx +10 -0
- package/templates/starter-page/src/services/dataverse.ts +30 -0
- package/templates/starter-page/tsconfig.json +15 -0
- package/templates/starter-page/webpack.config.js +17 -0
- package/templates/react-custom-page/deployment/README.md +0 -484
- package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
- package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
- package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
- package/templates/react-custom-page/public/index.html +0 -15
- package/templates/react-custom-page/scripts/custom-build.js +0 -255
- package/templates/react-custom-page/src/components/AccountForm.css +0 -71
- package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
- package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
- package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
- package/templates/react-custom-page/src/components/ContactForm.css +0 -48
- package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
- package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
- package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
- package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
- package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
- package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
- package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
- package/templates/react-custom-page/src/constants/account.ts +0 -410
- package/templates/react-custom-page/src/constants/contact.ts +0 -362
- package/templates/react-custom-page/src/models/Account.ts +0 -480
- package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
- package/templates/react-custom-page/src/models/Contact.ts +0 -580
- package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
- package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
- package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
- package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
- package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
- package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
- package/templates/react-custom-page/src/styles/index.css +0 -171
- package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
- package/templates/react-custom-page/webpack.config.js +0 -57
- /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
|
-
};
|