@khester/create-dynamics-app 1.0.8 → 1.1.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 (107) hide show
  1. package/bin/create-dynamics-app.js +1 -1
  2. package/dist/index.js +140 -15
  3. package/dist/index.js.map +1 -1
  4. package/dist/utils/consultingHelpers.d.ts +13 -0
  5. package/dist/utils/consultingHelpers.d.ts.map +1 -0
  6. package/dist/utils/consultingHelpers.js +569 -0
  7. package/dist/utils/consultingHelpers.js.map +1 -0
  8. package/dist/utils/copyTemplate.d.ts.map +1 -1
  9. package/dist/utils/copyTemplate.js.map +1 -1
  10. package/dist/utils/initGit.d.ts.map +1 -1
  11. package/dist/utils/initGit.js.map +1 -1
  12. package/dist/utils/installDependencies.d.ts.map +1 -1
  13. package/dist/utils/installDependencies.js +3 -2
  14. package/dist/utils/installDependencies.js.map +1 -1
  15. package/dist/utils/updatePackageJson.d.ts +1 -1
  16. package/dist/utils/updatePackageJson.d.ts.map +1 -1
  17. package/dist/utils/updatePackageJson.js +11 -1
  18. package/dist/utils/updatePackageJson.js.map +1 -1
  19. package/package.json +1 -1
  20. package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +302 -0
  21. package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +305 -0
  22. package/templates/dynamics-365-starter/README.md +566 -137
  23. package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +507 -0
  24. package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +372 -0
  25. package/templates/dynamics-365-starter/deployment/README.md +484 -0
  26. package/templates/dynamics-365-starter/deployment/pipelines/README.md +375 -0
  27. package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +330 -0
  28. package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +422 -0
  29. package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +636 -0
  30. package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +417 -0
  31. package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +582 -0
  32. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +486 -0
  33. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +567 -0
  34. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +703 -0
  35. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +671 -0
  36. package/templates/dynamics-365-starter/docs/ARCHITECTURE_OVERVIEW.md +506 -0
  37. package/templates/dynamics-365-starter/docs/BEST_PRACTICES.md +723 -0
  38. package/templates/dynamics-365-starter/docs/MIGRATION_GUIDE.md +447 -0
  39. package/templates/dynamics-365-starter/docs/team-standards/README.md +273 -0
  40. package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +577 -0
  41. package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +359 -0
  42. package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +700 -0
  43. package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +736 -0
  44. package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +727 -0
  45. package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +758 -0
  46. package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +878 -0
  47. package/templates/dynamics-365-starter/package.json +22 -1
  48. package/templates/dynamics-365-starter/public/index.html +8 -11
  49. package/templates/dynamics-365-starter/scripts/custom-build.js +255 -0
  50. package/templates/dynamics-365-starter/src/client-project-template/README.md +234 -0
  51. package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +114 -0
  52. package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +186 -0
  53. package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +667 -0
  54. package/templates/dynamics-365-starter/src/components/AccountForm.css +71 -0
  55. package/templates/dynamics-365-starter/src/components/AccountForm.tsx +541 -0
  56. package/templates/dynamics-365-starter/src/components/AccountManagement.css +86 -0
  57. package/templates/dynamics-365-starter/src/components/AccountManagement.tsx +370 -0
  58. package/templates/dynamics-365-starter/src/components/ContactForm.tsx +149 -63
  59. package/templates/dynamics-365-starter/src/components/ContactManagement.tsx +153 -63
  60. package/templates/dynamics-365-starter/src/components/Logging/LogDialog.tsx +291 -0
  61. package/templates/dynamics-365-starter/src/components/Logging/LoggingContext.tsx +166 -0
  62. package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.css +192 -0
  63. package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.tsx +177 -0
  64. package/templates/dynamics-365-starter/src/components/Logging/LoggingProvider.tsx +3 -0
  65. package/templates/dynamics-365-starter/src/components/Logging/logger.ts +193 -0
  66. package/templates/dynamics-365-starter/src/constants/account.ts +410 -0
  67. package/templates/dynamics-365-starter/src/constants/contact.ts +362 -0
  68. package/templates/dynamics-365-starter/src/examples/README.md +52 -0
  69. package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +625 -0
  70. package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +545 -0
  71. package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +722 -0
  72. package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +662 -0
  73. package/templates/dynamics-365-starter/src/index.tsx +107 -19
  74. package/templates/dynamics-365-starter/src/models/Account.ts +480 -0
  75. package/templates/dynamics-365-starter/src/models/BaseEntity.ts +204 -0
  76. package/templates/dynamics-365-starter/src/models/Contact.ts +580 -0
  77. package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +519 -0
  78. package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +456 -0
  79. package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +406 -0
  80. package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +578 -0
  81. package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +629 -0
  82. package/templates/dynamics-365-starter/src/pcf/ContactControlWrapper.tsx +75 -22
  83. package/templates/dynamics-365-starter/src/pcf/MultiEntityControlWrapper.tsx +205 -0
  84. package/templates/dynamics-365-starter/src/providers/DynamicsProvider.tsx +297 -80
  85. package/templates/dynamics-365-starter/src/services/MockApiService.ts +260 -0
  86. package/templates/dynamics-365-starter/src/services/ServiceFactory.ts +65 -0
  87. package/templates/dynamics-365-starter/src/services/XrmApiService.ts +213 -0
  88. package/templates/dynamics-365-starter/src/styles/index.css +74 -7
  89. package/templates/dynamics-365-starter/tools/entity-generator/index.js +168 -0
  90. package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +124 -0
  91. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +283 -0
  92. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +275 -0
  93. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +204 -0
  94. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +413 -0
  95. package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +250 -0
  96. package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +410 -0
  97. package/templates/dynamics-365-starter/tools/metadata-sync/index.js +512 -0
  98. package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +675 -0
  99. package/templates/dynamics-365-starter/tsconfig.json +11 -8
  100. package/templates/dynamics-365-starter/webpack.config.js +8 -9
  101. package/templates/power-pages-starter/README.md +7 -1
  102. package/templates/power-pages-starter/public/index.html +8 -11
  103. package/templates/power-pages-starter/src/components/ContactForm.tsx +60 -41
  104. package/templates/power-pages-starter/src/index.tsx +3 -3
  105. package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +46 -23
  106. package/templates/power-pages-starter/tsconfig.json +3 -9
  107. package/templates/power-pages-starter/webpack.config.js +8 -3
