@khester/create-dynamics-app 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/artifacts/registry.d.ts +4 -3
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +122 -12
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/artifacts/types.d.ts +1 -1
  6. package/dist/artifacts/types.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/injectDevTools.d.ts.map +1 -1
  10. package/dist/injectDevTools.js +4 -2
  11. package/dist/injectDevTools.js.map +1 -1
  12. package/dist/scaffold.d.ts +1 -0
  13. package/dist/scaffold.d.ts.map +1 -1
  14. package/dist/scaffold.js +3 -1
  15. package/dist/scaffold.js.map +1 -1
  16. package/package.json +3 -2
  17. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  18. package/templates/grid-starter/README.md +122 -0
  19. package/templates/grid-starter/env.example +16 -0
  20. package/templates/grid-starter/gitignore +6 -0
  21. package/templates/grid-starter/index.html +16 -0
  22. package/templates/grid-starter/package.json +39 -0
  23. package/templates/grid-starter/src/App.tsx +23 -0
  24. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  25. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  26. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  27. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  28. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  29. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  30. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  31. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  32. package/templates/grid-starter/src/index.tsx +18 -0
  33. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  34. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  35. package/templates/grid-starter/tsconfig.json +19 -0
  36. package/templates/grid-starter/vite.config.ts +76 -0
  37. package/templates/pcf-dataset/package.json +3 -1
  38. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  41. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  42. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  43. package/templates/pcf-field/index.ts +1 -1
  44. package/templates/pcf-field/package.json +3 -1
  45. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  46. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  47. package/templates/react-custom-page/README.md +74 -568
  48. package/templates/react-custom-page/env.example +16 -0
  49. package/templates/react-custom-page/gitignore +1 -0
  50. package/templates/react-custom-page/index.html +16 -0
  51. package/templates/react-custom-page/package.json +21 -49
  52. package/templates/react-custom-page/src/App.tsx +26 -0
  53. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  54. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  55. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  56. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  57. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  58. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  59. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  60. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  61. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  62. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  63. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  64. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  65. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  67. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  69. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  70. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  71. package/templates/react-custom-page/src/index.tsx +18 -128
  72. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  73. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  74. package/templates/react-custom-page/tsconfig.json +12 -22
  75. package/templates/react-custom-page/vite.config.ts +76 -0
  76. package/templates/starter-page/README.md +38 -0
  77. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  78. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  79. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  80. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  81. package/templates/starter-page/gitignore +5 -0
  82. package/templates/starter-page/package.json +27 -0
  83. package/templates/starter-page/public/index.html +11 -0
  84. package/templates/starter-page/src/index.tsx +10 -0
  85. package/templates/starter-page/src/services/dataverse.ts +30 -0
  86. package/templates/starter-page/tsconfig.json +15 -0
  87. package/templates/starter-page/webpack.config.js +17 -0
  88. package/templates/react-custom-page/deployment/README.md +0 -484
  89. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  90. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  91. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  92. package/templates/react-custom-page/public/index.html +0 -15
  93. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  94. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  95. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  96. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  97. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  98. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  99. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  100. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  101. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  102. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  103. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  105. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  106. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  107. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  108. package/templates/react-custom-page/src/constants/account.ts +0 -410
  109. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  110. package/templates/react-custom-page/src/models/Account.ts +0 -480
  111. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  112. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  113. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  114. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  115. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  116. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  117. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  118. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  119. package/templates/react-custom-page/src/styles/index.css +0 -171
  120. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  121. package/templates/react-custom-page/webpack.config.js +0 -57
  122. /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
