@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,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;
|