@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,370 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
- import {
3
- Button,
4
- TextField,
5
- DetailsList,
6
- Dialog,
7
- Panel,
8
- } from '@khester/dynamics-ui-components';
9
- import {
10
- SelectionMode,
11
- DetailsListLayoutMode,
12
- CheckboxVisibility,
13
- } from '@fluentui/react';
14
- import { useDynamicsApi } from '../providers/DynamicsProvider';
15
- import { Account } from '../models/Account';
16
- import { AccountConstants } from '../constants/account';
17
- import { Logger } from './Logging/logger';
18
- import { AccountForm } from './AccountForm';
19
- import './AccountManagement.css';
20
-
21
- const columns = [
22
- {
23
- key: AccountConstants.PrimaryName,
24
- name: 'Account Name',
25
- fieldName: AccountConstants.PrimaryName,
26
- minWidth: 200,
27
- maxWidth: 300,
28
- isResizable: true,
29
- },
30
- {
31
- key: AccountConstants.AccountNumber,
32
- name: 'Account Number',
33
- fieldName: AccountConstants.AccountNumber,
34
- minWidth: 120,
35
- maxWidth: 160,
36
- isResizable: true,
37
- },
38
- {
39
- key: AccountConstants.EMailAddress1,
40
- name: 'Email',
41
- fieldName: AccountConstants.EMailAddress1,
42
- minWidth: 200,
43
- maxWidth: 300,
44
- isResizable: true,
45
- },
46
- {
47
- key: AccountConstants.Telephone1,
48
- name: 'Phone',
49
- fieldName: AccountConstants.Telephone1,
50
- minWidth: 150,
51
- maxWidth: 200,
52
- isResizable: true,
53
- },
54
- {
55
- key: AccountConstants.WebSiteURL,
56
- name: 'Website',
57
- fieldName: AccountConstants.WebSiteURL,
58
- minWidth: 150,
59
- maxWidth: 200,
60
- isResizable: true,
61
- },
62
- {
63
- key: AccountConstants.CreatedOn,
64
- name: 'Created',
65
- fieldName: AccountConstants.CreatedOn,
66
- minWidth: 120,
67
- maxWidth: 160,
68
- isResizable: true,
69
- },
70
- ];
71
-
72
- export const AccountManagement: React.FC = () => {
73
- const { apiService, isEnvironmentMock, environmentType } = useDynamicsApi();
74
-
75
- const [accounts, setAccounts] = useState<Account[]>([]);
76
- const [filteredAccounts, setFilteredAccounts] = useState<Account[]>([]);
77
- const [loading, setLoading] = useState(false);
78
- const [searchText, setSearchText] = useState('');
79
- const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
80
- const [showNewAccountPanel, setShowNewAccountPanel] = useState(false);
81
- const [showEditAccountPanel, setShowEditAccountPanel] = useState(false);
82
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
83
- const [accountToDelete, setAccountToDelete] = useState<Account | null>(null);
84
-
85
- useEffect(() => {
86
- Logger.userAction(
87
- 'AccountManagement loaded',
88
- { environmentType, isEnvironmentMock },
89
- 'AccountManagement'
90
- );
91
- }, [environmentType, isEnvironmentMock]);
92
-
93
- const loadAccounts = useCallback(async () => {
94
- if (!apiService) {
95
- Logger.error(
96
- 'API service not available',
97
- 'AccountManagement.loadAccounts'
98
- );
99
- return;
100
- }
101
-
102
- setLoading(true);
103
- Logger.log('Loading accounts', 'AccountManagement.loadAccounts');
104
-
105
- try {
106
- const accountsData = await Account.retrieveActiveAccounts(apiService);
107
-
108
- const accountsWithDisplayData = accountsData.map((account) => {
109
- const accountInstance = new Account(account);
110
- return {
111
- ...accountInstance,
112
- createdon: account.createdon
113
- ? new Date(account.createdon).toLocaleDateString()
114
- : '',
115
- } as Account;
116
- });
117
-
118
- setAccounts(accountsWithDisplayData);
119
- setFilteredAccounts(accountsWithDisplayData);
120
-
121
- Logger.log(
122
- `Successfully loaded ${accountsWithDisplayData.length} accounts`,
123
- 'AccountManagement.loadAccounts'
124
- );
125
- } catch (error) {
126
- Logger.error(
127
- 'Failed to load accounts',
128
- 'AccountManagement.loadAccounts',
129
- error
130
- );
131
- } finally {
132
- setLoading(false);
133
- }
134
- }, [apiService]);
135
-
136
- useEffect(() => {
137
- loadAccounts();
138
- }, [loadAccounts]);
139
-
140
- useEffect(() => {
141
- if (!searchText) {
142
- setFilteredAccounts(accounts);
143
- } else {
144
- const filtered = accounts.filter(
145
- (account) =>
146
- account.name?.toLowerCase().includes(searchText.toLowerCase()) ||
147
- account.accountnumber
148
- ?.toLowerCase()
149
- .includes(searchText.toLowerCase()) ||
150
- account.emailaddress1
151
- ?.toLowerCase()
152
- .includes(searchText.toLowerCase()) ||
153
- account.telephone1?.includes(searchText) ||
154
- account.websiteurl?.toLowerCase().includes(searchText.toLowerCase())
155
- );
156
- setFilteredAccounts(filtered);
157
- }
158
- }, [accounts, searchText]);
159
-
160
- const handleItemInvoked = useCallback(
161
- (item: Account) => {
162
- Logger.userAction(
163
- 'Account item invoked',
164
- { accountId: item.accountid, accountName: item.name },
165
- 'AccountManagement.handleItemInvoked'
166
- );
167
-
168
- if (selectedAccount?.accountid === item.accountid) {
169
- setShowEditAccountPanel(true);
170
- } else {
171
- setSelectedAccount(item);
172
- }
173
- },
174
- [selectedAccount]
175
- );
176
-
177
- const handleNewAccount = useCallback(() => {
178
- Logger.userAction(
179
- 'New account panel opened',
180
- {},
181
- 'AccountManagement.handleNewAccount'
182
- );
183
- setShowNewAccountPanel(true);
184
- }, []);
185
-
186
- const handleEditAccount = useCallback(() => {
187
- if (selectedAccount) {
188
- Logger.userAction(
189
- 'Edit account panel opened',
190
- {
191
- accountId: selectedAccount.accountid,
192
- accountName: selectedAccount.name,
193
- },
194
- 'AccountManagement.handleEditAccount'
195
- );
196
- setShowEditAccountPanel(true);
197
- }
198
- }, [selectedAccount]);
199
-
200
- const handleDeleteAccount = useCallback(() => {
201
- if (selectedAccount) {
202
- Logger.userAction(
203
- 'Delete account dialog opened',
204
- {
205
- accountId: selectedAccount.accountid,
206
- accountName: selectedAccount.name,
207
- },
208
- 'AccountManagement.handleDeleteAccount'
209
- );
210
- setAccountToDelete(selectedAccount);
211
- setShowDeleteDialog(true);
212
- }
213
- }, [selectedAccount]);
214
-
215
- const confirmDelete = useCallback(async () => {
216
- if (accountToDelete && apiService) {
217
- try {
218
- Logger.userAction(
219
- 'Account deletion confirmed',
220
- {
221
- accountId: accountToDelete.accountid,
222
- accountName: accountToDelete.name,
223
- },
224
- 'AccountManagement.confirmDelete'
225
- );
226
-
227
- await Account.delete(apiService, accountToDelete.accountid!);
228
- await loadAccounts();
229
-
230
- setShowDeleteDialog(false);
231
- setAccountToDelete(null);
232
- setSelectedAccount(null);
233
-
234
- Logger.log(
235
- `Successfully deleted account: ${accountToDelete.name}`,
236
- 'AccountManagement.confirmDelete'
237
- );
238
- } catch (error) {
239
- Logger.error(
240
- `Failed to delete account: ${accountToDelete.name}`,
241
- 'AccountManagement.confirmDelete',
242
- error
243
- );
244
- }
245
- }
246
- }, [accountToDelete, apiService, loadAccounts]);
247
-
248
- const handleAccountSaved = useCallback(() => {
249
- Logger.userAction(
250
- 'Account saved, refreshing list',
251
- {},
252
- 'AccountManagement.handleAccountSaved'
253
- );
254
- loadAccounts();
255
- setShowNewAccountPanel(false);
256
- setShowEditAccountPanel(false);
257
- setSelectedAccount(null);
258
- }, [loadAccounts]);
259
-
260
- return (
261
- <div className="account-management">
262
- <div className="account-management__header">
263
- <h2>Account Management</h2>
264
- <div className="account-management__actions">
265
- <TextField
266
- placeholder="Search accounts..."
267
- value={searchText}
268
- onChange={(_, value) => setSearchText(value || '')}
269
- iconProps={{ iconName: 'Search' }}
270
- />
271
- <Button
272
- text="New Account"
273
- variant="primary"
274
- onClick={handleNewAccount}
275
- iconProps={{ iconName: 'Add' }}
276
- />
277
- </div>
278
- </div>
279
-
280
- <div className="account-management__toolbar">
281
- <Button
282
- text="Edit"
283
- onClick={handleEditAccount}
284
- disabled={!selectedAccount}
285
- iconProps={{ iconName: 'Edit' }}
286
- />
287
- <Button
288
- text="Delete"
289
- onClick={handleDeleteAccount}
290
- disabled={!selectedAccount}
291
- iconProps={{ iconName: 'Delete' }}
292
- />
293
- <Button
294
- text="Refresh"
295
- onClick={loadAccounts}
296
- iconProps={{ iconName: 'Refresh' }}
297
- />
298
- </div>
299
-
300
- <div className="account-management__list">
301
- <DetailsList
302
- items={filteredAccounts}
303
- columns={columns}
304
- onItemInvoked={handleItemInvoked}
305
- selectionMode={SelectionMode.single}
306
- layoutMode={DetailsListLayoutMode.justified}
307
- checkboxVisibility={CheckboxVisibility.onHover}
308
- />
309
-
310
- {loading && (
311
- <div className="account-management__loading">Loading accounts...</div>
312
- )}
313
-
314
- {!loading && filteredAccounts.length === 0 && (
315
- <div className="account-management__empty">
316
- {searchText
317
- ? 'No accounts found matching your search.'
318
- : 'No accounts found. Create your first account!'}
319
- </div>
320
- )}
321
- </div>
322
-
323
- {/* New Account Panel */}
324
- <Panel
325
- isOpen={showNewAccountPanel}
326
- onDismiss={() => setShowNewAccountPanel(false)}
327
- headerText="New Account"
328
- >
329
- <AccountForm
330
- onSave={handleAccountSaved}
331
- onCancel={() => setShowNewAccountPanel(false)}
332
- />
333
- </Panel>
334
-
335
- {/* Edit Account Panel */}
336
- <Panel
337
- isOpen={showEditAccountPanel}
338
- onDismiss={() => {
339
- setShowEditAccountPanel(false);
340
- setSelectedAccount(null);
341
- }}
342
- headerText="Edit Account"
343
- >
344
- {selectedAccount && (
345
- <AccountForm
346
- accountId={selectedAccount.accountid}
347
- initialData={selectedAccount}
348
- onSave={handleAccountSaved}
349
- onCancel={() => {
350
- setShowEditAccountPanel(false);
351
- setSelectedAccount(null);
352
- }}
353
- />
354
- )}
355
- </Panel>
356
-
357
- {/* Delete Confirmation Dialog */}
358
- <Dialog
359
- hidden={!showDeleteDialog}
360
- onDismiss={() => setShowDeleteDialog(false)}
361
- title="Confirm Delete"
362
- content={`Are you sure you want to delete ${accountToDelete?.name}? This action cannot be undone.`}
363
- actions={[
364
- { text: 'Delete', onClick: confirmDelete, primary: true },
365
- { text: 'Cancel', onClick: () => setShowDeleteDialog(false) },
366
- ]}
367
- />
368
- </div>
369
- );
370
- };
@@ -1,48 +0,0 @@
1
- .contact-form {
2
- padding: 16px 0;
3
- }
4
-
5
- .contact-form__content {
6
- margin-bottom: 24px;
7
- }
8
-
9
- .contact-form__row {
10
- display: grid;
11
- grid-template-columns: 1fr 1fr;
12
- gap: 16px;
13
- margin-bottom: 16px;
14
- }
15
-
16
- .contact-form__field {
17
- display: flex;
18
- flex-direction: column;
19
- }
20
-
21
- .contact-form__actions {
22
- display: flex;
23
- gap: 12px;
24
- justify-content: flex-start;
25
- padding-top: 16px;
26
- border-top: 1px solid #edebe9;
27
- }
28
-
29
- .contact-form__error {
30
- padding: 12px 16px;
31
- margin-bottom: 16px;
32
- background-color: #fef7f1;
33
- border: 1px solid #d13438;
34
- border-radius: 4px;
35
- color: #d13438;
36
- font-weight: 500;
37
- }
38
-
39
- @media (max-width: 768px) {
40
- .contact-form__row {
41
- grid-template-columns: 1fr;
42
- gap: 12px;
43
- }
44
-
45
- .contact-form__actions {
46
- flex-direction: column;
47
- }
48
- }