@khester/create-dynamics-app 2.0.0 → 2.2.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/README.md +28 -0
  2. package/dist/artifacts/registry.d.ts +4 -3
  3. package/dist/artifacts/registry.d.ts.map +1 -1
  4. package/dist/artifacts/registry.js +145 -11
  5. package/dist/artifacts/registry.js.map +1 -1
  6. package/dist/artifacts/types.d.ts +10 -1
  7. package/dist/artifacts/types.d.ts.map +1 -1
  8. package/dist/index.js +19 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/injectDevTools.d.ts.map +1 -1
  11. package/dist/injectDevTools.js +4 -2
  12. package/dist/injectDevTools.js.map +1 -1
  13. package/dist/scaffold.d.ts +23 -1
  14. package/dist/scaffold.d.ts.map +1 -1
  15. package/dist/scaffold.js +27 -1
  16. package/dist/scaffold.js.map +1 -1
  17. package/package.json +3 -2
  18. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  19. package/templates/grid-starter/README.md +122 -0
  20. package/templates/grid-starter/env.example +16 -0
  21. package/templates/grid-starter/gitignore +6 -0
  22. package/templates/grid-starter/index.html +16 -0
  23. package/templates/grid-starter/package.json +39 -0
  24. package/templates/grid-starter/src/App.tsx +23 -0
  25. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  26. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  27. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  28. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  29. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  30. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  31. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  32. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  33. package/templates/grid-starter/src/index.tsx +18 -0
  34. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  35. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  36. package/templates/grid-starter/tsconfig.json +19 -0
  37. package/templates/grid-starter/vite.config.ts +76 -0
  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,580 +0,0 @@