@@ -0,0 +1,204 @@
1
+ /* {{displayName}} Management Component Styles */
2
+ /* Generated by Entity Generator */
3
+ /* Replace 'sample-entity' with your actual entity name during template processing */
4
+
5
+ .sample-entity-management {
6
+ padding: 20px;
7
+ max-width: 100%;
8
+ min-height: 400px;
9
+ }
10
+
11
+ .sample-entity-management-title {
12
+ margin: 0 0 20px 0;
13
+ font-size: 24px;
14
+ font-weight: 600;
15
+ color: #323130;
16
+ }
17
+
18
+ .sample-entity-management-command-bar {
19
+ margin-bottom: 16px;
20
+ border-bottom: 1px solid #edebe9;
21
+ padding-bottom: 8px;
22
+ }
23
+
24
+ .sample-entity-management-loading {
25
+ display: flex;
26
+ justify-content: center;
27
+ align-items: center;
28
+ min-height: 200px;
29
+ flex-direction: column;
30
+ }
31
+
32
+ .sample-entity-management-list {
33
+ margin-top: 10px;
34
+ }
35
+
36
+ .sample-entity-management-name-link {
37
+ color: #0078d4;
38
+ cursor: pointer;
39
+ text-decoration: none;
40
+ font-weight: 500;
41
+ }
42
+
43
+ .sample-entity-management-name-link:hover {
44
+ text-decoration: underline;
45
+ color: #106ebe;
46
+ }
47
+
48
+ /* Responsive design */
49
+ @media (max-width: 768px) {
50
+ .sample-entity-management {
51
+ padding: 10px;
52
+ }
53
+
54
+ .sample-entity-management-title {
55
+ font-size: 20px;
56
+ margin-bottom: 15px;
57
+ }
58
+ }
59
+
60
+ /* List row hover effects */
61
+ .sample-entity-management-list .ms-DetailsRow:hover {
62
+ background-color: #f8f9fa;
63
+ }
64
+
65
+ .sample-entity-management-list .ms-DetailsRow.is-selected {
66
+ background-color: #deecf9;
67
+ }
68
+
69
+ /* Command bar customizations */
70
+ .sample-entity-management-command-bar .ms-CommandBar {
71
+ background-color: transparent;
72
+ }
73
+
74
+ .sample-entity-management-command-bar .ms-CommandBarItem-link {
75
+ color: #323130;
76
+ }
77
+
78
+ .sample-entity-management-command-bar .ms-CommandBarItem-link:hover {
79
+ background-color: #f3f2f1;
80
+ }
81
+
82
+ .sample-entity-management-command-bar .ms-CommandBarItem-link:disabled {
83
+ color: #a19f9d;
84
+ }
85
+
86
+ /* Loading spinner */
87
+ .sample-entity-management-loading .ms-Spinner-label {
88
+ margin-top: 10px;
89
+ color: #605e5c;
90
+ }
91
+
92
+ /* Message bar styling */
93
+ .sample-entity-management .ms-MessageBar {
94
+ margin-bottom: 16px;
95
+ }
96
+
97
+ /* Panel content spacing */
98
+ .sample-entity-management .ms-Panel-main .ms-Panel-contentInner {
99
+ padding: 0 24px 20px 24px;
100
+ }
101
+
102
+ /* Details list column headers */
103
+ .sample-entity-management-list .ms-DetailsHeader-cell {
104
+ font-weight: 600;
105
+ color: #323130;
106
+ }
107
+
108
+ /* Priority column styling */
109
+ .sample-entity-management-list .priority-high {
110
+ color: #d13438;
111
+ font-weight: 600;
112
+ }
113
+
114
+ .sample-entity-management-list .priority-medium {
115
+ color: #ff8c00;
116
+ font-weight: 500;
117
+ }
118
+
119
+ .sample-entity-management-list .priority-low {
120
+ color: #107c10;
121
+ }
122
+
123
+ .sample-entity-management-list .priority-critical {
124
+ color: #a4262c;
125
+ font-weight: 700;
126
+ }
127
+
128
+ /* Date column styling */
129
+ .sample-entity-management-list .date-column {
130
+ color: #605e5c;
131
+ font-size: 13px;
132
+ }
133
+
134
+ /* Empty state */
135
+ .sample-entity-management-empty {
136
+ text-align: center;
137
+ padding: 40px 20px;
138
+ color: #605e5c;
139
+ }
140
+
141
+ .sample-entity-management-empty-icon {
142
+ font-size: 48px;
143
+ color: #a19f9d;
144
+ margin-bottom: 16px;
145
+ }
146
+
147
+ .sample-entity-management-empty-title {
148
+ font-size: 18px;
149
+ font-weight: 600;
150
+ margin-bottom: 8px;
151
+ color: #323130;
152
+ }
153
+
154
+ .sample-entity-management-empty-description {
155
+ font-size: 14px;
156
+ margin-bottom: 20px;
157
+ }
158
+
159
+ /* Error state */
160
+ .sample-entity-management-error {
161
+ text-align: center;
162
+ padding: 40px 20px;
163
+ }
164
+
165
+ .sample-entity-management-error-icon {
166
+ font-size: 48px;
167
+ color: #d13438;
168
+ margin-bottom: 16px;
169
+ }
170
+
171
+ .sample-entity-management-error-title {
172
+ font-size: 18px;
173
+ font-weight: 600;
174
+ margin-bottom: 8px;
175
+ color: #d13438;
176
+ }
177
+
178
+ .sample-entity-management-error-description {
179
+ font-size: 14px;
180
+ color: #605e5c;
181
+ margin-bottom: 20px;
182
+ }
183
+
184
+ /* Accessibility improvements */
185
+ .sample-entity-management-name-link:focus {
186
+ outline: 2px solid #0078d4;
187
+ outline-offset: 2px;
188
+ }
189
+
190
+ /* Print styles */
191
+ @media print {
192
+ .sample-entity-management-command-bar {
193
+ display: none;
194
+ }
195
+
196
+ .sample-entity-management {
197
+ padding: 0;
198
+ }
199
+
200
+ .sample-entity-management-title {
201
+ font-size: 18px;
202
+ margin-bottom: 10px;
203
+ }
204
+ }
@@ -0,0 +1,413 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ DetailsList,
4
+ IColumn,
5
+ SelectionMode,
6
+ CommandBar,
7
+ ICommandBarItemProps,
8
+ MessageBar,
9
+ MessageBarType,
10
+ Spinner,
11
+ SpinnerSize,
12
+ Panel,
13
+ PanelType,
14
+ DefaultButton,
15
+ PrimaryButton,
16
+ Dialog,
17
+ DialogType,
18
+ DialogFooter
19
+ } from '@fluentui/react';
20
+ // Replace 'SampleEntity' with your actual entity PascalCase name during template processing
21
+ // import { SampleEntity, ISampleEntity } from '../models/SampleEntity';
22
+ // import { SampleEntityForm } from './SampleEntityForm';
23
+ // import { SampleEntityConstants } from '../constants/sample-entity';
24
+ // import { useDynamicsApi } from '../providers/DynamicsProvider';
25
+ // import { Logger } from './Logging/logger';
26
+ import './SampleEntityManagement.css';
27
+
28
+ // Template base entity interface - replace with actual BaseEntity during generation
29
+ interface BaseEntity {
30
+ id?: string;
31
+ createdOn?: Date;
32
+ modifiedOn?: Date;
33
+ [key: string]: any;
34
+ }
35
+
36
+ // Template Logger - replace with actual Logger during generation
37
+ const Logger = {
38
+ log: (message: string, source?: string) => console.log(message, source),
39
+ warn: (message: string, source?: string) => console.warn(message, source),
40
+ error: (message: string, source?: string, error?: any) => console.error(message, source, error)
41
+ };
42
+
43
+ // Template API service hook - replace with actual useDynamicsApi during generation
44
+ const useDynamicsApi = () => ({
45
+ apiService: {
46
+ retrieveMultipleRecords: async () => [],
47
+ createRecord: async () => ({}),
48
+ updateRecord: async () => ({}),
49
+ deleteRecord: async () => ({})
50
+ }
51
+ });
52
+
53
+ // Template interfaces - replace with actual entity interface during generation
54
+ interface ISampleEntity {
55
+ id?: string;
56
+ name?: string;
57
+ description?: string;
58
+ priority?: number;
59
+ dueDate?: Date;
60
+ amount?: number;
61
+ isActive?: boolean;
62
+ createdOn?: Date;
63
+ }
64
+
65
+ class SampleEntity implements BaseEntity, ISampleEntity {
66
+ id?: string;
67
+ name?: string;
68
+ description?: string;
69
+ priority?: number;
70
+ dueDate?: Date;
71
+ amount?: number;
72
+ isActive?: boolean;
73
+ createdOn?: Date;
74
+ modifiedOn?: Date;
75
+
76
+ constructor(data: Partial<ISampleEntity> = {}) {
77
+ Object.assign(this, data);
78
+ }
79
+
80
+ static async retrieveByFilter(apiService: any, filter?: string, maxRecords?: number): Promise<SampleEntity[]> {
81
+ // Template implementation - replace during generation
82
+ return [];
83
+ }
84
+
85
+ static async create(apiService: any, entity: SampleEntity): Promise<SampleEntity> {
86
+ // Template implementation - replace during generation
87
+ return entity;
88
+ }
89
+
90
+ async update(apiService: any): Promise<void> {
91
+ // Template implementation - replace during generation
92
+ }
93
+
94
+ async delete(apiService: any): Promise<void> {
95
+ // Template implementation - replace during generation
96
+ }
97
+ }
98
+
99
+ // Template constants - replace with actual constants during generation
100
+ const SampleEntityConstants = {
101
+ PrimaryNameAttribute: 'name',
102
+ Description: 'description',
103
+ Priority: 'priority',
104
+ CreatedOn: 'createdOn',
105
+ FieldDisplayNames: {
106
+ name: 'Name',
107
+ description: 'Description',
108
+ priority: 'Priority',
109
+ createdOn: 'Created On'
110
+ },
111
+ PriorityOptions: {
112
+ Low: 1,
113
+ Medium: 2,
114
+ High: 3,
115
+ Critical: 4
116
+ }
117
+ };
118
+
119
+ // Template form component - replace with actual form during generation
120
+ const SampleEntityForm: React.FC<{
121
+ sampleEntity?: SampleEntity | null;
122
+ onSave: (data: Partial<ISampleEntity>) => Promise<void>;
123
+ onCancel: () => void;
124
+ }> = ({ sampleEntity, onSave, onCancel }) => {
125
+ return (
126
+ <div>
127
+ <p>Template Form Component - Replace during generation</p>
128
+ <button onClick={() => onSave({})}>Save</button>
129
+ <button onClick={onCancel}>Cancel</button>
130
+ </div>
131
+ );
132
+ };
133
+
134
+ interface SampleEntityManagementProps {
135
+ title?: string;
136
+ showCommandBar?: boolean;
137
+ maxRecords?: number;
138
+ filter?: string;
139
+ onItemSelected?: (item: SampleEntity) => void;
140
+ }
141
+
142
+ export const SampleEntityManagement: React.FC<SampleEntityManagementProps> = ({
143
+ title = "sampleEntities",
144
+ showCommandBar = true,
145
+ maxRecords = 100,
146
+ filter,
147
+ onItemSelected
148
+ }) => {
149
+ const { apiService } = useDynamicsApi();
150
+ const [sampleEntities, setSampleEntities] = useState<SampleEntity[]>([]);
151
+ const [loading, setLoading] = useState(true);
152
+ const [error, setError] = useState<string | null>(null);
153
+ const [selectedItem, setSelectedItem] = useState<SampleEntity | null>(null);
154
+ const [showPanel, setShowPanel] = useState(false);
155
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
156
+ const [isEditing, setIsEditing] = useState(false);
157
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
158
+
159
+ // Load sampleEntities data
160
+ const loadSampleEntities = useCallback(async () => {
161
+ try {
162
+ setLoading(true);
163
+ setError(null);
164
+ Logger.log('Loading sampleEntities...');
165
+
166
+ const data = await SampleEntity.retrieveByFilter(apiService, filter, maxRecords);
167
+ setSampleEntities(data);
168
+ Logger.log(`Loaded ${data.length} sampleEntities`);
169
+ } catch (err) {
170
+ const errorMessage = err instanceof Error ? err.message : 'Failed to load sampleEntities';
171
+ setError(errorMessage);
172
+ Logger.error('Error loading sampleEntities:', err);
173
+ } finally {
174
+ setLoading(false);
175
+ }
176
+ }, [apiService, filter, maxRecords]);
177
+
178
+ useEffect(() => {
179
+ loadSampleEntities();
180
+ }, [loadSampleEntities, refreshTrigger]);
181
+
182
+ // Handle item selection
183
+ const handleItemClick = (item: SampleEntity) => {
184
+ setSelectedItem(item);
185
+ if (onItemSelected) {
186
+ onItemSelected(item);
187
+ }
188
+ };
189
+
190
+ // Handle new sampleEntity
191
+ const handleNew = () => {
192
+ setSelectedItem(null);
193
+ setIsEditing(false);
194
+ setShowPanel(true);
195
+ };
196
+
197
+ // Handle edit sampleEntity
198
+ const handleEdit = () => {
199
+ if (selectedItem) {
200
+ setIsEditing(true);
201
+ setShowPanel(true);
202
+ }
203
+ };
204
+
205
+ // Handle delete sampleEntity
206
+ const handleDelete = () => {
207
+ if (selectedItem) {
208
+ setShowDeleteDialog(true);
209
+ }
210
+ };
211
+
212
+ // Confirm delete
213
+ const confirmDelete = async () => {
214
+ if (selectedItem) {
215
+ try {
216
+ await selectedItem.delete(apiService);
217
+ setShowDeleteDialog(false);
218
+ setSelectedItem(null);
219
+ setRefreshTrigger(prev => prev + 1);
220
+ Logger.log(`Sample Entity deleted successfully`);
221
+ } catch (err) {
222
+ const errorMessage = err instanceof Error ? err.message : 'Failed to delete Sample Entity';
223
+ setError(errorMessage);
224
+ Logger.error('Error deleting Sample Entity:', err);
225
+ }
226
+ }
227
+ };
228
+
229
+ // Handle save
230
+ const handleSave = async (sampleEntityData: Partial<ISampleEntity>) => {
231
+ try {
232
+ if (isEditing && selectedItem) {
233
+ // Update existing
234
+ Object.assign(selectedItem, sampleEntityData);
235
+ await selectedItem.update(apiService);
236
+ Logger.log(`Sample Entity updated successfully`);
237
+ } else {
238
+ // Create new
239
+ const newSampleEntity = new SampleEntity(sampleEntityData);
240
+ await SampleEntity.create(apiService, newSampleEntity);
241
+ Logger.log(`Sample Entity created successfully`);
242
+ }
243
+
244
+ setShowPanel(false);
245
+ setSelectedItem(null);
246
+ setRefreshTrigger(prev => prev + 1);
247
+ } catch (err) {
248
+ const errorMessage = err instanceof Error ? err.message : `Failed to save Sample Entity`;
249
+ setError(errorMessage);
250
+ Logger.error('Error saving Sample Entity:', err);
251
+ }
252
+ };
253
+
254
+ // Handle panel dismiss
255
+ const handlePanelDismiss = () => {
256
+ setShowPanel(false);
257
+ setSelectedItem(null);
258
+ setIsEditing(false);
259
+ };
260
+
261
+ // Define columns
262
+ const columns: IColumn[] = [
263
+ {
264
+ key: 'name',
265
+ name: SampleEntityConstants.FieldDisplayNames[SampleEntityConstants.PrimaryNameAttribute],
266
+ fieldName: SampleEntityConstants.PrimaryNameAttribute,
267
+ minWidth: 150,
268
+ maxWidth: 250,
269
+ isResizable: true,
270
+ onRender: (item: SampleEntity) => (
271
+ <span
272
+ className="sample-entity-management-name-link"
273
+ onClick={() => handleItemClick(item)}
274
+ >
275
+ {item[SampleEntityConstants.PrimaryNameAttribute]}
276
+ </span>
277
+ )
278
+ },
279
+ {
280
+ key: 'description',
281
+ name: SampleEntityConstants.FieldDisplayNames[SampleEntityConstants.Description],
282
+ fieldName: SampleEntityConstants.Description,
283
+ minWidth: 200,
284
+ maxWidth: 300,
285
+ isResizable: true
286
+ },
287
+ {
288
+ key: 'priority',
289
+ name: SampleEntityConstants.FieldDisplayNames[SampleEntityConstants.Priority],
290
+ fieldName: SampleEntityConstants.Priority,
291
+ minWidth: 100,
292
+ maxWidth: 150,
293
+ isResizable: true,
294
+ onRender: (item: SampleEntity) => {
295
+ const priority = item[SampleEntityConstants.Priority];
296
+ const priorityText = Object.keys(SampleEntityConstants.PriorityOptions).find(
297
+ key => SampleEntityConstants.PriorityOptions[key as keyof typeof SampleEntityConstants.PriorityOptions] === priority
298
+ );
299
+ return <span>{priorityText || priority}</span>;
300
+ }
301
+ },
302
+ {
303
+ key: 'createdOn',
304
+ name: SampleEntityConstants.FieldDisplayNames[SampleEntityConstants.CreatedOn],
305
+ fieldName: 'createdOn',
306
+ minWidth: 120,
307
+ maxWidth: 180,
308
+ isResizable: true,
309
+ onRender: (item: SampleEntity) => (
310
+ <span>{item.createdOn?.toLocaleDateString()}</span>
311
+ )
312
+ }
313
+ ];
314
+
315
+ // Command bar items
316
+ const commandBarItems: ICommandBarItemProps[] = showCommandBar ? [
317
+ {
318
+ key: 'new',
319
+ text: 'New Sample Entity',
320
+ iconProps: { iconName: 'Add' },
321
+ onClick: handleNew
322
+ },
323
+ {
324
+ key: 'edit',
325
+ text: 'Edit',
326
+ iconProps: { iconName: 'Edit' },
327
+ disabled: !selectedItem,
328
+ onClick: handleEdit
329
+ },
330
+ {
331
+ key: 'delete',
332
+ text: 'Delete',
333
+ iconProps: { iconName: 'Delete' },
334
+ disabled: !selectedItem,
335
+ onClick: handleDelete
336
+ },
337
+ {
338
+ key: 'refresh',
339
+ text: 'Refresh',
340
+ iconProps: { iconName: 'Refresh' },
341
+ onClick: () => setRefreshTrigger(prev => prev + 1)
342
+ }
343
+ ] : [];
344
+
345
+ return (
346
+ <div className="sample-entity-management">
347
+ <h2 className="sample-entity-management-title">{title}</h2>
348
+
349
+ {error && (
350
+ <MessageBar
351
+ messageBarType={MessageBarType.error}
352
+ onDismiss={() => setError(null)}
353
+ >
354
+ {error}
355
+ </MessageBar>
356
+ )}
357
+
358
+ {showCommandBar && (
359
+ <CommandBar
360
+ items={commandBarItems}
361
+ className="sample-entity-management-command-bar"
362
+ />
363
+ )}
364
+
365
+ {loading ? (
366
+ <div className="sample-entity-management-loading">
367
+ <Spinner size={SpinnerSize.large} label="Loading sampleEntities..." />
368
+ </div>
369
+ ) : (
370
+ <DetailsList
371
+ items={sampleEntities}
372
+ columns={columns}
373
+ setKey="set"
374
+ layoutMode={0}
375
+ selectionMode={SelectionMode.single}
376
+ onActiveItemChanged={setSelectedItem}
377
+ className="sample-entity-management-list"
378
+ />
379
+ )}
380
+
381
+ {/* Form Panel */}
382
+ <Panel
383
+ headerText={isEditing ? `Edit Sample Entity` : `New Sample Entity`}
384
+ isOpen={showPanel}
385
+ onDismiss={handlePanelDismiss}
386
+ type={PanelType.medium}
387
+ closeButtonAriaLabel="Close"
388
+ >
389
+ <SampleEntityForm
390
+ sampleEntity={selectedItem}
391
+ onSave={handleSave}
392
+ onCancel={handlePanelDismiss}
393
+ />
394
+ </Panel>
395
+
396
+ {/* Delete Confirmation Dialog */}
397
+ <Dialog
398
+ hidden={!showDeleteDialog}
399
+ onDismiss={() => setShowDeleteDialog(false)}
400
+ dialogContentProps={{
401
+ type: DialogType.normal,
402
+ title: 'Confirm Delete',
403
+ subText: `Are you sure you want to delete "${selectedItem?.[SampleEntityConstants.PrimaryNameAttribute]}"?`
404
+ }}
405
+ >
406
+ <DialogFooter>
407
+ <PrimaryButton onClick={confirmDelete} text="Delete" />
408
+ <DefaultButton onClick={() => setShowDeleteDialog(false)} text="Cancel" />
409
+ </DialogFooter>
410
+ </Dialog>
411
+ </div>
412
+ );
413
+ };