@khester/create-dynamics-app 1.1.0 → 2.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/README.md +74 -0
- package/dist/artifacts/registry.d.ts +18 -0
- package/dist/artifacts/registry.d.ts.map +1 -0
- package/dist/artifacts/registry.js +340 -0
- package/dist/artifacts/registry.js.map +1 -0
- package/dist/artifacts/types.d.ts +122 -0
- package/dist/artifacts/types.d.ts.map +1 -0
- package/dist/artifacts/types.js +7 -0
- package/dist/artifacts/types.js.map +1 -0
- package/dist/artifacts/validators.d.ts +16 -0
- package/dist/artifacts/validators.d.ts.map +1 -0
- package/dist/artifacts/validators.js +45 -0
- package/dist/artifacts/validators.js.map +1 -0
- package/dist/fromDesign.d.ts +5 -0
- package/dist/fromDesign.d.ts.map +1 -0
- package/dist/fromDesign.js +98 -0
- package/dist/fromDesign.js.map +1 -0
- package/dist/index.js +129 -177
- package/dist/index.js.map +1 -1
- package/dist/injectDevTools.d.ts +28 -0
- package/dist/injectDevTools.d.ts.map +1 -0
- package/dist/injectDevTools.js +148 -0
- package/dist/injectDevTools.js.map +1 -0
- package/dist/scaffold.d.ts +48 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +180 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/templatePlan.d.ts +3 -0
- package/dist/templatePlan.d.ts.map +1 -0
- package/dist/templatePlan.js +43 -0
- package/dist/templatePlan.js.map +1 -0
- package/dist/utils/copyTemplate.d.ts +13 -1
- package/dist/utils/copyTemplate.d.ts.map +1 -1
- package/dist/utils/copyTemplate.js +98 -4
- package/dist/utils/copyTemplate.js.map +1 -1
- package/dist/utils/updatePackageJson.d.ts +11 -1
- package/dist/utils/updatePackageJson.d.ts.map +1 -1
- package/dist/utils/updatePackageJson.js +12 -10
- package/dist/utils/updatePackageJson.js.map +1 -1
- package/package.json +10 -7
- package/templates/_shared/dev-tools/auth/get-token.js +72 -0
- package/templates/_shared/dev-tools/dev/mock-xrm.js +42 -0
- package/templates/_shared/dev-tools/metadata-sync/index.js +152 -0
- package/templates/_shared/dev-tools/smoke/test-retrieve.js +44 -0
- package/templates/dialog-form/README.md +27 -0
- package/templates/dialog-form/_variants/App.v8.tsx +39 -0
- package/templates/dialog-form/_variants/App.v9.tsx +41 -0
- package/templates/dialog-form/gitignore +5 -0
- package/templates/dialog-form/package.json +27 -0
- package/templates/dialog-form/public/index.html +11 -0
- package/templates/dialog-form/src/index.tsx +10 -0
- package/templates/dialog-form/src/services/dataverse.ts +30 -0
- package/templates/dialog-form/tsconfig.json +15 -0
- package/templates/dialog-form/webpack.config.js +17 -0
- package/templates/grid-customizer/README.md +28 -0
- package/templates/grid-customizer/gitignore +4 -0
- package/templates/grid-customizer/package.json +25 -0
- package/templates/grid-customizer/src/GridCustomizer.ts +28 -0
- package/templates/grid-customizer/src/cell-renderers.tsx +35 -0
- package/templates/grid-customizer/src/index.ts +4 -0
- package/templates/grid-customizer/src/types/grid-types.ts +30 -0
- package/templates/grid-customizer/src/utils/color-utils.ts +24 -0
- package/templates/grid-customizer/tsconfig.json +15 -0
- package/templates/grid-customizer/webpack.config.js +17 -0
- package/templates/pcf-dataset/ControlManifest.Input.xml +16 -0
- package/templates/pcf-dataset/README.md +21 -0
- package/templates/pcf-dataset/gitignore +5 -0
- package/templates/pcf-dataset/index.ts +39 -0
- package/templates/pcf-dataset/package.json +30 -0
- package/templates/pcf-dataset/strings/{{componentName}}.1033.resx +47 -0
- package/templates/pcf-dataset/tsconfig.json +8 -0
- package/templates/pcf-dataset/{{componentName}}Component.tsx +39 -0
- package/templates/pcf-field/ControlManifest.Input.xml +17 -0
- package/templates/pcf-field/README.md +95 -0
- package/templates/pcf-field/_variants/ValueInput.boolean.tsx +24 -0
- package/templates/pcf-field/_variants/ValueInput.date.tsx +27 -0
- package/templates/pcf-field/_variants/ValueInput.number.tsx +35 -0
- package/templates/pcf-field/_variants/ValueInput.text.tsx +27 -0
- package/templates/pcf-field/gitignore +5 -0
- package/templates/pcf-field/index.ts +61 -0
- package/templates/pcf-field/package.json +30 -0
- package/templates/pcf-field/strings/{{componentName}}.1033.resx +47 -0
- package/templates/pcf-field/tsconfig.json +8 -0
- package/templates/pcf-field/{{componentName}}Component.tsx +35 -0
- package/templates/power-pages-starter/gitignore +5 -0
- package/templates/react-custom-page/gitignore +5 -0
- package/templates/{dynamics-365-starter → react-custom-page}/package.json +3 -3
- package/templates/react-custom-page/tools/metadata-sync/index.js +152 -0
- package/templates/static-web-app/README.md +36 -0
- package/templates/static-web-app/_variants/App.v8.tsx +32 -0
- package/templates/static-web-app/_variants/App.v9.tsx +31 -0
- package/templates/static-web-app/api/host.json +12 -0
- package/templates/static-web-app/api/package.json +19 -0
- package/templates/static-web-app/api/src/functions/hello.ts +16 -0
- package/templates/static-web-app/api/tsconfig.json +14 -0
- package/templates/static-web-app/frontend/index.html +12 -0
- package/templates/static-web-app/frontend/package.json +23 -0
- package/templates/static-web-app/frontend/src/index.tsx +8 -0
- package/templates/static-web-app/frontend/tsconfig.json +16 -0
- package/templates/static-web-app/frontend/vite.config.ts +13 -0
- package/templates/static-web-app/gitignore +8 -0
- package/templates/static-web-app/package.json +15 -0
- package/templates/static-web-app/staticwebapp.config.json +7 -0
- package/templates/teams-app/README.md +27 -0
- package/templates/teams-app/_variants/graph.off.ts +7 -0
- package/templates/teams-app/_variants/graph.on.ts +22 -0
- package/templates/teams-app/appPackage/manifest.json +26 -0
- package/templates/teams-app/gitignore +5 -0
- package/templates/teams-app/index.html +12 -0
- package/templates/teams-app/package.json +26 -0
- package/templates/teams-app/src/App.tsx +25 -0
- package/templates/teams-app/src/index.tsx +8 -0
- package/templates/teams-app/tsconfig.json +16 -0
- package/templates/teams-app/vite.config.ts +9 -0
- package/templates/web-resource/README.md +39 -0
- package/templates/web-resource/_variants/App.v8.tsx +29 -0
- package/templates/web-resource/_variants/App.v9.tsx +28 -0
- package/templates/web-resource/gitignore +5 -0
- package/templates/web-resource/package.json +27 -0
- package/templates/web-resource/public/index.html +11 -0
- package/templates/web-resource/src/index.tsx +10 -0
- package/templates/web-resource/src/services/dataverse.ts +30 -0
- package/templates/web-resource/tsconfig.json +15 -0
- package/templates/web-resource/webpack.config.js +17 -0
- package/dist/utils/consultingHelpers.d.ts +0 -13
- package/dist/utils/consultingHelpers.d.ts.map +0 -1
- package/dist/utils/consultingHelpers.js +0 -569
- package/dist/utils/consultingHelpers.js.map +0 -1
- package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +0 -302
- package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +0 -305
- package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +0 -507
- package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +0 -372
- package/templates/dynamics-365-starter/deployment/pipelines/README.md +0 -375
- package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +0 -330
- package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +0 -422
- package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +0 -636
- package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +0 -417
- package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +0 -582
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +0 -486
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +0 -567
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +0 -703
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +0 -671
- package/templates/dynamics-365-starter/docs/team-standards/README.md +0 -273
- package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +0 -577
- package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +0 -359
- package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +0 -700
- package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +0 -736
- package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +0 -727
- package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +0 -758
- package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +0 -878
- package/templates/dynamics-365-starter/src/client-project-template/README.md +0 -234
- package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +0 -114
- package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +0 -186
- package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +0 -667
- package/templates/dynamics-365-starter/src/examples/README.md +0 -52
- package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +0 -625
- package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +0 -545
- package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +0 -722
- package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +0 -662
- package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +0 -519
- package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +0 -456
- package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +0 -406
- package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +0 -578
- package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +0 -629
- package/templates/dynamics-365-starter/tools/entity-generator/index.js +0 -168
- package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +0 -124
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +0 -283
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +0 -275
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +0 -204
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +0 -413
- package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +0 -250
- package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +0 -410
- package/templates/dynamics-365-starter/tools/metadata-sync/index.js +0 -512
- package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +0 -675
- /package/templates/{dynamics-365-starter → react-custom-page}/README.md +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/deployment/README.md +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/docs/ARCHITECTURE_OVERVIEW.md +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/docs/BEST_PRACTICES.md +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/docs/MIGRATION_GUIDE.md +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/public/index.html +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/scripts/custom-build.js +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.css +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.css +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.css +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.css +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LogDialog.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingContext.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.css +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingProvider.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/logger.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/account.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/contact.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/index.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Account.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/models/BaseEntity.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Contact.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/ContactControlWrapper.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/MultiEntityControlWrapper.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/providers/DynamicsProvider.tsx +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/services/MockApiService.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/services/ServiceFactory.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/services/XrmApiService.ts +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/src/styles/index.css +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/tsconfig.json +0 -0
- /package/templates/{dynamics-365-starter → react-custom-page}/webpack.config.js +0 -0
|
@@ -1,700 +0,0 @@
|
|
|
1
|
-
# Coding Standards
|
|
2
|
-
|
|
3
|
-
## Table of Contents
|
|
4
|
-
|
|
5
|
-
1. [General Principles](#general-principles)
|
|
6
|
-
2. [TypeScript Standards](#typescript-standards)
|
|
7
|
-
3. [React Component Standards](#react-component-standards)
|
|
8
|
-
4. [Entity Model Standards](#entity-model-standards)
|
|
9
|
-
5. [CSS and Styling Standards](#css-and-styling-standards)
|
|
10
|
-
6. [API and Service Standards](#api-and-service-standards)
|
|
11
|
-
7. [Error Handling Standards](#error-handling-standards)
|
|
12
|
-
8. [Testing Standards](#testing-standards)
|
|
13
|
-
9. [Documentation Standards](#documentation-standards)
|
|
14
|
-
10. [File Organization](#file-organization)
|
|
15
|
-
|
|
16
|
-
## General Principles
|
|
17
|
-
|
|
18
|
-
### Code Quality
|
|
19
|
-
|
|
20
|
-
- **Readability**: Code should be self-documenting
|
|
21
|
-
- **Consistency**: Follow established patterns throughout the codebase
|
|
22
|
-
- **Simplicity**: Choose simple solutions over complex ones
|
|
23
|
-
- **Maintainability**: Write code that's easy to modify and extend
|
|
24
|
-
- **Performance**: Consider performance implications of coding decisions
|
|
25
|
-
|
|
26
|
-
### Best Practices
|
|
27
|
-
|
|
28
|
-
- Use meaningful variable and function names
|
|
29
|
-
- Keep functions small and focused (single responsibility)
|
|
30
|
-
- Avoid deep nesting (max 3 levels)
|
|
31
|
-
- Use early returns to reduce complexity
|
|
32
|
-
- Prefer composition over inheritance
|
|
33
|
-
- Follow DRY (Don't Repeat Yourself) principle
|
|
34
|
-
|
|
35
|
-
## TypeScript Standards
|
|
36
|
-
|
|
37
|
-
### Type Definitions
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
// ✅ Good: Explicit interface definitions
|
|
41
|
-
interface IAccount {
|
|
42
|
-
id: string;
|
|
43
|
-
name: string;
|
|
44
|
-
revenue?: number;
|
|
45
|
-
createdOn: Date;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ❌ Bad: Using 'any' type
|
|
49
|
-
function processData(data: any): any {
|
|
50
|
-
return data.something;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ✅ Good: Proper type guards
|
|
54
|
-
function isAccount(obj: unknown): obj is IAccount {
|
|
55
|
-
return typeof obj === 'object' && obj !== null && 'id' in obj && 'name' in obj;
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Naming Conventions
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// Interfaces: PascalCase with 'I' prefix
|
|
63
|
-
interface IApiService {}
|
|
64
|
-
|
|
65
|
-
// Classes: PascalCase
|
|
66
|
-
class AccountService {}
|
|
67
|
-
|
|
68
|
-
// Functions and variables: camelCase
|
|
69
|
-
const fetchAccountData = async () => {};
|
|
70
|
-
|
|
71
|
-
// Constants: SCREAMING_SNAKE_CASE
|
|
72
|
-
const MAX_RETRY_ATTEMPTS = 3;
|
|
73
|
-
|
|
74
|
-
// Enums: PascalCase
|
|
75
|
-
enum EntityState {
|
|
76
|
-
Active = 0,
|
|
77
|
-
Inactive = 1,
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Generic Types
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
// ✅ Good: Descriptive generic constraints
|
|
85
|
-
interface IRepository<T extends BaseEntity> {
|
|
86
|
-
create(entity: T): Promise<T>;
|
|
87
|
-
update(id: string, entity: Partial<T>): Promise<T>;
|
|
88
|
-
delete(id: string): Promise<void>;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ✅ Good: Default generic parameters
|
|
92
|
-
interface IApiResponse<T = unknown> {
|
|
93
|
-
data: T;
|
|
94
|
-
success: boolean;
|
|
95
|
-
message?: string;
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## React Component Standards
|
|
100
|
-
|
|
101
|
-
### Component Structure
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
// ✅ Good: Functional component with proper typing
|
|
105
|
-
interface AccountFormProps {
|
|
106
|
-
account?: Account;
|
|
107
|
-
onSave: (account: Account) => Promise<void>;
|
|
108
|
-
onCancel: () => void;
|
|
109
|
-
readOnly?: boolean;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export const AccountForm: React.FC<AccountFormProps> = ({
|
|
113
|
-
account,
|
|
114
|
-
onSave,
|
|
115
|
-
onCancel,
|
|
116
|
-
readOnly = false
|
|
117
|
-
}) => {
|
|
118
|
-
// State declarations
|
|
119
|
-
const [formData, setFormData] = useState<Partial<IAccount>>({});
|
|
120
|
-
const [loading, setLoading] = useState(false);
|
|
121
|
-
const [errors, setErrors] = useState<string[]>([]);
|
|
122
|
-
|
|
123
|
-
// Effects
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (account) {
|
|
126
|
-
setFormData(account);
|
|
127
|
-
}
|
|
128
|
-
}, [account]);
|
|
129
|
-
|
|
130
|
-
// Event handlers
|
|
131
|
-
const handleSubmit = async (event: React.FormEvent) => {
|
|
132
|
-
event.preventDefault();
|
|
133
|
-
// Implementation
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Render
|
|
137
|
-
return (
|
|
138
|
-
<form onSubmit={handleSubmit}>
|
|
139
|
-
{/* JSX content */}
|
|
140
|
-
</form>
|
|
141
|
-
);
|
|
142
|
-
};
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Hooks Usage
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
// ✅ Good: Custom hooks for reusable logic
|
|
149
|
-
const useAccountData = (accountId: string) => {
|
|
150
|
-
const [account, setAccount] = useState<Account | null>(null);
|
|
151
|
-
const [loading, setLoading] = useState(true);
|
|
152
|
-
const [error, setError] = useState<string | null>(null);
|
|
153
|
-
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const loadAccount = async () => {
|
|
156
|
-
try {
|
|
157
|
-
setLoading(true);
|
|
158
|
-
const data = await AccountService.getById(accountId);
|
|
159
|
-
setAccount(data);
|
|
160
|
-
} catch (err) {
|
|
161
|
-
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
162
|
-
} finally {
|
|
163
|
-
setLoading(false);
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
loadAccount();
|
|
168
|
-
}, [accountId]);
|
|
169
|
-
|
|
170
|
-
return { account, loading, error };
|
|
171
|
-
};
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Event Handlers
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
// ✅ Good: Properly typed event handlers
|
|
178
|
-
const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
179
|
-
const { name, value } = event.target;
|
|
180
|
-
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
181
|
-
}, []);
|
|
182
|
-
|
|
183
|
-
const handleDropdownChange = useCallback(
|
|
184
|
-
(_: React.FormEvent<HTMLDivElement>, option?: IDropdownOption) => {
|
|
185
|
-
if (option) {
|
|
186
|
-
setSelectedValue(option.key as string);
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
[]
|
|
190
|
-
);
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
## Entity Model Standards
|
|
194
|
-
|
|
195
|
-
### Entity Class Structure
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
// ✅ Good: Well-structured entity class
|
|
199
|
-
export class Account extends BaseEntity implements IAccount {
|
|
200
|
-
// Properties with proper typing
|
|
201
|
-
public id: string = '';
|
|
202
|
-
public name: string = '';
|
|
203
|
-
public revenue?: number;
|
|
204
|
-
public createdOn?: Date;
|
|
205
|
-
public modifiedOn?: Date;
|
|
206
|
-
|
|
207
|
-
constructor(data?: Partial<IAccount>) {
|
|
208
|
-
super();
|
|
209
|
-
if (data) {
|
|
210
|
-
Object.assign(this, data);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Static factory methods
|
|
215
|
-
public static async create(apiService: IApiService, account: Account): Promise<Account> {
|
|
216
|
-
try {
|
|
217
|
-
logger.logInfo(`Creating account: ${account.name}`);
|
|
218
|
-
|
|
219
|
-
const validation = account.validate();
|
|
220
|
-
if (!validation.isValid) {
|
|
221
|
-
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const result = await apiService.createRecord(
|
|
225
|
-
AccountConstants.EntityLogicalName,
|
|
226
|
-
account.toCreateData()
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
return new Account({
|
|
230
|
-
...account,
|
|
231
|
-
id: result.id,
|
|
232
|
-
});
|
|
233
|
-
} catch (error) {
|
|
234
|
-
logger.logError('Error creating account:', error);
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Instance methods
|
|
240
|
-
public validate(): ValidationResult {
|
|
241
|
-
const errors: string[] = [];
|
|
242
|
-
|
|
243
|
-
if (!this.name?.trim()) {
|
|
244
|
-
errors.push('Name is required');
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (this.revenue !== undefined && this.revenue < 0) {
|
|
248
|
-
errors.push('Revenue cannot be negative');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
isValid: errors.length === 0,
|
|
253
|
-
errors,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private toCreateData(): Partial<IAccount> {
|
|
258
|
-
return {
|
|
259
|
-
name: this.name,
|
|
260
|
-
revenue: this.revenue,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Constants Structure
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
// ✅ Good: Comprehensive constants class
|
|
270
|
-
export class AccountConstants {
|
|
271
|
-
// Entity metadata
|
|
272
|
-
public static readonly EntityLogicalName = 'account';
|
|
273
|
-
public static readonly EntityCollectionName = 'accounts';
|
|
274
|
-
public static readonly PrimaryIdAttribute = 'accountid';
|
|
275
|
-
public static readonly PrimaryNameAttribute = 'name';
|
|
276
|
-
|
|
277
|
-
// Field names with metadata comments
|
|
278
|
-
/** Type: String, RequiredLevel: ApplicationRequired, MaxLength: 160 */
|
|
279
|
-
public static readonly Name = 'name';
|
|
280
|
-
|
|
281
|
-
/** Type: Money, RequiredLevel: None */
|
|
282
|
-
public static readonly Revenue = 'revenue';
|
|
283
|
-
|
|
284
|
-
/** Type: DateTime, ReadOnly: true */
|
|
285
|
-
public static readonly CreatedOn = 'createdon';
|
|
286
|
-
|
|
287
|
-
// Option set values
|
|
288
|
-
public static readonly StateCodeOptions = {
|
|
289
|
-
Active: 0,
|
|
290
|
-
Inactive: 1,
|
|
291
|
-
} as const;
|
|
292
|
-
|
|
293
|
-
// Display names for UI
|
|
294
|
-
public static readonly FieldDisplayNames = {
|
|
295
|
-
[this.Name]: 'Account Name',
|
|
296
|
-
[this.Revenue]: 'Annual Revenue',
|
|
297
|
-
[this.CreatedOn]: 'Created On',
|
|
298
|
-
} as const;
|
|
299
|
-
|
|
300
|
-
// Required fields array
|
|
301
|
-
public static readonly RequiredFields = [this.Name] as const;
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## CSS and Styling Standards
|
|
306
|
-
|
|
307
|
-
### CSS Modules
|
|
308
|
-
|
|
309
|
-
```css
|
|
310
|
-
/* ✅ Good: BEM-like naming with CSS modules */
|
|
311
|
-
.accountForm {
|
|
312
|
-
padding: 20px;
|
|
313
|
-
max-width: 600px;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
.accountForm__field {
|
|
317
|
-
margin-bottom: 16px;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
.accountForm__field--required {
|
|
321
|
-
position: relative;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.accountForm__field--required::after {
|
|
325
|
-
content: '*';
|
|
326
|
-
color: #d13438;
|
|
327
|
-
margin-left: 4px;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
.accountForm__actions {
|
|
331
|
-
display: flex;
|
|
332
|
-
justify-content: flex-end;
|
|
333
|
-
gap: 12px;
|
|
334
|
-
margin-top: 24px;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/* Responsive design */
|
|
338
|
-
@media (max-width: 768px) {
|
|
339
|
-
.accountForm {
|
|
340
|
-
padding: 16px;
|
|
341
|
-
max-width: 100%;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.accountForm__actions {
|
|
345
|
-
flex-direction: column;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Styling Conventions
|
|
351
|
-
|
|
352
|
-
- Use CSS Modules for component-specific styles
|
|
353
|
-
- Follow BEM-like naming convention
|
|
354
|
-
- Use semantic class names
|
|
355
|
-
- Implement responsive design principles
|
|
356
|
-
- Use CSS custom properties for theming
|
|
357
|
-
- Avoid inline styles except for dynamic values
|
|
358
|
-
|
|
359
|
-
## API and Service Standards
|
|
360
|
-
|
|
361
|
-
### Service Interface
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
// ✅ Good: Well-defined service interface
|
|
365
|
-
interface IApiService {
|
|
366
|
-
createRecord(entityName: string, data: Record<string, unknown>): Promise<ApiResponse>;
|
|
367
|
-
updateRecord(entityName: string, id: string, data: Record<string, unknown>): Promise<void>;
|
|
368
|
-
deleteRecord(entityName: string, id: string): Promise<void>;
|
|
369
|
-
retrieveRecord(entityName: string, id: string, fields?: string[]): Promise<ApiResponse>;
|
|
370
|
-
retrieveMultipleRecords(entityName: string, fetchXml: string): Promise<ApiResponse>;
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Service Implementation
|
|
375
|
-
|
|
376
|
-
```typescript
|
|
377
|
-
// ✅ Good: Proper error handling and logging
|
|
378
|
-
export class XrmApiService implements IApiService {
|
|
379
|
-
private readonly baseUrl: string;
|
|
380
|
-
private readonly timeout: number;
|
|
381
|
-
|
|
382
|
-
constructor(config: ApiConfig) {
|
|
383
|
-
this.baseUrl = config.baseUrl;
|
|
384
|
-
this.timeout = config.timeout || 30000;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
public async createRecord(
|
|
388
|
-
entityName: string,
|
|
389
|
-
data: Record<string, unknown>
|
|
390
|
-
): Promise<ApiResponse> {
|
|
391
|
-
try {
|
|
392
|
-
logger.logInfo(`Creating ${entityName} record`);
|
|
393
|
-
|
|
394
|
-
const response = await this.makeRequest('POST', `${entityName}`, data);
|
|
395
|
-
|
|
396
|
-
logger.logInfo(`Successfully created ${entityName} record`);
|
|
397
|
-
return response;
|
|
398
|
-
} catch (error) {
|
|
399
|
-
logger.logError(`Error creating ${entityName} record:`, error);
|
|
400
|
-
throw this.handleApiError(error);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
private async makeRequest(
|
|
405
|
-
method: string,
|
|
406
|
-
endpoint: string,
|
|
407
|
-
data?: Record<string, unknown>
|
|
408
|
-
): Promise<ApiResponse> {
|
|
409
|
-
const url = `${this.baseUrl}/${endpoint}`;
|
|
410
|
-
const options: RequestInit = {
|
|
411
|
-
method,
|
|
412
|
-
headers: {
|
|
413
|
-
'Content-Type': 'application/json',
|
|
414
|
-
Accept: 'application/json',
|
|
415
|
-
},
|
|
416
|
-
signal: AbortSignal.timeout(this.timeout),
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
if (data) {
|
|
420
|
-
options.body = JSON.stringify(data);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const response = await fetch(url, options);
|
|
424
|
-
|
|
425
|
-
if (!response.ok) {
|
|
426
|
-
throw new ApiError(`HTTP ${response.status}: ${response.statusText}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return response.json();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
private handleApiError(error: unknown): Error {
|
|
433
|
-
if (error instanceof ApiError) {
|
|
434
|
-
return error;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (error instanceof Error) {
|
|
438
|
-
return new ApiError(`API request failed: ${error.message}`);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return new ApiError('Unknown API error occurred');
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
## Error Handling Standards
|
|
447
|
-
|
|
448
|
-
### Error Types
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
// ✅ Good: Custom error classes
|
|
452
|
-
export class ValidationError extends Error {
|
|
453
|
-
constructor(
|
|
454
|
-
message: string,
|
|
455
|
-
public readonly field: string,
|
|
456
|
-
public readonly value: unknown
|
|
457
|
-
) {
|
|
458
|
-
super(message);
|
|
459
|
-
this.name = 'ValidationError';
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export class ApiError extends Error {
|
|
464
|
-
constructor(
|
|
465
|
-
message: string,
|
|
466
|
-
public readonly statusCode?: number,
|
|
467
|
-
public readonly response?: unknown
|
|
468
|
-
) {
|
|
469
|
-
super(message);
|
|
470
|
-
this.name = 'ApiError';
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Error Handling Patterns
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
// ✅ Good: Comprehensive error handling
|
|
479
|
-
const saveAccount = async (account: Account): Promise<void> => {
|
|
480
|
-
try {
|
|
481
|
-
// Validate input
|
|
482
|
-
const validation = account.validate();
|
|
483
|
-
if (!validation.isValid) {
|
|
484
|
-
throw new ValidationError(validation.errors.join(', '), 'account', account);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Attempt save
|
|
488
|
-
await account.save(apiService);
|
|
489
|
-
|
|
490
|
-
// Log success
|
|
491
|
-
logger.logInfo(`Account saved successfully: ${account.name}`);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
// Log error with context
|
|
494
|
-
logger.logError('Failed to save account:', {
|
|
495
|
-
error,
|
|
496
|
-
accountName: account.name,
|
|
497
|
-
accountId: account.id,
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// Re-throw with user-friendly message
|
|
501
|
-
if (error instanceof ValidationError) {
|
|
502
|
-
throw new Error(`Validation failed: ${error.message}`);
|
|
503
|
-
} else if (error instanceof ApiError) {
|
|
504
|
-
throw new Error('Unable to save account. Please try again.');
|
|
505
|
-
} else {
|
|
506
|
-
throw new Error('An unexpected error occurred.');
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
## Testing Standards
|
|
513
|
-
|
|
514
|
-
### Unit Test Structure
|
|
515
|
-
|
|
516
|
-
```typescript
|
|
517
|
-
// ✅ Good: Comprehensive unit tests
|
|
518
|
-
describe('Account', () => {
|
|
519
|
-
let mockApiService: jest.Mocked<IApiService>;
|
|
520
|
-
|
|
521
|
-
beforeEach(() => {
|
|
522
|
-
mockApiService = {
|
|
523
|
-
createRecord: jest.fn(),
|
|
524
|
-
updateRecord: jest.fn(),
|
|
525
|
-
deleteRecord: jest.fn(),
|
|
526
|
-
retrieveRecord: jest.fn(),
|
|
527
|
-
retrieveMultipleRecords: jest.fn(),
|
|
528
|
-
};
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
describe('validation', () => {
|
|
532
|
-
it('should require name field', () => {
|
|
533
|
-
// Arrange
|
|
534
|
-
const account = new Account({ name: '' });
|
|
535
|
-
|
|
536
|
-
// Act
|
|
537
|
-
const result = account.validate();
|
|
538
|
-
|
|
539
|
-
// Assert
|
|
540
|
-
expect(result.isValid).toBe(false);
|
|
541
|
-
expect(result.errors).toContain('Name is required');
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
it('should not allow negative revenue', () => {
|
|
545
|
-
// Arrange
|
|
546
|
-
const account = new Account({
|
|
547
|
-
name: 'Test Account',
|
|
548
|
-
revenue: -1000,
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
// Act
|
|
552
|
-
const result = account.validate();
|
|
553
|
-
|
|
554
|
-
// Assert
|
|
555
|
-
expect(result.isValid).toBe(false);
|
|
556
|
-
expect(result.errors).toContain('Revenue cannot be negative');
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
describe('create', () => {
|
|
561
|
-
it('should create account successfully', async () => {
|
|
562
|
-
// Arrange
|
|
563
|
-
const accountData = { name: 'Test Account', revenue: 100000 };
|
|
564
|
-
const account = new Account(accountData);
|
|
565
|
-
|
|
566
|
-
mockApiService.createRecord.mockResolvedValue({
|
|
567
|
-
data: { id: 'test-id', ...accountData },
|
|
568
|
-
success: true,
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
// Act
|
|
572
|
-
const result = await Account.create(mockApiService, account);
|
|
573
|
-
|
|
574
|
-
// Assert
|
|
575
|
-
expect(mockApiService.createRecord).toHaveBeenCalledWith(
|
|
576
|
-
AccountConstants.EntityLogicalName,
|
|
577
|
-
expect.objectContaining(accountData)
|
|
578
|
-
);
|
|
579
|
-
expect(result.id).toBe('test-id');
|
|
580
|
-
expect(result.name).toBe('Test Account');
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
## Documentation Standards
|
|
587
|
-
|
|
588
|
-
### JSDoc Comments
|
|
589
|
-
|
|
590
|
-
````typescript
|
|
591
|
-
/**
|
|
592
|
-
* Represents an Account entity in Dynamics 365
|
|
593
|
-
*
|
|
594
|
-
* @example
|
|
595
|
-
* ```typescript
|
|
596
|
-
* const account = new Account({ name: 'ACME Corp', revenue: 100000 });
|
|
597
|
-
* await Account.create(apiService, account);
|
|
598
|
-
* ```
|
|
599
|
-
*/
|
|
600
|
-
export class Account extends BaseEntity {
|
|
601
|
-
/**
|
|
602
|
-
* Creates a new account in Dynamics 365
|
|
603
|
-
*
|
|
604
|
-
* @param apiService - The API service instance
|
|
605
|
-
* @param account - The account data to create
|
|
606
|
-
* @returns Promise resolving to the created account
|
|
607
|
-
* @throws {ValidationError} When account data is invalid
|
|
608
|
-
* @throws {ApiError} When API request fails
|
|
609
|
-
*/
|
|
610
|
-
public static async create(apiService: IApiService, account: Account): Promise<Account> {
|
|
611
|
-
// Implementation
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Validates the account data
|
|
616
|
-
*
|
|
617
|
-
* @returns Validation result with isValid flag and error messages
|
|
618
|
-
*/
|
|
619
|
-
public validate(): ValidationResult {
|
|
620
|
-
// Implementation
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
````
|
|
624
|
-
|
|
625
|
-
## File Organization
|
|
626
|
-
|
|
627
|
-
### Directory Structure
|
|
628
|
-
|
|
629
|
-
```
|
|
630
|
-
src/
|
|
631
|
-
├── components/ # React components
|
|
632
|
-
│ ├── AccountForm/
|
|
633
|
-
│ │ ├── AccountForm.tsx
|
|
634
|
-
│ │ ├── AccountForm.css
|
|
635
|
-
│ │ ├── AccountForm.test.tsx
|
|
636
|
-
│ │ └── index.ts
|
|
637
|
-
│ └── shared/ # Shared components
|
|
638
|
-
├── models/ # Entity models
|
|
639
|
-
│ ├── BaseEntity.ts
|
|
640
|
-
│ ├── Account.ts
|
|
641
|
-
│ └── Contact.ts
|
|
642
|
-
├── constants/ # Entity constants
|
|
643
|
-
│ ├── account.ts
|
|
644
|
-
│ └── contact.ts
|
|
645
|
-
├── services/ # API services
|
|
646
|
-
│ ├── IApiService.ts
|
|
647
|
-
│ ├── XrmApiService.ts
|
|
648
|
-
│ └── MockApiService.ts
|
|
649
|
-
├── utils/ # Utility functions
|
|
650
|
-
├── types/ # Type definitions
|
|
651
|
-
├── hooks/ # Custom React hooks
|
|
652
|
-
└── providers/ # React context providers
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### Import Order
|
|
656
|
-
|
|
657
|
-
```typescript
|
|
658
|
-
// ✅ Good: Organized imports
|
|
659
|
-
// 1. Node modules
|
|
660
|
-
import React, { useState, useEffect } from 'react';
|
|
661
|
-
import { IColumn, DetailsList } from '@fluentui/react';
|
|
662
|
-
|
|
663
|
-
// 2. Internal modules (absolute paths)
|
|
664
|
-
import { Account } from '../models/Account';
|
|
665
|
-
import { AccountConstants } from '../constants/account';
|
|
666
|
-
import { useApiService } from '../providers/DynamicsProvider';
|
|
667
|
-
|
|
668
|
-
// 3. Relative imports
|
|
669
|
-
import './AccountList.css';
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### Naming Conventions
|
|
673
|
-
|
|
674
|
-
- **Files**: PascalCase for components, camelCase for utilities
|
|
675
|
-
- **Directories**: kebab-case or camelCase consistently
|
|
676
|
-
- **Test files**: Same name as source file with `.test.ts` suffix
|
|
677
|
-
- **Type files**: Same name as source file with `.types.ts` suffix
|
|
678
|
-
|
|
679
|
-
## Code Review Requirements
|
|
680
|
-
|
|
681
|
-
### Before Submitting
|
|
682
|
-
|
|
683
|
-
- [ ] All TypeScript errors resolved
|
|
684
|
-
- [ ] All ESLint warnings addressed
|
|
685
|
-
- [ ] Unit tests added/updated
|
|
686
|
-
- [ ] Documentation updated
|
|
687
|
-
- [ ] Manual testing completed
|
|
688
|
-
|
|
689
|
-
### Review Checklist
|
|
690
|
-
|
|
691
|
-
- [ ] Code follows established patterns
|
|
692
|
-
- [ ] Proper error handling implemented
|
|
693
|
-
- [ ] Performance considerations addressed
|
|
694
|
-
- [ ] Security best practices followed
|
|
695
|
-
- [ ] Accessibility requirements met
|
|
696
|
-
|
|
697
|
-
---
|
|
698
|
-
|
|
699
|
-
These standards ensure consistency, maintainability, and quality across all Dynamics 365 consulting
|
|
700
|
-
projects.
|