1
- import { IApiService } from '@khester/dynamics-ui-api-client';
2
- import { BaseEntity } from './BaseEntity';
3
- import { ContactConstants } from '../constants/contact';
4
-
5
- /**
6
- * Interface for Contact entity
7
- */
8
- export interface IContact {
9
- contactid?: string;
10
- firstname?: string;
11
- lastname?: string;
12
- fullname?: string;
13
- middlename?: string;
14
- salutation?: string;
15
- suffix?: string;
16
- jobtitle?: string;
17
- emailaddress1?: string;
18
- emailaddress2?: string;
19
- emailaddress3?: string;
20
- telephone1?: string; // Business Phone
21
- telephone2?: string; // Home Phone
22
- mobilephone?: string;
23
- fax?: string;
24
- pager?: string;
25
- parentcustomerid?: string;
26
- parentcustomerid_name?: string;
27
- department?: string;
28
- managerid?: string;
29
- assistantname?: string;
30
- assistantphone?: string;
31
- birthdate?: string;
32
- anniversary?: string;
33
- gendercode?: number;
34
- familystatuscode?: number;
35
- haschildrencode?: boolean;
36
- childrensnames?: string;
37
- description?: string;
38
- donotbulkemail?: boolean;
39
- donotemail?: boolean;
40
- donotfax?: boolean;
41
- donotphone?: boolean;
42
- donotpostalmail?: boolean;
43
- donotsendmm?: boolean;
44
- preferredcontactmethodcode?: number;
45
- preferredappointmentdaycode?: number;
46
- preferredappointmenttimecode?: number;
47
- address1_addresstypecode?: string;
48
- address1_city?: string;
49
- address1_country?: string;
50
- address1_county?: string;
51
- address1_fax?: string;
52
- address1_latitude?: number;
53
- address1_line1?: string;
54
- address1_line2?: string;
55
- address1_line3?: string;
56
- address1_longitude?: number;
57
- address1_name?: string;
58
- address1_postalcode?: string;
59
- address1_postofficebox?: string;
60
- address1_primarycontactname?: string;
61
- address1_shippingmethodcode?: number;
62
- address1_stateorprovince?: string;
63
- address1_telephone1?: string;
64
- address1_telephone2?: string;
65
- address1_telephone3?: string;
66
- address1_upszone?: string;
67
- address1_utcoffset?: number;
68
- creditlimit?: number;
69
- creditonhold?: boolean;
70
- paymenttermscode?: number;
71
- customersizecode?: number;
72
- leadsourcecode?: number;
73
- websiteurl?: string;
74
- governmentid?: string;
75
- yomifirstname?: string;
76
- yomilastname?: string;
77
- yomimiddlename?: string;
78
- yomifullname?: string;
79
- statecode?: number;
80
- statuscode?: number;
81
- createdon?: string;
82
- modifiedon?: string;
83
- createdby?: string;
84
- modifiedby?: string;
85
- ownerid?: string;
86
- transactioncurrencyid?: string;
87
- }
88
-
89
- /**
90
- * Contact entity model class
91
- */
92
- export class Contact extends BaseEntity implements IContact {
93
- contactid?: string;
94
- firstname?: string;
95
- lastname?: string;
96
- fullname?: string;
97
- middlename?: string;
98
- salutation?: string;
99
- suffix?: string;
100
- jobtitle?: string;
101
- emailaddress1?: string;
102
- emailaddress2?: string;
103
- emailaddress3?: string;
104
- telephone1?: string;
105
- telephone2?: string;
106
- mobilephone?: string;
107
- fax?: string;
108
- pager?: string;
109
- parentcustomerid?: string;
110
- parentcustomerid_name?: string;
111
- department?: string;
112
- managerid?: string;
113
- assistantname?: string;
114
- assistantphone?: string;
115
- birthdate?: string;
116
- anniversary?: string;
117
- gendercode?: number;
118
- familystatuscode?: number;
119
- haschildrencode?: boolean;
120
- childrensnames?: string;
121
- description?: string;
122
- donotbulkemail?: boolean;
123
- donotemail?: boolean;
124
- donotfax?: boolean;
125
- donotphone?: boolean;
126
- donotpostalmail?: boolean;
127
- donotsendmm?: boolean;
128
- preferredcontactmethodcode?: number;
129
- preferredappointmentdaycode?: number;
130
- preferredappointmenttimecode?: number;
131
- address1_addresstypecode?: string;
132
- address1_city?: string;
133
- address1_country?: string;
134
- address1_county?: string;
135
- address1_fax?: string;
136
- address1_latitude?: number;
137
- address1_line1?: string;
138
- address1_line2?: string;
139
- address1_line3?: string;
140
- address1_longitude?: number;
141
- address1_name?: string;
142
- address1_postalcode?: string;
143
- address1_postofficebox?: string;
144
- address1_primarycontactname?: string;
145
- address1_shippingmethodcode?: number;
146
- address1_stateorprovince?: string;
147
- address1_telephone1?: string;
148
- address1_telephone2?: string;
149
- address1_telephone3?: string;
150
- address1_upszone?: string;
151
- address1_utcoffset?: number;
152
- creditlimit?: number;
153
- creditonhold?: boolean;
154
- paymenttermscode?: number;
155
- customersizecode?: number;
156
- leadsourcecode?: number;
157
- websiteurl?: string;
158
- governmentid?: string;
159
- yomifirstname?: string;
160
- yomilastname?: string;
161
- yomimiddlename?: string;
162
- yomifullname?: string;
163
- statecode?: number;
164
- statuscode?: number;
165
- createdon?: string;
166
- modifiedon?: string;
167
- createdby?: string;
168
- modifiedby?: string;
169
- ownerid?: string;
170
- transactioncurrencyid?: string;
171
-
172
- /**
173
- * Constructor for Contact entity
174
- * @param contact The contact data
175
- */
176
- constructor(contact: IContact) {
177
- super();
178
- this.contactid = contact.contactid;
179
- this.firstname = contact.firstname;
180
- this.lastname = contact.lastname;
181
- this.fullname = contact.fullname;
182
- this.middlename = contact.middlename;
183
- this.salutation = contact.salutation;
184
- this.suffix = contact.suffix;
185
- this.jobtitle = contact.jobtitle;
186
- this.emailaddress1 = contact.emailaddress1;
187
- this.emailaddress2 = contact.emailaddress2;
188
- this.emailaddress3 = contact.emailaddress3;
189
- this.telephone1 = contact.telephone1;
190
- this.telephone2 = contact.telephone2;
191
- this.mobilephone = contact.mobilephone;
192
- this.fax = contact.fax;
193
- this.pager = contact.pager;
194
- this.parentcustomerid = contact.parentcustomerid;
195
- this.parentcustomerid_name = contact.parentcustomerid_name;
196
- this.department = contact.department;
197
- this.managerid = contact.managerid;
198
- this.assistantname = contact.assistantname;
199
- this.assistantphone = contact.assistantphone;
200
- this.birthdate = contact.birthdate;
201
- this.anniversary = contact.anniversary;
202
- this.gendercode = contact.gendercode;
203
- this.familystatuscode = contact.familystatuscode;
204
- this.haschildrencode = contact.haschildrencode;
205
- this.childrensnames = contact.childrensnames;
206
- this.description = contact.description;
207
- this.donotbulkemail = contact.donotbulkemail;
208
- this.donotemail = contact.donotemail;
209
- this.donotfax = contact.donotfax;
210
- this.donotphone = contact.donotphone;
211
- this.donotpostalmail = contact.donotpostalmail;
212
- this.donotsendmm = contact.donotsendmm;
213
- this.preferredcontactmethodcode = contact.preferredcontactmethodcode;
214
- this.preferredappointmentdaycode = contact.preferredappointmentdaycode;
215
- this.preferredappointmenttimecode = contact.preferredappointmenttimecode;
216
- this.address1_addresstypecode = contact.address1_addresstypecode;
217
- this.address1_city = contact.address1_city;
218
- this.address1_country = contact.address1_country;
219
- this.address1_county = contact.address1_county;
220
- this.address1_fax = contact.address1_fax;
221
- this.address1_latitude = contact.address1_latitude;
222
- this.address1_line1 = contact.address1_line1;
223
- this.address1_line2 = contact.address1_line2;
224
- this.address1_line3 = contact.address1_line3;
225
- this.address1_longitude = contact.address1_longitude;
226
- this.address1_name = contact.address1_name;
227
- this.address1_postalcode = contact.address1_postalcode;
228
- this.address1_postofficebox = contact.address1_postofficebox;
229
- this.address1_primarycontactname = contact.address1_primarycontactname;
230
- this.address1_shippingmethodcode = contact.address1_shippingmethodcode;
231
- this.address1_stateorprovince = contact.address1_stateorprovince;
232
- this.address1_telephone1 = contact.address1_telephone1;
233
- this.address1_telephone2 = contact.address1_telephone2;
234
- this.address1_telephone3 = contact.address1_telephone3;
235
- this.address1_upszone = contact.address1_upszone;
236
- this.address1_utcoffset = contact.address1_utcoffset;
237
- this.creditlimit = contact.creditlimit;
238
- this.creditonhold = contact.creditonhold;
239
- this.paymenttermscode = contact.paymenttermscode;
240
- this.customersizecode = contact.customersizecode;
241
- this.leadsourcecode = contact.leadsourcecode;
242
- this.websiteurl = contact.websiteurl;
243
- this.governmentid = contact.governmentid;
244
- this.yomifirstname = contact.yomifirstname;
245
- this.yomilastname = contact.yomilastname;
246
- this.yomimiddlename = contact.yomimiddlename;
247
- this.yomifullname = contact.yomifullname;
248
- this.statecode = contact.statecode;
249
- this.statuscode = contact.statuscode;
250
- this.createdon = contact.createdon;
251
- this.modifiedon = contact.modifiedon;
252
- this.createdby = contact.createdby;
253
- this.modifiedby = contact.modifiedby;
254
- this.ownerid = contact.ownerid;
255
- this.transactioncurrencyid = contact.transactioncurrencyid;
256
- }
257
-
258
- /**
259
- * Validates the contact instance
260
- * @returns True if valid, throws error if invalid
261
- */
262
- validate(): boolean {
263
- // At least one of firstname or lastname is required
264
- if (
265
- (!this.firstname || this.firstname.trim().length === 0) &&
266
- (!this.lastname || this.lastname.trim().length === 0)
267
- ) {
268
- throw new Error('Contact must have either first name or last name');
269
- }
270
-
271
- if (this.firstname && this.firstname.length > 50) {
272
- throw new Error('First name cannot exceed 50 characters');
273
- }
274
-
275
- if (this.lastname && this.lastname.length > 50) {
276
- throw new Error('Last name cannot exceed 50 characters');
277
- }
278
-
279
- if (this.middlename && this.middlename.length > 100) {
280
- throw new Error('Middle name cannot exceed 100 characters');
281
- }
282
-
283
- if (this.jobtitle && this.jobtitle.length > 100) {
284
- throw new Error('Job title cannot exceed 100 characters');
285
- }
286
-
287
- if (this.emailaddress1 && !this.isValidEmail(this.emailaddress1)) {
288
- throw new Error('Invalid primary email address format');
289
- }
290
-
291
- if (this.emailaddress2 && !this.isValidEmail(this.emailaddress2)) {
292
- throw new Error('Invalid secondary email address format');
293
- }
294
-
295
- if (this.emailaddress3 && !this.isValidEmail(this.emailaddress3)) {
296
- throw new Error('Invalid tertiary email address format');
297
- }
298
-
299
- if (this.websiteurl && !this.isValidUrl(this.websiteurl)) {
300
- throw new Error('Invalid website URL format');
301
- }
302
-
303
- if (this.birthdate && !this.isValidDate(this.birthdate)) {
304
- throw new Error('Invalid birth date format');
305
- }
306
-
307
- if (this.anniversary && !this.isValidDate(this.anniversary)) {
308
- throw new Error('Invalid anniversary date format');
309
- }
310
-
311
- if (this.creditlimit !== undefined && this.creditlimit < 0) {
312
- throw new Error('Credit limit cannot be negative');
313
- }
314
-
315
- return true;
316
- }
317
-
318
- /**
319
- * Creates a new contact record
320
- * @param apiService The API service instance
321
- * @param contact The contact to create
322
- * @returns Promise resolving to the created contact
323
- */
324
- public static async create(
325
- apiService: IApiService,
326
- contact: Contact
327
- ): Promise<Contact> {
328
- const loggerContext = 'Contact.create';
329
- console.log(`${loggerContext}: Creating new contact`, {
330
- firstname: contact.firstname,
331
- lastname: contact.lastname,
332
- });
333
-
334
- return await this.createEntity<Contact>(
335
- apiService,
336
- contact,
337
- ContactConstants.EntityCollectionName,
338
- loggerContext
339
- );
340
- }
341
-
342
- /**
343
- * Updates an existing contact record
344
- * @param apiService The API service instance
345
- * @param contact The contact to update
346
- * @returns Promise resolving to the updated contact
347
- */
348
- public static async update(
349
- apiService: IApiService,
350
- contact: Contact
351
- ): Promise<Contact> {
352
- const loggerContext = 'Contact.update';
353
-
354
- if (!contact.contactid) {
355
- throw new Error('Contact ID is required for update');
356
- }
357
-
358
- console.log(`${loggerContext}: Updating contact`, {
359
- contactid: contact.contactid,
360
- firstname: contact.firstname,
361
- lastname: contact.lastname,
362
- });
363
-
364
- return await this.updateEntity<Contact>(
365
- apiService,
366
- contact.contactid,
367
- contact,
368
- ContactConstants.EntityCollectionName,
369
- ContactConstants.PrimaryKey,
370
- loggerContext
371
- );
372
- }
373
-
374
- /**
375
- * Deletes a contact record
376
- * @param apiService The API service instance
377
- * @param contactId The ID of the contact to delete
378
- * @returns Promise resolving when deletion is complete
379
- */
380
- public static async delete(
381
- apiService: IApiService,
382
- contactId: string
383
- ): Promise<void> {
384
- const loggerContext = 'Contact.delete';
385
- console.log(`${loggerContext}: Deleting contact`, { contactid: contactId });
386
-
387
- return await this.deleteEntity(
388
- apiService,
389
- contactId,
390
- ContactConstants.EntityCollectionName,
391
- loggerContext
392
- );
393
- }
394
-
395
- /**
396
- * Retrieves contacts based on a filter
397
- * @param apiService The API service instance
398
- * @param filter Optional FetchXML filter string
399
- * @returns Promise resolving to an array of contacts
400
- */
401
- public static async retrieveByFilter(
402
- apiService: IApiService,
403
- filter?: string
404
- ): Promise<Contact[]> {
405
- const loggerContext = 'Contact.retrieveByFilter';
406
- console.log(`${loggerContext}: Retrieving contacts with filter`, {
407
- filter,
408
- });
409
-
410
- const attributes = [
411
- ContactConstants.PrimaryKey,
412
- ContactConstants.FirstName,
413
- ContactConstants.LastName,
414
- ContactConstants.PrimaryName,
415
- ContactConstants.JobTitle,
416
- ContactConstants.EMailAddress1,
417
- ContactConstants.BusinessPhone,
418
- ContactConstants.MobilePhone,
419
- ContactConstants.CompanyName,
420
- ContactConstants.Department,
421
- ContactConstants.Address1_City,
422
- ContactConstants.Address1_StateOrProvince,
423
- ContactConstants.Address1_Country,
424
- ContactConstants.StateCode,
425
- ContactConstants.StatusCode,
426
- ContactConstants.CreatedOn,
427
- ContactConstants.ModifiedOn,
428
- ];
429
-
430
- const fetchXml = this.buildFetchXml(
431
- ContactConstants.EntityName,
432
- attributes,
433
- filter,
434
- { attribute: ContactConstants.LastName }
435
- );
436
-
437
- return await this.retrieveEntitiesByFilter<Contact>(
438
- apiService,
439
- ContactConstants.EntityCollectionName,
440
- fetchXml,
441
- Contact,
442
- loggerContext
443
- );
444
- }
445
-
446
- /**
447
- * Retrieves a single contact by ID
448
- * @param apiService The API service instance
449
- * @param contactId The contact ID
450
- * @returns Promise resolving to the contact or null if not found
451
- */
452
- public static async retrieveById(
453
- apiService: IApiService,
454
- contactId: string
455
- ): Promise<Contact | null> {
456
- const loggerContext = 'Contact.retrieveById';
457
- console.log(`${loggerContext}: Retrieving contact by ID`, {
458
- contactid: contactId,
459
- });
460
-
461
- const filter = `<filter type="and">
462
- <condition attribute="${ContactConstants.PrimaryKey}" operator="eq" value="${this.escapeXml(contactId)}" />
463
- </filter>`;
464
-
465
- const contacts = await this.retrieveByFilter(apiService, filter);
466
- return contacts.length > 0 ? contacts[0] : null;
467
- }
468
-
469
- /**
470
- * Retrieves contacts by name (partial match on first or last name)
471
- * @param apiService The API service instance
472
- * @param name The name to search for
473
- * @returns Promise resolving to an array of matching contacts
474
- */
475
- public static async retrieveByName(
476
- apiService: IApiService,
477
- name: string
478
- ): Promise<Contact[]> {
479
- const loggerContext = 'Contact.retrieveByName';
480
- console.log(`${loggerContext}: Retrieving contacts by name`, { name });
481
-
482
- const filter = `<filter type="or">
483
- <condition attribute="${ContactConstants.FirstName}" operator="like" value="%${this.escapeXml(name)}%" />
484
- <condition attribute="${ContactConstants.LastName}" operator="like" value="%${this.escapeXml(name)}%" />
485
- <condition attribute="${ContactConstants.PrimaryName}" operator="like" value="%${this.escapeXml(name)}%" />
486
- </filter>`;
487
-
488
- return await this.retrieveByFilter(apiService, filter);
489
- }
490
-
491
- /**
492
- * Retrieves contacts by email address
493
- * @param apiService The API service instance
494
- * @param email The email address to search for
495
- * @returns Promise resolving to an array of matching contacts
496
- */
497
- public static async retrieveByEmail(
498
- apiService: IApiService,
499
- email: string
500
- ): Promise<Contact[]> {
501
- const loggerContext = 'Contact.retrieveByEmail';
502
- console.log(`${loggerContext}: Retrieving contacts by email`, { email });
503
-
504
- const filter = `<filter type="or">
505
- <condition attribute="${ContactConstants.EMailAddress1}" operator="eq" value="${this.escapeXml(email)}" />
506
- <condition attribute="${ContactConstants.EMailAddress2}" operator="eq" value="${this.escapeXml(email)}" />
507
- <condition attribute="${ContactConstants.EMailAddress3}" operator="eq" value="${this.escapeXml(email)}" />
508
- </filter>`;
509
-
510
- return await this.retrieveByFilter(apiService, filter);
511
- }
512
-
513
- /**
514
- * Retrieves contacts by account (parent customer)
515
- * @param apiService The API service instance
516
- * @param accountId The account ID
517
- * @returns Promise resolving to an array of contacts for the account
518
- */
519
- public static async retrieveByAccount(
520
- apiService: IApiService,
521
- accountId: string
522
- ): Promise<Contact[]> {
523
- const loggerContext = 'Contact.retrieveByAccount';
524
- console.log(`${loggerContext}: Retrieving contacts by account`, {
525
- accountId,
526
- });
527
-
528
- const filter = `<filter type="and">
529
- <condition attribute="${ContactConstants.ParentCustomerId}" operator="eq" value="${this.escapeXml(accountId)}" />
530
- </filter>`;
531
-
532
- return await this.retrieveByFilter(apiService, filter);
533
- }
534
-
535
- /**
536
- * Retrieves active contacts only
537
- * @param apiService The API service instance
538
- * @returns Promise resolving to an array of active contacts
539
- */
540
- public static async retrieveActiveContacts(
541
- apiService: IApiService
542
- ): Promise<Contact[]> {
543
- const loggerContext = 'Contact.retrieveActiveContacts';
544
- console.log(`${loggerContext}: Retrieving active contacts`);
545
-
546
- const filter = `<filter type="and">
547
- <condition attribute="${ContactConstants.StateCode}" operator="eq" value="0" />
548
- </filter>`;
549
-
550
- return await this.retrieveByFilter(apiService, filter);
551
- }
552
-
553
- /**
554
- * Helper method to validate email format
555
- */
556
- private isValidEmail(email: string): boolean {
557
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
558
- return emailRegex.test(email);
559
- }
560
-
561
- /**
562
- * Helper method to validate URL format
563
- */
564
- private isValidUrl(url: string): boolean {
565
- try {
566
- new URL(url);
567
- return true;
568
- } catch {
569
- return false;
570
- }
571
- }
572
-
573
- /**
574
- * Helper method to validate date format
575
- */
576
- private isValidDate(dateString: string): boolean {
577
- const date = new Date(dateString);
578
- return !isNaN(date.getTime());
579
- }
580
- }
@@ -1,107 +0,0 @@
1
- import React from 'react';
2
- import { ContactManagement } from '../components/ContactManagement';
3
- import { DynamicsProvider } from '../providers/DynamicsProvider';
4
- import { ServiceFactory } from '../services/ServiceFactory';
5
- import { Logger } from '../components/Logging/logger';
6
-
7
- interface PCFContextType {
8
- // Define PCF context properties based on your needs
9
- webAPI: any;
10
- utils: any;
11
- parameters: any;
12
- }
13
-
14
- interface ContactControlWrapperProps {
15
- context: PCFContextType;
16
- }
17
-
18
- /**
19
- * Wrapper component for integrating ContactManagement with PCF (PowerApps Component Framework)
20
- * This allows the component to be used as a custom control in Dynamics 365 forms and views
21
- */
22
- export const ContactControlWrapper: React.FC<ContactControlWrapperProps> = ({
23
- context,
24
- }) => {
25
- // Extract configuration from PCF context
26
- const baseUrl = context.parameters?.baseUrl?.raw || '';
27
-
28
- // Create API service using ServiceFactory for environment detection
29
- const createPCFApiService = () => {
30
- try {
31
- // Check if we're in PCF context with webAPI available
32
- if (context.webAPI) {
33
- Logger.log(
34
- 'PCF WebAPI available, creating custom PCF API service',
35
- 'ContactControlWrapper'
36
- );
37
-
38
- // Create a custom API service that uses PCF's webAPI
39
- return {
40
- createRecord: async (entityName: string, data: any) => {
41
- return await context.webAPI.createRecord(entityName, data);
42
- },
43
- retrieveRecord: async (
44
- entityName: string,
45
- id: string,
46
- select?: string
47
- ) => {
48
- return await context.webAPI.retrieveRecord(entityName, id, select);
49
- },
50
- updateRecord: async (entityName: string, id: string, data: any) => {
51
- return await context.webAPI.updateRecord(entityName, id, data);
52
- },
53
- deleteRecord: async (entityName: string, id: string) => {
54
- return await context.webAPI.deleteRecord(entityName, id);
55
- },
56
- retrieveMultipleRecords: async (
57
- entityName: string,
58
- fetchXml: string
59
- ) => {
60
- return await context.webAPI.retrieveMultipleRecords(
61
- entityName,
62
- `?fetchXml=${encodeURIComponent(fetchXml)}`
63
- );
64
- },
65
- executeRequest: async (requestName: string, _requestData: any) => {
66
- Logger.log(
67
- `Executing request: ${requestName}`,
68
- 'ContactControlWrapper.executeRequest'
69
- );
70
- throw new Error('Custom requests not supported in PCF WebAPI');
71
- },
72
- uploadFile: async (_file: File) => {
73
- Logger.log(
74
- 'File upload requested',
75
- 'ContactControlWrapper.uploadFile'
76
- );
77
- throw new Error('File upload not supported in PCF WebAPI');
78
- },
79
- };
80
- }
81
-
82
- // Fallback to ServiceFactory for environment detection
83
- const xrmGlobal = ServiceFactory.isDynamics365Context()
84
- ? (window as any).Xrm
85
- : undefined;
86
- return ServiceFactory.createApiService(xrmGlobal);
87
- } catch (error) {
88
- Logger.error(
89
- 'Failed to create API service in PCF context',
90
- 'ContactControlWrapper',
91
- error
92
- );
93
- throw error;
94
- }
95
- };
96
-
97
- return (
98
- <DynamicsProvider customApiService={createPCFApiService()}>
99
- <div style={{ width: '100%', height: '100%' }}>
100
- <ContactManagement />
101
- </div>
102
- </DynamicsProvider>
103
- );
104
- };
105
-
106
- // Export for PCF integration
107
- export default ContactControlWrapper;