@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.
- package/bin/create-dynamics-app.js +1 -1
- package/dist/index.js +140 -15
- package/dist/index.js.map +1 -1
- package/dist/utils/consultingHelpers.d.ts +13 -0
- package/dist/utils/consultingHelpers.d.ts.map +1 -0
- package/dist/utils/consultingHelpers.js +569 -0
- package/dist/utils/consultingHelpers.js.map +1 -0
- package/dist/utils/copyTemplate.d.ts.map +1 -1
- package/dist/utils/copyTemplate.js.map +1 -1
- package/dist/utils/initGit.d.ts.map +1 -1
- package/dist/utils/initGit.js.map +1 -1
- package/dist/utils/installDependencies.d.ts.map +1 -1
- package/dist/utils/installDependencies.js +3 -2
- package/dist/utils/installDependencies.js.map +1 -1
- package/dist/utils/updatePackageJson.d.ts +1 -1
- package/dist/utils/updatePackageJson.d.ts.map +1 -1
- package/dist/utils/updatePackageJson.js +11 -1
- package/dist/utils/updatePackageJson.js.map +1 -1
- package/package.json +1 -1
- package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +302 -0
- package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +305 -0
- package/templates/dynamics-365-starter/README.md +566 -137
- package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +507 -0
- package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +372 -0
- package/templates/dynamics-365-starter/deployment/README.md +484 -0
- package/templates/dynamics-365-starter/deployment/pipelines/README.md +375 -0
- package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +330 -0
- package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +422 -0
- package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +636 -0
- package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +417 -0
- package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +582 -0
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +486 -0
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +567 -0
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +703 -0
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +671 -0
- package/templates/dynamics-365-starter/docs/ARCHITECTURE_OVERVIEW.md +506 -0
- package/templates/dynamics-365-starter/docs/BEST_PRACTICES.md +723 -0
- package/templates/dynamics-365-starter/docs/MIGRATION_GUIDE.md +447 -0
- package/templates/dynamics-365-starter/docs/team-standards/README.md +273 -0
- package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +577 -0
- package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +359 -0
- package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +700 -0
- package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +736 -0
- package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +727 -0
- package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +758 -0
- package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +878 -0
- package/templates/dynamics-365-starter/package.json +22 -1
- package/templates/dynamics-365-starter/public/index.html +8 -11
- package/templates/dynamics-365-starter/scripts/custom-build.js +255 -0
- package/templates/dynamics-365-starter/src/client-project-template/README.md +234 -0
- package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +114 -0
- package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +186 -0
- package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +667 -0
- package/templates/dynamics-365-starter/src/components/AccountForm.css +71 -0
- package/templates/dynamics-365-starter/src/components/AccountForm.tsx +541 -0
- package/templates/dynamics-365-starter/src/components/AccountManagement.css +86 -0
- package/templates/dynamics-365-starter/src/components/AccountManagement.tsx +370 -0
- package/templates/dynamics-365-starter/src/components/ContactForm.tsx +149 -63
- package/templates/dynamics-365-starter/src/components/ContactManagement.tsx +153 -63
- package/templates/dynamics-365-starter/src/components/Logging/LogDialog.tsx +291 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingContext.tsx +166 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.css +192 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.tsx +177 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingProvider.tsx +3 -0
- package/templates/dynamics-365-starter/src/components/Logging/logger.ts +193 -0
- package/templates/dynamics-365-starter/src/constants/account.ts +410 -0
- package/templates/dynamics-365-starter/src/constants/contact.ts +362 -0
- package/templates/dynamics-365-starter/src/examples/README.md +52 -0
- package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +625 -0
- package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +545 -0
- package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +722 -0
- package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +662 -0
- package/templates/dynamics-365-starter/src/index.tsx +107 -19
- package/templates/dynamics-365-starter/src/models/Account.ts +480 -0
- package/templates/dynamics-365-starter/src/models/BaseEntity.ts +204 -0
- package/templates/dynamics-365-starter/src/models/Contact.ts +580 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +519 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +456 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +406 -0
- package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +578 -0
- package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +629 -0
- package/templates/dynamics-365-starter/src/pcf/ContactControlWrapper.tsx +75 -22
- package/templates/dynamics-365-starter/src/pcf/MultiEntityControlWrapper.tsx +205 -0
- package/templates/dynamics-365-starter/src/providers/DynamicsProvider.tsx +297 -80
- package/templates/dynamics-365-starter/src/services/MockApiService.ts +260 -0
- package/templates/dynamics-365-starter/src/services/ServiceFactory.ts +65 -0
- package/templates/dynamics-365-starter/src/services/XrmApiService.ts +213 -0
- package/templates/dynamics-365-starter/src/styles/index.css +74 -7
- package/templates/dynamics-365-starter/tools/entity-generator/index.js +168 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +124 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +283 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +275 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +204 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +413 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +250 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +410 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/index.js +512 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +675 -0
- package/templates/dynamics-365-starter/tsconfig.json +11 -8
- package/templates/dynamics-365-starter/webpack.config.js +8 -9
- package/templates/power-pages-starter/README.md +7 -1
- package/templates/power-pages-starter/public/index.html +8 -11
- package/templates/power-pages-starter/src/components/ContactForm.tsx +60 -41
- package/templates/power-pages-starter/src/index.tsx +3 -3
- package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +46 -23
- package/templates/power-pages-starter/tsconfig.json +3 -9
- package/templates/power-pages-starter/webpack.config.js +8 -3
package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css
ADDED
|
@@ -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
|
+
}
|
package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx
ADDED
|
@@ -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
|
+
};
|