@khester/create-dynamics-app 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/artifacts/registry.d.ts +4 -3
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +145 -11
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/artifacts/types.d.ts +10 -1
- package/dist/artifacts/types.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/injectDevTools.d.ts.map +1 -1
- package/dist/injectDevTools.js +4 -2
- package/dist/injectDevTools.js.map +1 -1
- package/dist/scaffold.d.ts +23 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +27 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +3 -2
- package/templates/grid-starter/ARCHITECTURE.md +66 -0
- package/templates/grid-starter/README.md +122 -0
- package/templates/grid-starter/env.example +16 -0
- package/templates/grid-starter/gitignore +6 -0
- package/templates/grid-starter/index.html +16 -0
- package/templates/grid-starter/package.json +39 -0
- package/templates/grid-starter/src/App.tsx +23 -0
- package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
- package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
- package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
- package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
- package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
- package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
- package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
- package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
- package/templates/grid-starter/src/index.tsx +18 -0
- package/templates/grid-starter/src/vite-env.d.ts +15 -0
- package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/grid-starter/tsconfig.json +19 -0
- package/templates/grid-starter/vite.config.ts +76 -0
- package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
- package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
- package/templates/pcf-field/index.ts +1 -1
- package/templates/pcf-field/package.json +3 -1
- package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
- package/templates/react-custom-page/ARCHITECTURE.md +75 -0
- package/templates/react-custom-page/README.md +74 -568
- package/templates/react-custom-page/env.example +16 -0
- package/templates/react-custom-page/gitignore +1 -0
- package/templates/react-custom-page/index.html +16 -0
- package/templates/react-custom-page/package.json +21 -49
- package/templates/react-custom-page/src/App.tsx +26 -0
- package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
- package/templates/react-custom-page/src/core/recordContext.ts +51 -0
- package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
- package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
- package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
- package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
- package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
- package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
- package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
- package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
- package/templates/react-custom-page/src/domain/diff.ts +38 -0
- package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
- package/templates/react-custom-page/src/example/exampleError.ts +36 -0
- package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
- package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
- package/templates/react-custom-page/src/example/models/Account.ts +74 -0
- package/templates/react-custom-page/src/index.tsx +18 -128
- package/templates/react-custom-page/src/vite-env.d.ts +15 -0
- package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/react-custom-page/tsconfig.json +12 -22
- package/templates/react-custom-page/vite.config.ts +76 -0
- package/templates/starter-page/README.md +38 -0
- package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
- package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
- package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
- package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
- package/templates/starter-page/gitignore +5 -0
- package/templates/starter-page/package.json +27 -0
- package/templates/starter-page/public/index.html +11 -0
- package/templates/starter-page/src/index.tsx +10 -0
- package/templates/starter-page/src/services/dataverse.ts +30 -0
- package/templates/starter-page/tsconfig.json +15 -0
- package/templates/starter-page/webpack.config.js +17 -0
- package/templates/react-custom-page/deployment/README.md +0 -484
- package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
- package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
- package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
- package/templates/react-custom-page/public/index.html +0 -15
- package/templates/react-custom-page/scripts/custom-build.js +0 -255
- package/templates/react-custom-page/src/components/AccountForm.css +0 -71
- package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
- package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
- package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
- package/templates/react-custom-page/src/components/ContactForm.css +0 -48
- package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
- package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
- package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
- package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
- package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
- package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
- package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
- package/templates/react-custom-page/src/constants/account.ts +0 -410
- package/templates/react-custom-page/src/constants/contact.ts +0 -362
- package/templates/react-custom-page/src/models/Account.ts +0 -480
- package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
- package/templates/react-custom-page/src/models/Contact.ts +0 -580
- package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
- package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
- package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
- package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
- package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
- package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
- package/templates/react-custom-page/src/styles/index.css +0 -171
- package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
- package/templates/react-custom-page/webpack.config.js +0 -57
- /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
|
@@ -1,607 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React Custom Page (Dataverse)
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
A minimal, production-shaped starter for a React app mounted as a **Dynamics 365
|
|
4
|
+
custom page**. It ships one thin example form (edit a single Account) on a clean
|
|
5
|
+
layering you can copy: `component → hook → pure mapper/diff → model → IApiService`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
UI primitives, theme, and logging come from
|
|
8
|
+
[`@khester/reusable-components`](https://www.npmjs.com/package/@khester/reusable-components);
|
|
9
|
+
the data-access seam (`src/core/services`) is owned by this app.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Commands
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
| Command | What it does |
|
|
14
|
+
|---------|--------------|
|
|
15
|
+
| `npm install` | Install dependencies |
|
|
16
|
+
| `npm run dev` | Run in **mock** mode — seeded data, no org needed |
|
|
17
|
+
| `npm run dev:token` | Run against a **live org** — refreshes the token + starts Vite |
|
|
18
|
+
| `npm run auth:token -- --url https://<org>.crm.dynamics.com` | Acquire/refresh a Dataverse token into `.env` (needs `az login`) |
|
|
19
|
+
| `npm run build` | Production build → `dist/` (multi-file) |
|
|
20
|
+
| `npm run build:d365` | Single self-contained `dist/index.html` (the web resource) |
|
|
21
|
+
| `npm run deploy` | Build + upload + **publish** the web resource to your org |
|
|
22
|
+
| `npm test` | Unit-test the pure layers (diff, mappers, record context) |
|
|
23
|
+
| `npm run typecheck` | Type-check (`tsc --noEmit`) |
|
|
24
|
+
| `npm install @khester/reusable-components@latest` | Update the shared component library |
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
> Requires **Node 18+**. For `dev:token` / `deploy` you also need `az login` and
|
|
27
|
+
> `DYNAMICS_URL` in `.env` (set once via `npm run auth:token -- --url …`).
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
- **ServiceFactory**: Smart environment detection (development vs production) with automatic API
|
|
22
|
-
service selection
|
|
23
|
-
- **Centralized Logging**: Comprehensive logging system with debug UI and export capabilities
|
|
24
|
-
- **Entity Constants**: Complete field dictionaries with metadata for type safety
|
|
25
|
-
|
|
26
|
-
### **Advanced UI Components**
|
|
27
|
-
|
|
28
|
-
- **Enhanced Forms**: Account and Contact forms with validation and error handling
|
|
29
|
-
- **Interactive Debug Panel**: Real-time logging with filtering and export functionality
|
|
30
|
-
- **Responsive Design**: Mobile-optimized layouts with accessibility features
|
|
31
|
-
- **PCF Integration**: Multi-entity control wrapper for Dynamics 365 custom controls
|
|
32
|
-
|
|
33
|
-
### **Production-Ready Deployment**
|
|
34
|
-
|
|
35
|
-
- **Custom Build System**: D365-optimized webpack configuration with single-file output
|
|
36
|
-
- **Multiple Deployment Targets**: Web Resources, PCF Controls, and Custom Pages
|
|
37
|
-
- **Environment Detection**: Automatic MockApiService vs XrmApiService selection
|
|
38
|
-
- **Performance Optimization**: Minified production builds with deployment guidance
|
|
39
|
-
|
|
40
|
-
## 🏁 Quick Start
|
|
41
|
-
|
|
42
|
-
### Installation
|
|
29
|
+
## Quick start
|
|
43
30
|
|
|
44
31
|
```bash
|
|
45
|
-
# Using the CLI tool
|
|
46
|
-
npx create-dynamics-app my-d365-app --template dynamics-365-starter
|
|
47
|
-
|
|
48
|
-
# Or clone and install
|
|
49
|
-
git clone <repository-url>
|
|
50
|
-
cd dynamics-365-starter
|
|
51
32
|
npm install
|
|
33
|
+
npm run dev # mock mode — a seeded account, no org needed
|
|
52
34
|
```
|
|
53
35
|
|
|
54
|
-
|
|
36
|
+
Open http://localhost:3000. Edit a field; **Save** enables when the form is dirty
|
|
37
|
+
and logs a `[CRUD] UPDATE accounts ok` line. On localhost, `window.dumpAppLogs()`
|
|
38
|
+
in the browser console prints the structured log buffer.
|
|
55
39
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Opens http://localhost:3000 with Account and Contact management
|
|
40
|
+
A floating **🔧 Dev Tools** panel (bottom-right, localhost only) shows the active
|
|
41
|
+
mode (Mock / Token-proxy / Production), lets you load a record by GUID (adds `?id=`
|
|
42
|
+
and reloads), and views the `[CRUD]` log buffer. It renders nothing once deployed.
|
|
60
43
|
|
|
61
|
-
|
|
62
|
-
npm run build:dev
|
|
44
|
+
## Three runtime modes
|
|
63
45
|
|
|
64
|
-
|
|
65
|
-
npm run build:prod
|
|
66
|
-
|
|
67
|
-
# Serve built files locally
|
|
68
|
-
npm run serve
|
|
69
|
-
```
|
|
46
|
+
`src/core/services/ServiceFactory.ts` picks the data service per environment:
|
|
70
47
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
│ ├── AccountManagement.tsx # Account CRUD interface
|
|
77
|
-
│ ├── AccountForm.tsx # Account creation/editing form
|
|
78
|
-
│ ├── ContactManagement.tsx # Contact CRUD interface
|
|
79
|
-
│ ├── ContactForm.tsx # Contact creation/editing form
|
|
80
|
-
│ └── Logging/ # Centralized logging system
|
|
81
|
-
│ ├── logger.ts # Logger utility class
|
|
82
|
-
│ ├── LoggingContext.tsx # React context for logs
|
|
83
|
-
│ ├── LoggingProvider.tsx # Provider component
|
|
84
|
-
│ └── LoggingDebugPanel.tsx # Debug UI with filtering
|
|
85
|
-
├── models/
|
|
86
|
-
│ ├── BaseEntity.ts # Abstract base class with CRUD
|
|
87
|
-
│ ├── Account.ts # Account entity with validation
|
|
88
|
-
│ └── Contact.ts # Contact entity with validation
|
|
89
|
-
├── constants/
|
|
90
|
-
│ ├── account.ts # Account field constants & metadata
|
|
91
|
-
│ └── contact.ts # Contact field constants & metadata
|
|
92
|
-
├── services/
|
|
93
|
-
│ ├── ServiceFactory.ts # Environment-aware service factory
|
|
94
|
-
│ ├── XrmApiService.ts # Production Dynamics 365 API service
|
|
95
|
-
│ └── MockApiService.ts # Development mock service
|
|
96
|
-
├── providers/
|
|
97
|
-
│ └── DynamicsProvider.tsx # API context with environment detection
|
|
98
|
-
├── pcf/
|
|
99
|
-
│ ├── ContactControlWrapper.tsx # Single-entity PCF wrapper
|
|
100
|
-
│ └── MultiEntityControlWrapper.tsx # Multi-entity PCF wrapper
|
|
101
|
-
├── scripts/
|
|
102
|
-
│ └── custom-build.js # D365-optimized build configuration
|
|
103
|
-
├── styles/
|
|
104
|
-
│ └── index.css # Global styles and responsive design
|
|
105
|
-
└── index.tsx # Application entry with navigation
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## 🏗️ Entity Model Architecture
|
|
109
|
-
|
|
110
|
-
### BaseEntity Pattern
|
|
111
|
-
|
|
112
|
-
All entities inherit from `BaseEntity` which provides:
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// Static CRUD methods with validation and logging
|
|
116
|
-
export abstract class BaseEntity {
|
|
117
|
-
protected static async createEntity<T>(
|
|
118
|
-
apiService: IApiService,
|
|
119
|
-
entity: T,
|
|
120
|
-
entityCollectionName: string,
|
|
121
|
-
loggerContext: string
|
|
122
|
-
): Promise<T>;
|
|
123
|
-
|
|
124
|
-
protected static async updateEntity<T>(...): Promise<T>;
|
|
125
|
-
protected static async deleteEntity(...): Promise<void>;
|
|
126
|
-
protected static async retrieveEntitiesByFilter<T>(...): Promise<T[]>;
|
|
127
|
-
}
|
|
128
|
-
```
|
|
48
|
+
| Mode | When | Service |
|
|
49
|
+
|------|------|---------|
|
|
50
|
+
| **Mock** | `localhost`, no `DYNAMICS_URL` | `MockApiService` (in-memory, seeds one account) |
|
|
51
|
+
| **Token** | `localhost` + `DYNAMICS_URL` set | `FetchApiService` → Vite proxy → real Dataverse |
|
|
52
|
+
| **Production** | deployed in a model-driven app | `XrmApiService` (Web API, session auth) |
|
|
129
53
|
|
|
130
|
-
###
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
// Account entity with complete CRUD operations
|
|
134
|
-
export class Account extends BaseEntity implements IAccount {
|
|
135
|
-
// Constructor and properties...
|
|
136
|
-
|
|
137
|
-
// Static CRUD methods
|
|
138
|
-
public static async create(apiService: IApiService, account: Account): Promise<Account> {
|
|
139
|
-
const loggerContext = 'Account.create';
|
|
140
|
-
console.log(`${loggerContext}: Creating new account`, { name: account.name });
|
|
141
|
-
|
|
142
|
-
return await this.createEntity<Account>(
|
|
143
|
-
apiService,
|
|
144
|
-
account,
|
|
145
|
-
AccountConstants.EntityCollectionName,
|
|
146
|
-
loggerContext
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
public static async retrieveActiveAccounts(apiService: IApiService): Promise<Account[]> {
|
|
151
|
-
// Built-in FetchXML generation and error handling
|
|
152
|
-
const filter = `<filter type="and">
|
|
153
|
-
<condition attribute="${AccountConstants.StateCode}" operator="eq" value="0" />
|
|
154
|
-
</filter>`;
|
|
155
|
-
|
|
156
|
-
return await this.retrieveByFilter(apiService, filter);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Client-side validation
|
|
160
|
-
validate(): boolean {
|
|
161
|
-
if (!this.name || this.name.trim().length === 0) {
|
|
162
|
-
throw new Error('Account name is required');
|
|
163
|
-
}
|
|
164
|
-
// Additional validation rules...
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Entity Constants
|
|
171
|
-
|
|
172
|
-
Complete field dictionaries with metadata:
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
export class AccountConstants {
|
|
176
|
-
/** Type: String, RequiredLevel: ApplicationRequired, MaxLength: 160 */
|
|
177
|
-
public static readonly PrimaryName: string = 'name';
|
|
178
|
-
|
|
179
|
-
/** Type: String, RequiredLevel: None, MaxLength: 100, Format: Email */
|
|
180
|
-
public static readonly EMailAddress1: string = 'emailaddress1';
|
|
181
|
-
|
|
182
|
-
/** Type: Money, RequiredLevel: None, MinValue: -922337203685477 */
|
|
183
|
-
public static readonly Revenue: string = 'revenue';
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## ⚙️ Service Factory & Environment Detection
|
|
188
|
-
|
|
189
|
-
The `ServiceFactory` automatically detects the runtime environment:
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
export class ServiceFactory {
|
|
193
|
-
// Automatic environment detection
|
|
194
|
-
public static get isMockEnvironment(): boolean {
|
|
195
|
-
const hostname = window.location.hostname;
|
|
196
|
-
return hostname === 'localhost' || hostname === '127.0.0.1';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Smart API service creation
|
|
200
|
-
public static createApiService(Xrm?: any): IApiService {
|
|
201
|
-
if (this.isMockEnvironment) {
|
|
202
|
-
return new MockApiService(); // Development with mock data
|
|
203
|
-
}
|
|
204
|
-
return new XrmApiService(Xrm); // Production with Dynamics 365
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## 📊 Centralized Logging System
|
|
210
|
-
|
|
211
|
-
Comprehensive logging with UI:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// Logger usage throughout the application
|
|
215
|
-
Logger.log('Application initialized', 'App');
|
|
216
|
-
Logger.userAction('Contact created', { contactId: '123' }, 'ContactForm');
|
|
217
|
-
Logger.error('API call failed', 'ContactManagement', error);
|
|
218
|
-
Logger.apiOperation('CREATE', 'contact', contactData, 'ContactForm.submit');
|
|
219
|
-
Logger.validation('Contact', ['Email is required'], 'ContactForm.validate');
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
The debug panel provides:
|
|
223
|
-
|
|
224
|
-
- Real-time log viewing with level filtering
|
|
225
|
-
- Search and export capabilities
|
|
226
|
-
- Performance timing and API operation tracking
|
|
227
|
-
- User action audit trail
|
|
228
|
-
|
|
229
|
-
## 🎯 Deployment Options
|
|
230
|
-
|
|
231
|
-
### 1. Dynamics 365 Web Resources
|
|
232
|
-
|
|
233
|
-
**Optimal for**: Dashboard widgets, form sections, custom pages
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
# Build optimized for D365
|
|
237
|
-
npm run build:d365
|
|
238
|
-
|
|
239
|
-
# Upload generated files as web resources:
|
|
240
|
-
# - dist/main.js → Script (JScript) web resource
|
|
241
|
-
# - dist/index.html → Web Page (HTML) web resource
|
|
242
|
-
# - Set "Available for Dynamics 365 mobile" if needed
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
The custom build process:
|
|
246
|
-
|
|
247
|
-
- Disables code splitting for single-file deployment
|
|
248
|
-
- Removes content hashes for consistent web resource naming
|
|
249
|
-
- Optimizes bundle size and provides deployment guidance
|
|
250
|
-
- Generates deployment info with asset details
|
|
251
|
-
|
|
252
|
-
### 2. PCF Custom Controls
|
|
253
|
-
|
|
254
|
-
**Optimal for**: Form controls, view extensions, embedded components
|
|
255
|
-
|
|
256
|
-
#### Single Entity Control
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
import { ContactControlWrapper } from './pcf/ContactControlWrapper';
|
|
260
|
-
|
|
261
|
-
// In your PCF component
|
|
262
|
-
export class ContactControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
|
|
263
|
-
public init(context: ComponentFramework.Context<IInputs>): void {
|
|
264
|
-
// Render ContactControlWrapper with PCF context
|
|
265
|
-
ReactDOM.render(React.createElement(ContactControlWrapper, { context }), this.container);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
#### Multi-Entity Control
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
import { MultiEntityControlWrapper } from './pcf/MultiEntityControlWrapper';
|
|
274
|
-
|
|
275
|
-
// Support both Account and Contact management in one control
|
|
276
|
-
ReactDOM.render(
|
|
277
|
-
React.createElement(MultiEntityControlWrapper, {
|
|
278
|
-
context,
|
|
279
|
-
defaultEntity: 'contact',
|
|
280
|
-
showTabs: true,
|
|
281
|
-
}),
|
|
282
|
-
this.container
|
|
283
|
-
);
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### 3. Dynamics 365 Custom Pages
|
|
287
|
-
|
|
288
|
-
**Optimal for**: Full-page applications, complex workflows
|
|
54
|
+
### Token mode — test against a real org locally
|
|
289
55
|
|
|
290
56
|
```bash
|
|
291
|
-
#
|
|
292
|
-
npm run
|
|
293
|
-
|
|
294
|
-
# Deploy as model-driven app page:
|
|
295
|
-
# 1. Upload main.js as web resource
|
|
296
|
-
# 2. Create Custom Page in Power Apps
|
|
297
|
-
# 3. Add HTML control with web resource reference
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
## 🔧 Configuration
|
|
301
|
-
|
|
302
|
-
### Environment Variables
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
# .env.local
|
|
306
|
-
REACT_APP_D365_URL=https://yourorg.crm.dynamics.com
|
|
307
|
-
REACT_APP_ENVIRONMENT=development
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### API Service Configuration
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
// Automatic configuration via ServiceFactory
|
|
314
|
-
const DynamicsProvider: React.FC = ({ children }) => {
|
|
315
|
-
useEffect(() => {
|
|
316
|
-
// ServiceFactory handles environment detection
|
|
317
|
-
const xrmObject = ServiceFactory.isDynamics365Context() ? window.Xrm : undefined;
|
|
318
|
-
const service = ServiceFactory.createApiService(xrmObject);
|
|
319
|
-
setApiService(service);
|
|
320
|
-
}, []);
|
|
321
|
-
|
|
322
|
-
// Provider implementation...
|
|
323
|
-
};
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### PCF Integration
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
// Custom API service for PCF context
|
|
330
|
-
const createPCFApiService = () => ({
|
|
331
|
-
createRecord: async (entityName: string, data: any) =>
|
|
332
|
-
await context.webAPI.createRecord(entityName, data),
|
|
333
|
-
retrieveMultipleRecords: async (entityName: string, fetchXml: string) =>
|
|
334
|
-
await context.webAPI.retrieveMultipleRecords(
|
|
335
|
-
entityName,
|
|
336
|
-
`?fetchXml=${encodeURIComponent(fetchXml)}`
|
|
337
|
-
),
|
|
338
|
-
// Additional PCF WebAPI methods...
|
|
339
|
-
});
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
## 🛠️ Development Workflow
|
|
343
|
-
|
|
344
|
-
### Adding New Entities
|
|
345
|
-
|
|
346
|
-
1. **Create Entity Model**:
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
// src/models/Opportunity.ts
|
|
350
|
-
export class Opportunity extends BaseEntity implements IOpportunity {
|
|
351
|
-
// Entity properties and validation
|
|
352
|
-
public static async create(
|
|
353
|
-
apiService: IApiService,
|
|
354
|
-
opportunity: Opportunity
|
|
355
|
-
): Promise<Opportunity> {
|
|
356
|
-
return await this.createEntity<Opportunity>(/*...*/);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
57
|
+
npm run auth:token -- --url https://<org>.crm.dynamics.com # writes .env via Azure CLI
|
|
58
|
+
npm run dev
|
|
359
59
|
```
|
|
360
60
|
|
|
361
|
-
|
|
61
|
+
The Vite dev server proxies `/api/data/*` to the org and injects the bearer token
|
|
62
|
+
**server-side** — the token never enters the browser bundle. Tokens last ~60 min;
|
|
63
|
+
re-run `npm run auth:token` to refresh (no dev-server restart needed). Requires
|
|
64
|
+
`az login` first. See `env.example` for the variables.
|
|
362
65
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
export class OpportunityConstants {
|
|
366
|
-
public static readonly EntityName: string = 'opportunity';
|
|
367
|
-
public static readonly PrimaryName: string = 'name';
|
|
368
|
-
// Field definitions with metadata...
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
3. **Create Management Component**:
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
// src/components/OpportunityManagement.tsx
|
|
376
|
-
export const OpportunityManagement: React.FC = () => {
|
|
377
|
-
// Follow AccountManagement pattern
|
|
378
|
-
const loadOpportunities = useCallback(async () => {
|
|
379
|
-
const opportunities = await Opportunity.retrieveActiveOpportunities(apiService);
|
|
380
|
-
// Component implementation...
|
|
381
|
-
}, [apiService]);
|
|
382
|
-
};
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### Custom Validation
|
|
386
|
-
|
|
387
|
-
```typescript
|
|
388
|
-
// In entity models
|
|
389
|
-
validate(): boolean {
|
|
390
|
-
const validationErrors: string[] = [];
|
|
391
|
-
|
|
392
|
-
if (!this.name?.trim()) {
|
|
393
|
-
validationErrors.push('Name is required');
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (this.revenue && this.revenue < 0) {
|
|
397
|
-
validationErrors.push('Revenue cannot be negative');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (validationErrors.length > 0) {
|
|
401
|
-
Logger.validation('Account', validationErrors, 'Account.validate');
|
|
402
|
-
throw new Error(validationErrors.join(', '));
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return true;
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Extending Logging
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
// Add custom log types
|
|
413
|
-
Logger.performance('Data fetch completed', startTime, 'AccountManagement');
|
|
414
|
-
Logger.businessRule('Credit limit exceeded', { accountId, creditLimit }, 'AccountForm');
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
## 📋 Available Scripts
|
|
66
|
+
Once `DYNAMICS_URL` is in `.env`, **`npm run dev:token`** refreshes the token and
|
|
67
|
+
starts the dev server in one step:
|
|
418
68
|
|
|
419
69
|
```bash
|
|
420
|
-
|
|
421
|
-
npm start # Start development server with hot reload
|
|
422
|
-
npm run dev # Alias for start
|
|
423
|
-
|
|
424
|
-
# Building
|
|
425
|
-
npm run build # Standard webpack production build
|
|
426
|
-
npm run build:dev # Unminified build for debugging
|
|
427
|
-
npm run build:prod # Optimized build for D365 deployment
|
|
428
|
-
npm run build:d365 # Alias for build:prod
|
|
429
|
-
|
|
430
|
-
# Quality Assurance
|
|
431
|
-
npm run typecheck # TypeScript compilation check
|
|
432
|
-
npm run lint # ESLint code quality check
|
|
433
|
-
npm run clean # Clean build directory
|
|
434
|
-
|
|
435
|
-
# Deployment
|
|
436
|
-
npm run serve # Serve built files locally on port 62874
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
## 🧪 Testing Workflow
|
|
440
|
-
|
|
441
|
-
### Component Testing
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
// Test entity models
|
|
445
|
-
const account = new Account({ name: 'Test Account' });
|
|
446
|
-
account.validate(); // Should pass
|
|
447
|
-
|
|
448
|
-
// Test API operations
|
|
449
|
-
const mockApiService = new MockApiService();
|
|
450
|
-
const createdAccount = await Account.create(mockApiService, account);
|
|
70
|
+
npm run dev:token
|
|
451
71
|
```
|
|
452
72
|
|
|
453
|
-
|
|
73
|
+
## Build & deploy
|
|
454
74
|
|
|
455
75
|
```bash
|
|
456
|
-
#
|
|
457
|
-
npm
|
|
458
|
-
|
|
459
|
-
# Test production build
|
|
460
|
-
npm run build:prod
|
|
461
|
-
npm run serve # Verify optimized bundle
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### PCF Testing
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
// Test PCF wrapper with mock context
|
|
468
|
-
const mockContext = {
|
|
469
|
-
webAPI: {
|
|
470
|
-
createRecord: jest.fn(),
|
|
471
|
-
retrieveMultipleRecords: jest.fn()
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
render(<ContactControlWrapper context={mockContext} />);
|
|
76
|
+
npm run build # standard multi-file build → dist/
|
|
77
|
+
npm run build:d365 # single self-contained dist/index.html (vite-plugin-singlefile)
|
|
78
|
+
npm run deploy # build:d365 + upload as an HTML web resource + publish
|
|
476
79
|
```
|
|
477
80
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
#### Build Errors
|
|
81
|
+
**`npm run deploy`** refreshes the token, builds the single-file bundle, and
|
|
82
|
+
upserts + publishes it as an HTML web resource via the Dataverse Web API (no PAC
|
|
83
|
+
CLI needed). Set the web-resource name in `.env` first — the prefix must match a
|
|
84
|
+
publisher that exists in your org:
|
|
483
85
|
|
|
484
86
|
```bash
|
|
485
|
-
#
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
# Check TypeScript compilation
|
|
490
|
-
npm run typecheck
|
|
87
|
+
# .env
|
|
88
|
+
WEBRESOURCE_NAME=cr1a2_/myapp/index.html # use YOUR publisher prefix
|
|
89
|
+
# WEBRESOURCE_SOLUTION=MySolution # optional: add it to an unmanaged solution
|
|
491
90
|
```
|
|
492
91
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
ServiceFactory.isMockEnvironment = true; // Development
|
|
498
|
-
ServiceFactory.isMockEnvironment = false; // Production
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
#### PCF Integration Problems
|
|
502
|
-
|
|
503
|
-
- Verify `context.webAPI` is available
|
|
504
|
-
- Check PCF manifest configuration
|
|
505
|
-
- Ensure proper component lifecycle implementation
|
|
506
|
-
- Review browser console for Xrm object availability
|
|
507
|
-
|
|
508
|
-
#### Logging Not Working
|
|
92
|
+
Then host it on a model-driven **custom page**, or open it via
|
|
93
|
+
`Xrm.Navigation.navigateTo({ pageType: "webresource", webresourceName: "<name>" })`.
|
|
94
|
+
The page reads an optional record id from the URL (`?id=<guid>`); in production it
|
|
95
|
+
resolves `Xrm` from `window.parent`.
|
|
509
96
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
97
|
+
> Some orgs enforce a Content-Security-Policy that blocks inline `<script>`. The
|
|
98
|
+
> single-file bundle is one inline script — if your environment blocks it, use the
|
|
99
|
+
> multi-file `npm run build` output instead.
|
|
513
100
|
|
|
514
|
-
|
|
515
|
-
Logger.setLoggerFunction((message, source) => {
|
|
516
|
-
console.log(`[${source}] ${message}`);
|
|
517
|
-
});
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
### Performance Optimization
|
|
101
|
+
## Make it your own
|
|
521
102
|
|
|
522
|
-
|
|
103
|
+
1. Replace `src/example/` with your page: `models/<Entity>.ts` (CRUD), a pure
|
|
104
|
+
`mappers/<entity>Mapper.ts` (+ test), `hooks/`, and the thin component.
|
|
105
|
+
2. Add a seed for your entity in `src/core/services/MockApiService.ts` so
|
|
106
|
+
`npm run dev` works offline.
|
|
107
|
+
3. Keep CRUD in the model and business rules in the pure layers — see
|
|
108
|
+
[ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
523
109
|
|
|
524
110
|
```bash
|
|
525
|
-
#
|
|
526
|
-
npm
|
|
527
|
-
# Add analyzer to build script and review output
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
#### Entity Model Performance
|
|
531
|
-
|
|
532
|
-
```typescript
|
|
533
|
-
// Use selective field retrieval
|
|
534
|
-
const accounts = await Account.retrieveByFilter(apiService, filter, [
|
|
535
|
-
AccountConstants.PrimaryName,
|
|
536
|
-
AccountConstants.EMailAddress1,
|
|
537
|
-
AccountConstants.Telephone1,
|
|
538
|
-
]);
|
|
111
|
+
npm run test # unit-tests the pure layers (diff, mappers)
|
|
112
|
+
npm run typecheck # tsc --noEmit
|
|
539
113
|
```
|
|
540
|
-
|
|
541
|
-
## 📚 Learning Resources
|
|
542
|
-
|
|
543
|
-
### Essential Documentation
|
|
544
|
-
|
|
545
|
-
- [Dynamics UI Kit Components](https://github.com/your-org/dynamics-ui-kit)
|
|
546
|
-
- [Dynamics 365 Web API Reference](https://docs.microsoft.com/dynamics365/customer-engagement/web-api/)
|
|
547
|
-
- [PowerApps Component Framework](https://docs.microsoft.com/powerapps/developer/component-framework/)
|
|
548
|
-
- [Fluent UI v8 Components](https://developer.microsoft.com/fluentui/get-started/web#fluent-ui-react-v8)
|
|
549
|
-
|
|
550
|
-
### Advanced Topics
|
|
551
|
-
|
|
552
|
-
- [Entity Relationship Modeling in D365](https://docs.microsoft.com/dynamics365/customerengagement/on-premises/developer/introduction-entities)
|
|
553
|
-
- [Custom Page Development](https://docs.microsoft.com/powerapps/developer/model-driven-apps/customizable-controls-custom-pages)
|
|
554
|
-
- [FetchXML Query Examples](https://docs.microsoft.com/powerapps/developer/data-platform/use-fetchxml-construct-query)
|
|
555
|
-
|
|
556
|
-
## 🤝 Contributing
|
|
557
|
-
|
|
558
|
-
### Development Setup
|
|
559
|
-
|
|
560
|
-
1. **Fork and Clone**: Create a fork of the repository
|
|
561
|
-
2. **Install Dependencies**: Run `npm install` in the template directory
|
|
562
|
-
3. **Create Feature Branch**: `git checkout -b feature/your-feature`
|
|
563
|
-
4. **Follow Patterns**: Use existing entity models and components as templates
|
|
564
|
-
5. **Add Tests**: Include validation and component tests
|
|
565
|
-
6. **Update Documentation**: Add examples and update README
|
|
566
|
-
|
|
567
|
-
### Code Standards
|
|
568
|
-
|
|
569
|
-
- **TypeScript**: Full type safety with strict mode
|
|
570
|
-
- **Entity Models**: Follow BaseEntity pattern with validation
|
|
571
|
-
- **Components**: Use functional components with hooks
|
|
572
|
-
- **Logging**: Include comprehensive logging for debugging
|
|
573
|
-
- **Error Handling**: Implement proper error boundaries and validation
|
|
574
|
-
|
|
575
|
-
### Pull Request Guidelines
|
|
576
|
-
|
|
577
|
-
1. Ensure TypeScript compilation is clean
|
|
578
|
-
2. Test in both development and production builds
|
|
579
|
-
3. Verify PCF wrapper functionality
|
|
580
|
-
4. Update documentation for new features
|
|
581
|
-
5. Include migration guide for breaking changes
|
|
582
|
-
|
|
583
|
-
## 📄 License
|
|
584
|
-
|
|
585
|
-
This template is part of the Dynamics UI Kit and follows the same licensing terms.
|
|
586
|
-
|
|
587
|
-
## 🆘 Support
|
|
588
|
-
|
|
589
|
-
### Getting Help
|
|
590
|
-
|
|
591
|
-
- **Issues**: Create issues in the Dynamics UI Kit repository
|
|
592
|
-
- **Documentation**: Check the comprehensive guides and examples
|
|
593
|
-
- **Community**: Join discussions in the project community
|
|
594
|
-
|
|
595
|
-
### Enterprise Support
|
|
596
|
-
|
|
597
|
-
For enterprise deployments and custom development:
|
|
598
|
-
|
|
599
|
-
- Architecture consulting for complex D365 integrations
|
|
600
|
-
- Custom entity model development
|
|
601
|
-
- Performance optimization and scaling guidance
|
|
602
|
-
- Advanced PCF control development
|
|
603
|
-
|
|
604
|
-
---
|
|
605
|
-
|
|
606
|
-
Built with ❤️ using [Dynamics UI Kit](https://github.com/your-org/dynamics-ui-kit) | Optimized for
|
|
607
|
-
Microsoft Dynamics 365
|