@@ -1,327 +0,0 @@
1
- import React, { useState, useCallback, useEffect } from 'react';
2
- import {
3
- Button,
4
- TextField,
5
- Dropdown,
6
- DatePicker,
7
- } from '@khester/dynamics-ui-components';
8
- import { useDynamicsApi } from '../providers/DynamicsProvider';
9
- import { Contact, IContact } from '../models/Contact';
10
- import {
11
- ContactConstants,
12
- PreferredContactMethodCode_OptionSet,
13
- } from '../constants/contact';
14
- import { Logger } from './Logging/logger';
15
- import './ContactForm.css';
16
-
17
- interface ContactFormData {
18
- firstname: string;
19
- lastname: string;
20
- emailaddress1: string;
21
- telephone1: string;
22
- preferredcontactmethodcode: number;
23
- birthdate: Date | null;
24
- }
25
-
26
- interface ContactFormProps {
27
- contactId?: string;
28
- initialData?: Partial<ContactFormData>;
29
- onSave?: () => void;
30
- onCancel?: () => void;
31
- }
32
-
33
- const preferredContactOptions = [
34
- { key: PreferredContactMethodCode_OptionSet.Any, text: 'Any' },
35
- { key: PreferredContactMethodCode_OptionSet.Email, text: 'Email' },
36
- { key: PreferredContactMethodCode_OptionSet.Phone, text: 'Phone' },
37
- { key: PreferredContactMethodCode_OptionSet.Fax, text: 'Fax' },
38
- { key: PreferredContactMethodCode_OptionSet.Mail, text: 'Mail' },
39
- ];
40
-
41
- export const ContactForm: React.FC<ContactFormProps> = ({
42
- contactId,
43
- initialData,
44
- onSave,
45
- onCancel,
46
- }) => {
47
- const { apiService } = useDynamicsApi();
48
-
49
- const [formData, setFormData] = useState<ContactFormData>({
50
- firstname: '',
51
- lastname: '',
52
- emailaddress1: '',
53
- telephone1: '',
54
- preferredcontactmethodcode: PreferredContactMethodCode_OptionSet.Any,
55
- birthdate: null,
56
- });
57
-
58
- useEffect(() => {
59
- Logger.userAction(
60
- contactId
61
- ? 'Contact form opened for edit'
62
- : 'Contact form opened for new contact',
63
- { contactId },
64
- 'ContactForm'
65
- );
66
- }, [contactId]);
67
-
68
- const [errors, setErrors] = useState<Partial<ContactFormData>>({});
69
- const [isSubmitting, setIsSubmitting] = useState(false);
70
- const [submitError, setSubmitError] = useState<string>('');
71
-
72
- useEffect(() => {
73
- if (initialData) {
74
- Logger.debug(
75
- 'Populating form with initial data',
76
- 'ContactForm.useEffect',
77
- initialData
78
- );
79
-
80
- setFormData({
81
- firstname: initialData.firstname || '',
82
- lastname: initialData.lastname || '',
83
- emailaddress1: initialData.emailaddress1 || '',
84
- telephone1: initialData.telephone1 || '',
85
- preferredcontactmethodcode:
86
- initialData.preferredcontactmethodcode ||
87
- PreferredContactMethodCode_OptionSet.Any,
88
- birthdate: initialData.birthdate || null,
89
- });
90
- }
91
- }, [initialData]);
92
-
93
- const validateForm = useCallback((): boolean => {
94
- const newErrors: Partial<ContactFormData> = {};
95
- const validationErrors: string[] = [];
96
-
97
- // At least one name is required (following Contact model validation)
98
- if (!formData.firstname.trim() && !formData.lastname.trim()) {
99
- const error = 'Either first name or last name is required';
100
- newErrors.firstname = error;
101
- newErrors.lastname = error;
102
- validationErrors.push(error);
103
- }
104
-
105
- if (
106
- formData.emailaddress1 &&
107
- !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.emailaddress1)
108
- ) {
109
- const error = 'Please enter a valid email address';
110
- newErrors.emailaddress1 = error;
111
- validationErrors.push(error);
112
- }
113
-
114
- if (formData.telephone1 && !/^\+?[\d\s\-()]+$/.test(formData.telephone1)) {
115
- const error = 'Please enter a valid phone number';
116
- newErrors.telephone1 = error;
117
- validationErrors.push(error);
118
- }
119
-
120
- if (validationErrors.length > 0) {
121
- Logger.validation(
122
- 'Contact',
123
- validationErrors,
124
- 'ContactForm.validateForm'
125
- );
126
- }
127
-
128
- setErrors(newErrors);
129
- return Object.keys(newErrors).length === 0;
130
- }, [formData]);
131
-
132
- const handleInputChange = useCallback(
133
- (field: keyof ContactFormData, value: any) => {
134
- setFormData((prev) => ({ ...prev, [field]: value }));
135
-
136
- // Clear error for this field when user starts typing
137
- if (errors[field]) {
138
- setErrors((prev) => ({ ...prev, [field]: undefined }));
139
- }
140
- setSubmitError('');
141
- },
142
- [errors]
143
- );
144
-
145
- const handleSubmit = useCallback(async () => {
146
- if (!validateForm()) {
147
- return;
148
- }
149
-
150
- if (!apiService) {
151
- setSubmitError('API service not available');
152
- Logger.error('API service not available', 'ContactForm.handleSubmit');
153
- return;
154
- }
155
-
156
- setIsSubmitting(true);
157
- setSubmitError('');
158
-
159
- try {
160
- Logger.userAction(
161
- contactId ? 'Contact update submitted' : 'Contact creation submitted',
162
- { contactId, formData },
163
- 'ContactForm.handleSubmit'
164
- );
165
-
166
- const contactData: IContact = {
167
- [ContactConstants.FirstName]: formData.firstname.trim() || undefined,
168
- [ContactConstants.LastName]: formData.lastname.trim() || undefined,
169
- [ContactConstants.EMailAddress1]:
170
- formData.emailaddress1.trim() || undefined,
171
- [ContactConstants.BusinessPhone]:
172
- formData.telephone1.trim() || undefined,
173
- [ContactConstants.PreferredContactMethodCode]:
174
- formData.preferredcontactmethodcode,
175
- [ContactConstants.BirthDate]: formData.birthdate
176
- ? formData.birthdate.toISOString().split('T')[0]
177
- : undefined,
178
- };
179
-
180
- // Remove undefined values
181
- Object.keys(contactData).forEach((key) => {
182
- if (contactData[key as keyof IContact] === undefined) {
183
- delete contactData[key as keyof IContact];
184
- }
185
- });
186
-
187
- const contact = new Contact(contactData);
188
-
189
- if (contactId) {
190
- contact.contactid = contactId;
191
- await Contact.update(apiService, contact);
192
- Logger.log(
193
- `Successfully updated contact: ${contact.firstname} ${contact.lastname}`,
194
- 'ContactForm.handleSubmit'
195
- );
196
- } else {
197
- await Contact.create(apiService, contact);
198
- Logger.log(
199
- `Successfully created contact: ${contact.firstname} ${contact.lastname}`,
200
- 'ContactForm.handleSubmit'
201
- );
202
- }
203
-
204
- if (onSave) {
205
- onSave();
206
- }
207
- } catch (error) {
208
- const errorMessage =
209
- error instanceof Error
210
- ? error.message
211
- : 'An error occurred while saving';
212
- setSubmitError(errorMessage);
213
- Logger.error(
214
- `Failed to ${contactId ? 'update' : 'create'} contact`,
215
- 'ContactForm.handleSubmit',
216
- error
217
- );
218
- } finally {
219
- setIsSubmitting(false);
220
- }
221
- }, [formData, contactId, validateForm, apiService, onSave]);
222
-
223
- const handleCancel = useCallback(() => {
224
- if (onCancel) {
225
- onCancel();
226
- }
227
- }, [onCancel]);
228
-
229
- return (
230
- <div className="contact-form">
231
- {submitError && (
232
- <div className="contact-form__error" role="alert">
233
- {submitError}
234
- </div>
235
- )}
236
-
237
- <div className="contact-form__content">
238
- <div className="contact-form__row">
239
- <div className="contact-form__field">
240
- <TextField
241
- label="First Name"
242
- required
243
- value={formData.firstname}
244
- onChange={(_, value) =>
245
- handleInputChange('firstname', value || '')
246
- }
247
- errorMessage={errors.firstname}
248
- disabled={isSubmitting}
249
- />
250
- </div>
251
- <div className="contact-form__field">
252
- <TextField
253
- label="Last Name"
254
- required
255
- value={formData.lastname}
256
- onChange={(_, value) =>
257
- handleInputChange('lastname', value || '')
258
- }
259
- errorMessage={errors.lastname}
260
- disabled={isSubmitting}
261
- />
262
- </div>
263
- </div>
264
-
265
- <div className="contact-form__row">
266
- <div className="contact-form__field">
267
- <TextField
268
- label="Email Address"
269
- type="email"
270
- required
271
- value={formData.emailaddress1}
272
- onChange={(_, value) =>
273
- handleInputChange('emailaddress1', value || '')
274
- }
275
- errorMessage={errors.emailaddress1}
276
- disabled={isSubmitting}
277
- />
278
- </div>
279
- <div className="contact-form__field">
280
- <TextField
281
- label="Phone Number"
282
- type="tel"
283
- value={formData.telephone1}
284
- onChange={(_, value) =>
285
- handleInputChange('telephone1', value || '')
286
- }
287
- errorMessage={errors.telephone1}
288
- disabled={isSubmitting}
289
- />
290
- </div>
291
- </div>
292
-
293
- <div className="contact-form__row">
294
- <div className="contact-form__field">
295
- <Dropdown
296
- label="Preferred Contact Method"
297
- options={preferredContactOptions}
298
- selectedKey={formData.preferredcontactmethodcode}
299
- onChange={(_, option) =>
300
- handleInputChange('preferredcontactmethodcode', option?.key)
301
- }
302
- disabled={isSubmitting}
303
- />
304
- </div>
305
- <div className="contact-form__field">
306
- <DatePicker
307
- label="Birth Date"
308
- value={formData.birthdate ?? undefined}
309
- onSelectDate={(date) => handleInputChange('birthdate', date)}
310
- disabled={isSubmitting}
311
- placeholder="Select date..."
312
- />
313
- </div>
314
- </div>
315
- </div>
316
-
317
- <div className="contact-form__actions">
318
- <Button
319
- text={contactId ? 'Update' : 'Save'}
320
- onClick={handleSubmit}
321
- disabled={isSubmitting}
322
- />
323
- <Button text="Cancel" onClick={handleCancel} disabled={isSubmitting} />
324
- </div>
325
- </div>
326
- );
327
- };
@@ -1,86 +0,0 @@
1
- .contact-management {
2
- padding: 16px;
3
- }
4
-
5
- .contact-management__header {
6
- display: flex;
7
- justify-content: space-between;
8
- align-items: center;
9
- margin-bottom: 16px;
10
- flex-wrap: wrap;
11
- gap: 16px;
12
- }
13
-
14
- .contact-management__header h2 {
15
- margin: 0;
16
- font-size: 20px;
17
- font-weight: 600;
18
- color: #323130;
19
- }
20
-
21
- .contact-management__actions {
22
- display: flex;
23
- gap: 12px;
24
- align-items: center;
25
- flex-wrap: wrap;
26
- }
27
-
28
- .contact-management__toolbar {
29
- display: flex;
30
- gap: 8px;
31
- margin-bottom: 16px;
32
- padding: 8px 0;
33
- border-bottom: 1px solid #edebe9;
34
- }
35
-
36
- .contact-management__list {
37
- min-height: 400px;
38
- border: 1px solid #edebe9;
39
- border-radius: 4px;
40
- }
41
-
42
- .contact-management__loading {
43
- display: flex;
44
- justify-content: center;
45
- align-items: center;
46
- height: 200px;
47
- color: #605e5c;
48
- font-style: italic;
49
- }
50
-
51
- .contact-management__empty {
52
- display: flex;
53
- justify-content: center;
54
- align-items: center;
55
- height: 200px;
56
- color: #605e5c;
57
- font-style: italic;
58
- text-align: center;
59
- padding: 16px;
60
- }
61
-
62
- .contact-management__dialog-actions {
63
- display: flex;
64
- gap: 12px;
65
- justify-content: flex-end;
66
- padding-top: 16px;
67
- }
68
-
69
- @media (max-width: 768px) {
70
- .contact-management {
71
- padding: 8px;
72
- }
73
-
74
- .contact-management__header {
75
- flex-direction: column;
76
- align-items: stretch;
77
- }
78
-
79
- .contact-management__actions {
80
- justify-content: stretch;
81
- }
82
-
83
- .contact-management__toolbar {
84
- flex-wrap: wrap;
85
- }
86
- }