@khester/create-dynamics-app 2.1.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/dist/artifacts/registry.d.ts +4 -3
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +121 -11
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/artifacts/types.d.ts +1 -1
- package/dist/artifacts/types.d.ts.map +1 -1
- package/dist/index.js +2 -1
- 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 +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +3 -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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
A Dynamics 365 **starter page** — a React web resource built on a single
|
|
4
|
+
[`@dataverse-kit/surface-kit`](https://www.npmjs.com/package/@dataverse-kit/surface-kit)
|
|
5
|
+
surface (Fluent UI v8), hosted as a custom page / web resource.
|
|
6
|
+
|
|
7
|
+
The surface was chosen at scaffold time (`--surface <form|panel|dashboard|master-detail>`)
|
|
8
|
+
and lives in `src/App.tsx`:
|
|
9
|
+
|
|
10
|
+
| Surface | Renders | Extra dep |
|
|
11
|
+
|---------|---------|-----------|
|
|
12
|
+
| `form` | `FormSurface` + `Section`s + `FieldRow`s (a record edit page) | — |
|
|
13
|
+
| `panel` | A list with a slide-in `PanelSurface` editor | — |
|
|
14
|
+
| `dashboard` | `DashboardSurface` tile grid hosting a `<DataGrid>` | `@dataverse-kit/grid-kit` |
|
|
15
|
+
| `master-detail` | `MasterDetailSurface` (list + detail split) hosting a `<DataGrid>` | `@dataverse-kit/grid-kit` |
|
|
16
|
+
|
|
17
|
+
## Develop
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
npm run dev # webpack dev server on http://localhost:8080
|
|
22
|
+
npm run build # production bundle → dist/{{projectName}}.js
|
|
23
|
+
npm run typecheck # tsc --noEmit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Data access goes through `src/services/dataverse.ts` (`Xrm.WebApi`). Outside a
|
|
27
|
+
model-driven host, stub `Xrm` with the `serve` dev-tool's `tools/dev/mock-xrm.js`.
|
|
28
|
+
|
|
29
|
+
## Dev-tools
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run auth:token -- --url https://YOUR_ORG.crm.dynamics.com # token → .env
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Deploy
|
|
36
|
+
|
|
37
|
+
Upload `dist/{{projectName}}.js` as a web resource and host it on a custom page or
|
|
38
|
+
open it from a form / ribbon command.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ThemeProvider } from '@fluentui/react/lib/Theme';
|
|
3
|
+
import { DashboardSurface, Section } from '@dataverse-kit/surface-kit';
|
|
4
|
+
import { DataGrid, type ColumnDef } from '@dataverse-kit/grid-kit';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Starter page — `dashboard` surface. A surface-kit `DashboardSurface` tile grid
|
|
8
|
+
* (2 cols, reflows to 1 on narrow widths) whose tiles host grid-kit `<DataGrid>`s.
|
|
9
|
+
* Swap the sample rows for `retrieveMultiple(...)` from `./services/dataverse`.
|
|
10
|
+
*/
|
|
11
|
+
interface AccountRow extends Record<string, unknown> {
|
|
12
|
+
key: string;
|
|
13
|
+
name: string;
|
|
14
|
+
revenue: number;
|
|
15
|
+
status: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const COLUMNS: ColumnDef<AccountRow>[] = [
|
|
19
|
+
{ key: 'name', fieldName: 'name', name: 'Account', rendererType: 'text' },
|
|
20
|
+
{ key: 'revenue', fieldName: 'revenue', name: 'Revenue', rendererType: 'currency' },
|
|
21
|
+
{ key: 'status', fieldName: 'status', name: 'Status', rendererType: 'text' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const ROWS: AccountRow[] = [
|
|
25
|
+
{ key: '1', name: 'Contoso', revenue: 5_000_000, status: 'Active' },
|
|
26
|
+
{ key: '2', name: 'Fabrikam', revenue: 2_400_000, status: 'Active' },
|
|
27
|
+
{ key: '3', name: 'Adventure Works', revenue: 980_000, status: 'On hold' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const App: React.FC = () => {
|
|
31
|
+
return (
|
|
32
|
+
<ThemeProvider>
|
|
33
|
+
<DashboardSurface
|
|
34
|
+
columns={2}
|
|
35
|
+
tiles={[
|
|
36
|
+
<Section key="accounts" title="Top accounts" variant="card">
|
|
37
|
+
<DataGrid<AccountRow> items={ROWS} columns={COLUMNS} />
|
|
38
|
+
</Section>,
|
|
39
|
+
<Section key="pipeline" title="Pipeline" variant="card">
|
|
40
|
+
<DataGrid<AccountRow> items={ROWS} columns={COLUMNS} />
|
|
41
|
+
</Section>,
|
|
42
|
+
]}
|
|
43
|
+
/>
|
|
44
|
+
</ThemeProvider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ThemeProvider } from '@fluentui/react/lib/Theme';
|
|
3
|
+
import { Stack } from '@fluentui/react/lib/Stack';
|
|
4
|
+
import { TextField } from '@fluentui/react/lib/TextField';
|
|
5
|
+
import { PrimaryButton, DefaultButton } from '@fluentui/react/lib/Button';
|
|
6
|
+
import { FormSurface, Section, FieldRow } from '@dataverse-kit/surface-kit';
|
|
7
|
+
import { createRecord } from './services/dataverse';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Starter page — `form` surface. A surface-kit `FormSurface` hosts a `Section` of
|
|
11
|
+
* `FieldRow`s; the layout stacks to one column on narrow widths (surface-kit owns
|
|
12
|
+
* the responsive breakpoints). Saves a contact via `Xrm.WebApi`.
|
|
13
|
+
*/
|
|
14
|
+
export const App: React.FC = () => {
|
|
15
|
+
const [name, setName] = React.useState('');
|
|
16
|
+
const [email, setEmail] = React.useState('');
|
|
17
|
+
const [phone, setPhone] = React.useState('');
|
|
18
|
+
const [saving, setSaving] = React.useState(false);
|
|
19
|
+
|
|
20
|
+
const onSave = async () => {
|
|
21
|
+
setSaving(true);
|
|
22
|
+
try {
|
|
23
|
+
await createRecord('contact', {
|
|
24
|
+
fullname: name,
|
|
25
|
+
emailaddress1: email,
|
|
26
|
+
telephone1: phone,
|
|
27
|
+
});
|
|
28
|
+
alert('Saved.');
|
|
29
|
+
} catch (e) {
|
|
30
|
+
alert(`Save failed (Xrm not available here?): ${String(e)}`);
|
|
31
|
+
} finally {
|
|
32
|
+
setSaving(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ThemeProvider>
|
|
38
|
+
<FormSurface maxWidth={960}>
|
|
39
|
+
<Section title="{{projectName}}" columnSpan={12}>
|
|
40
|
+
<FieldRow label="Full name" required>
|
|
41
|
+
<TextField value={name} onChange={(_, v) => setName(v ?? '')} />
|
|
42
|
+
</FieldRow>
|
|
43
|
+
<FieldRow label="Email">
|
|
44
|
+
<TextField value={email} onChange={(_, v) => setEmail(v ?? '')} />
|
|
45
|
+
</FieldRow>
|
|
46
|
+
<FieldRow label="Phone">
|
|
47
|
+
<TextField value={phone} onChange={(_, v) => setPhone(v ?? '')} />
|
|
48
|
+
</FieldRow>
|
|
49
|
+
<FieldRow>
|
|
50
|
+
<Stack horizontal tokens={{ childrenGap: 8 }}>
|
|
51
|
+
<PrimaryButton text="Save" onClick={onSave} disabled={saving} />
|
|
52
|
+
<DefaultButton text="Cancel" onClick={() => window.history.back()} />
|
|
53
|
+
</Stack>
|
|
54
|
+
</FieldRow>
|
|
55
|
+
</Section>
|
|
56
|
+
</FormSurface>
|
|
57
|
+
</ThemeProvider>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ThemeProvider } from '@fluentui/react/lib/Theme';
|
|
3
|
+
import { Text } from '@fluentui/react/lib/Text';
|
|
4
|
+
import { MasterDetailSurface, Section, FieldRow } from '@dataverse-kit/surface-kit';
|
|
5
|
+
import { DataGrid, type ColumnDef } from '@dataverse-kit/grid-kit';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Starter page — `master-detail` surface. A surface-kit `MasterDetailSurface`
|
|
9
|
+
* (list + detail split that stacks below the `md` breakpoint) with a grid-kit
|
|
10
|
+
* `<DataGrid>` master and a `Section` detail driven by the selected row. Swap the
|
|
11
|
+
* sample rows for `retrieveMultiple(...)` from `./services/dataverse`.
|
|
12
|
+
*/
|
|
13
|
+
interface AccountRow extends Record<string, unknown> {
|
|
14
|
+
key: string;
|
|
15
|
+
name: string;
|
|
16
|
+
city: string;
|
|
17
|
+
owner: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const COLUMNS: ColumnDef<AccountRow>[] = [
|
|
21
|
+
{ key: 'name', fieldName: 'name', name: 'Account', rendererType: 'text' },
|
|
22
|
+
{ key: 'city', fieldName: 'city', name: 'City', rendererType: 'text' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const ROWS: AccountRow[] = [
|
|
26
|
+
{ key: '1', name: 'Contoso', city: 'Seattle', owner: 'Dana' },
|
|
27
|
+
{ key: '2', name: 'Fabrikam', city: 'Austin', owner: 'Lee' },
|
|
28
|
+
{ key: '3', name: 'Adventure Works', city: 'Denver', owner: 'Sam' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const App: React.FC = () => {
|
|
32
|
+
const [selected, setSelected] = React.useState<AccountRow | undefined>(ROWS[0]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ThemeProvider>
|
|
36
|
+
<MasterDetailSurface
|
|
37
|
+
master={
|
|
38
|
+
<DataGrid<AccountRow>
|
|
39
|
+
items={ROWS}
|
|
40
|
+
columns={COLUMNS}
|
|
41
|
+
selectionMode="single"
|
|
42
|
+
onSelectionChanged={(rows) => setSelected(rows[0])}
|
|
43
|
+
/>
|
|
44
|
+
}
|
|
45
|
+
detail={
|
|
46
|
+
selected ? (
|
|
47
|
+
<Section title={selected.name} variant="flat">
|
|
48
|
+
<FieldRow label="City">
|
|
49
|
+
<Text>{selected.city}</Text>
|
|
50
|
+
</FieldRow>
|
|
51
|
+
<FieldRow label="Owner">
|
|
52
|
+
<Text>{selected.owner}</Text>
|
|
53
|
+
</FieldRow>
|
|
54
|
+
</Section>
|
|
55
|
+
) : null
|
|
56
|
+
}
|
|
57
|
+
detailFallback={<Text>Select an account to see its details.</Text>}
|
|
58
|
+
/>
|
|
59
|
+
</ThemeProvider>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ThemeProvider } from '@fluentui/react/lib/Theme';
|
|
3
|
+
import { Stack } from '@fluentui/react/lib/Stack';
|
|
4
|
+
import { TextField } from '@fluentui/react/lib/TextField';
|
|
5
|
+
import { PrimaryButton, DefaultButton } from '@fluentui/react/lib/Button';
|
|
6
|
+
import { DetailsList, SelectionMode, type IColumn } from '@fluentui/react/lib/DetailsList';
|
|
7
|
+
import { FormSurface, Section, FieldRow, PanelSurface } from '@dataverse-kit/surface-kit';
|
|
8
|
+
import { createRecord } from './services/dataverse';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Starter page — `panel` surface. A list with a slide-in surface-kit `PanelSurface`
|
|
12
|
+
* editor (Fluent v8 Panel, width-derived PanelType, sticky footer): the Section's
|
|
13
|
+
* command bar opens the panel to create a record, which is saved via `Xrm.WebApi`.
|
|
14
|
+
* The list is a plain Fluent `DetailsList` (no grid-kit) — swap the sample rows for
|
|
15
|
+
* `retrieveMultiple(...)` from `./services/dataverse`.
|
|
16
|
+
*/
|
|
17
|
+
interface ContactRow {
|
|
18
|
+
key: string;
|
|
19
|
+
name: string;
|
|
20
|
+
email: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const COLUMNS: IColumn[] = [
|
|
24
|
+
{ key: 'name', name: 'Name', fieldName: 'name', minWidth: 120, maxWidth: 240, isResizable: true },
|
|
25
|
+
{ key: 'email', name: 'Email', fieldName: 'email', minWidth: 160, isResizable: true },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const SAMPLE: ContactRow[] = [
|
|
29
|
+
{ key: '1', name: 'Dana Reed', email: 'dana@contoso.com' },
|
|
30
|
+
{ key: '2', name: 'Lee Park', email: 'lee@fabrikam.com' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const App: React.FC = () => {
|
|
34
|
+
const [open, setOpen] = React.useState(false);
|
|
35
|
+
const [name, setName] = React.useState('');
|
|
36
|
+
const [email, setEmail] = React.useState('');
|
|
37
|
+
const [saving, setSaving] = React.useState(false);
|
|
38
|
+
|
|
39
|
+
const onSave = async () => {
|
|
40
|
+
setSaving(true);
|
|
41
|
+
try {
|
|
42
|
+
await createRecord('contact', { fullname: name, emailaddress1: email });
|
|
43
|
+
setOpen(false);
|
|
44
|
+
setName('');
|
|
45
|
+
setEmail('');
|
|
46
|
+
} catch (e) {
|
|
47
|
+
alert(`Save failed (Xrm not available here?): ${String(e)}`);
|
|
48
|
+
} finally {
|
|
49
|
+
setSaving(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<ThemeProvider>
|
|
55
|
+
<FormSurface maxWidth={960}>
|
|
56
|
+
<Section
|
|
57
|
+
title="{{projectName}}"
|
|
58
|
+
columnSpan={12}
|
|
59
|
+
showCommandBar
|
|
60
|
+
commandBarItems={[
|
|
61
|
+
{
|
|
62
|
+
key: 'new',
|
|
63
|
+
text: 'New record',
|
|
64
|
+
iconProps: { iconName: 'Add' },
|
|
65
|
+
onClick: () => setOpen(true),
|
|
66
|
+
},
|
|
67
|
+
]}
|
|
68
|
+
>
|
|
69
|
+
<DetailsList
|
|
70
|
+
items={SAMPLE}
|
|
71
|
+
columns={COLUMNS}
|
|
72
|
+
selectionMode={SelectionMode.none}
|
|
73
|
+
setKey="contacts"
|
|
74
|
+
/>
|
|
75
|
+
</Section>
|
|
76
|
+
</FormSurface>
|
|
77
|
+
|
|
78
|
+
<PanelSurface
|
|
79
|
+
isOpen={open}
|
|
80
|
+
onDismiss={() => setOpen(false)}
|
|
81
|
+
title="New contact"
|
|
82
|
+
width={480}
|
|
83
|
+
footer={
|
|
84
|
+
<Stack horizontal tokens={{ childrenGap: 8 }}>
|
|
85
|
+
<PrimaryButton text="Save" onClick={onSave} disabled={saving} />
|
|
86
|
+
<DefaultButton text="Cancel" onClick={() => setOpen(false)} />
|
|
87
|
+
</Stack>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<FieldRow label="Full name" required>
|
|
91
|
+
<TextField value={name} onChange={(_, v) => setName(v ?? '')} />
|
|
92
|
+
</FieldRow>
|
|
93
|
+
<FieldRow label="Email">
|
|
94
|
+
<TextField value={email} onChange={(_, v) => setEmail(v ?? '')} />
|
|
95
|
+
</FieldRow>
|
|
96
|
+
</PanelSurface>
|
|
97
|
+
</ThemeProvider>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "{{projectName}} — Dynamics 365 starter page (React, @dataverse-kit/surface-kit, Fluent v8)",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "webpack serve --mode development",
|
|
8
|
+
"build": "webpack --mode production",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"lint": "eslint src --ext .ts,.tsx"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^18.2.0",
|
|
14
|
+
"react-dom": "^18.2.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/react": "^18.2.0",
|
|
18
|
+
"@types/react-dom": "^18.2.0",
|
|
19
|
+
"@types/xrm": "^9.0.74",
|
|
20
|
+
"html-webpack-plugin": "^5.5.3",
|
|
21
|
+
"ts-loader": "^9.5.1",
|
|
22
|
+
"typescript": "^5.3.3",
|
|
23
|
+
"webpack": "^5.89.0",
|
|
24
|
+
"webpack-cli": "^5.1.4",
|
|
25
|
+
"webpack-dev-server": "^4.15.1"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { App } from './App';
|
|
4
|
+
|
|
5
|
+
// On localhost, `tools/dev/mock-xrm.js` (from the `serve` dev-tool) can be loaded
|
|
6
|
+
// to stub the Xrm global so this resource runs outside a model-driven host.
|
|
7
|
+
const container = document.getElementById('root');
|
|
8
|
+
if (container) {
|
|
9
|
+
createRoot(container).render(<App />);
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper over Xrm.WebApi for this starter page's data access. The Xrm global
|
|
3
|
+
* is typed via @types/xrm; on localhost, stub it with tools/dev/mock-xrm.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type DataverseRecord = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
export async function retrieveMultiple(
|
|
9
|
+
entityLogicalName: string,
|
|
10
|
+
options = ''
|
|
11
|
+
): Promise<DataverseRecord[]> {
|
|
12
|
+
const result = await Xrm.WebApi.retrieveMultipleRecords(entityLogicalName, options);
|
|
13
|
+
return result.entities;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function retrieveRecord(
|
|
17
|
+
entityLogicalName: string,
|
|
18
|
+
id: string,
|
|
19
|
+
options = ''
|
|
20
|
+
): Promise<DataverseRecord> {
|
|
21
|
+
return Xrm.WebApi.retrieveRecord(entityLogicalName, id, options);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function createRecord(
|
|
25
|
+
entityLogicalName: string,
|
|
26
|
+
data: Record<string, unknown>
|
|
27
|
+
): Promise<string> {
|
|
28
|
+
const res = await Xrm.WebApi.createRecord(entityLogicalName, data);
|
|
29
|
+
return res.id;
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2018",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"lib": ["DOM", "DOM.Iterable", "ES2018"],
|
|
12
|
+
"types": ["xrm"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
entry: './src/index.tsx',
|
|
6
|
+
output: {
|
|
7
|
+
path: path.resolve(__dirname, 'dist'),
|
|
8
|
+
filename: '{{projectName}}.js',
|
|
9
|
+
clean: true,
|
|
10
|
+
},
|
|
11
|
+
resolve: { extensions: ['.ts', '.tsx', '.js'] },
|
|
12
|
+
module: {
|
|
13
|
+
rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }],
|
|
14
|
+
},
|
|
15
|
+
plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
|
|
16
|
+
devServer: { static: './dist', port: 8080, hot: true },
|
|
17
|
+
};
|