@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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "{{componentName}} — Dynamics 365 editable-grid customizer (Fluent v9)",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "webpack --mode production",
|
|
8
|
+
"dev": "webpack --mode development --watch",
|
|
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
|
+
"@fluentui/react-components": "^9.46.2"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^18.2.0",
|
|
19
|
+
"@types/react-dom": "^18.2.0",
|
|
20
|
+
"ts-loader": "^9.5.1",
|
|
21
|
+
"typescript": "^5.3.3",
|
|
22
|
+
"webpack": "^5.89.0",
|
|
23
|
+
"webpack-cli": "^5.1.4"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IGridCustomizer, CellRendererOverrides } from './types/grid-types';
|
|
2
|
+
import {
|
|
3
|
+
StatusBadgeRenderer,
|
|
4
|
+
CurrencyRenderer,
|
|
5
|
+
DateRenderer,
|
|
6
|
+
} from './cell-renderers';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* {{componentName}} — editable-grid customizer. Map a column data type (or a
|
|
10
|
+
* specific column logical name) to a cell renderer. Register this customizer on
|
|
11
|
+
* the grid control's customizer property.
|
|
12
|
+
*/
|
|
13
|
+
export class {{componentName}} implements IGridCustomizer {
|
|
14
|
+
public cellRendererOverrides: CellRendererOverrides = {
|
|
15
|
+
// By data type
|
|
16
|
+
OptionSet: StatusBadgeRenderer,
|
|
17
|
+
Currency: CurrencyRenderer,
|
|
18
|
+
// By column logical name
|
|
19
|
+
statuscode: StatusBadgeRenderer,
|
|
20
|
+
statecode: StatusBadgeRenderer,
|
|
21
|
+
createdon: DateRenderer,
|
|
22
|
+
modifiedon: DateRenderer,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
public cellEditorOverrides?: CellRendererOverrides = {
|
|
26
|
+
// Add custom inline editors here, keyed the same way as renderers.
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Badge, Text } from '@fluentui/react-components';
|
|
3
|
+
import type { CellRenderer } from './types/grid-types';
|
|
4
|
+
import { getStatusColor } from './utils/color-utils';
|
|
5
|
+
|
|
6
|
+
/** Render an option-set / status value as a colored badge. */
|
|
7
|
+
export const StatusBadgeRenderer: CellRenderer = ({ value, formattedValue }) => {
|
|
8
|
+
const text = formattedValue ?? String(value ?? '');
|
|
9
|
+
return (
|
|
10
|
+
<Badge appearance="filled" style={{ backgroundColor: getStatusColor(text) }}>
|
|
11
|
+
{text}
|
|
12
|
+
</Badge>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Render a number as currency. */
|
|
17
|
+
export const CurrencyRenderer: CellRenderer = ({ value }) => {
|
|
18
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
19
|
+
const text = Number.isNaN(n)
|
|
20
|
+
? ''
|
|
21
|
+
: new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' }).format(n);
|
|
22
|
+
return <Text>{text}</Text>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Render a 0–100 number as a percentage. */
|
|
26
|
+
export const ProgressRenderer: CellRenderer = ({ value }) => {
|
|
27
|
+
const n = Math.max(0, Math.min(100, Number(value) || 0));
|
|
28
|
+
return <Text>{n}%</Text>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Render a date value as a localized date string. */
|
|
32
|
+
export const DateRenderer: CellRenderer = ({ value }) => {
|
|
33
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
34
|
+
return <Text>{Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString()}</Text>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface GridColumn {
|
|
4
|
+
name: string;
|
|
5
|
+
displayName: string;
|
|
6
|
+
dataType: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CellRendererProps {
|
|
10
|
+
value: unknown;
|
|
11
|
+
formattedValue?: string;
|
|
12
|
+
column: GridColumn;
|
|
13
|
+
rowData: Record<string, unknown>;
|
|
14
|
+
entityId: string;
|
|
15
|
+
entityName: string;
|
|
16
|
+
isEditing: boolean;
|
|
17
|
+
isDisabled: boolean;
|
|
18
|
+
onChange?: (value: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CellRenderer = React.FC<CellRendererProps>;
|
|
22
|
+
|
|
23
|
+
export type CellRendererOverrides = {
|
|
24
|
+
[key: string]: CellRenderer;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface IGridCustomizer {
|
|
28
|
+
cellRendererOverrides: CellRendererOverrides;
|
|
29
|
+
cellEditorOverrides?: CellRendererOverrides;
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function stringToColor(str: string): string {
|
|
2
|
+
let hash = 0;
|
|
3
|
+
for (let i = 0; i < str.length; i++) {
|
|
4
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
5
|
+
}
|
|
6
|
+
const hue = hash % 360;
|
|
7
|
+
return `hsl(${hue}, 65%, 45%)`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const STATUS_COLORS: Record<string, string> = {
|
|
11
|
+
active: '#107c10',
|
|
12
|
+
inactive: '#a19f9d',
|
|
13
|
+
draft: '#0078d4',
|
|
14
|
+
pending: '#ffaa44',
|
|
15
|
+
approved: '#107c10',
|
|
16
|
+
rejected: '#d13438',
|
|
17
|
+
completed: '#107c10',
|
|
18
|
+
cancelled: '#a19f9d',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function getStatusColor(status: string): string {
|
|
22
|
+
const normalized = status.toLowerCase().replace(/\s+/g, '');
|
|
23
|
+
return STATUS_COLORS[normalized] || stringToColor(status);
|
|
24
|
+
}
|
|
@@ -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
|
+
"declaration": false,
|
|
12
|
+
"lib": ["DOM", "DOM.Iterable", "ES2018"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
entry: './src/index.ts',
|
|
5
|
+
output: {
|
|
6
|
+
path: path.resolve(__dirname, 'dist'),
|
|
7
|
+
filename: '{{projectName}}.js',
|
|
8
|
+
library: { type: 'umd', name: '{{componentName}}' },
|
|
9
|
+
clean: true,
|
|
10
|
+
},
|
|
11
|
+
resolve: { extensions: ['.ts', '.tsx', '.js'] },
|
|
12
|
+
module: {
|
|
13
|
+
rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }],
|
|
14
|
+
},
|
|
15
|
+
// Provided by the grid host at runtime.
|
|
16
|
+
externals: { react: 'React', 'react-dom': 'ReactDOM' },
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<manifest>
|
|
3
|
+
<control namespace="{{namespace}}" constructor="{{componentName}}" version="1.0.0" display-name-key="{{namespace}}_{{componentName}}" description-key="{{namespace}}_{{componentName}}_Desc" control-type="standard">
|
|
4
|
+
<data-set name="dataset" display-name-key="Dataset_Display_Key">
|
|
5
|
+
</data-set>
|
|
6
|
+
<resources>
|
|
7
|
+
<code path="index.ts" order="1"/>
|
|
8
|
+
<platform-library name="React" version="16.8.6" />
|
|
9
|
+
<platform-library name="Fluent" version="8.29.0" />
|
|
10
|
+
<resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
|
|
11
|
+
</resources>
|
|
12
|
+
<feature-usage>
|
|
13
|
+
<uses-feature name="Utility" required="true" />
|
|
14
|
+
</feature-usage>
|
|
15
|
+
</control>
|
|
16
|
+
</manifest>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# {{componentName}} (PCF dataset control)
|
|
2
|
+
|
|
3
|
+
A PowerApps Component Framework **dataset** control — binds to a grid/view and renders the records
|
|
4
|
+
with a Fluent UI v8 `DetailsList`.
|
|
5
|
+
|
|
6
|
+
## Develop
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
npm run build # pcf-scripts build
|
|
11
|
+
npm start # pcf-scripts start (test harness)
|
|
12
|
+
npm run refreshTypes # regenerate generated/ManifestTypes after manifest edits
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Bind it to a dataset (subgrid/view) on a form. Customize columns/rendering in
|
|
16
|
+
`{{componentName}}Component.tsx`.
|
|
17
|
+
|
|
18
|
+
## Namespace / constructor
|
|
19
|
+
|
|
20
|
+
- Namespace: `{{namespace}}`
|
|
21
|
+
- Constructor: `{{componentName}}`
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { IInputs, IOutputs } from "./generated/ManifestTypes";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import * as ReactDOM from "react-dom";
|
|
4
|
+
import { {{componentName}}Component } from "./{{componentName}}Component";
|
|
5
|
+
|
|
6
|
+
export class {{componentName}} implements ComponentFramework.StandardControl<IInputs, IOutputs> {
|
|
7
|
+
private _container: HTMLDivElement;
|
|
8
|
+
private _context: ComponentFramework.Context<IInputs>;
|
|
9
|
+
|
|
10
|
+
constructor() {}
|
|
11
|
+
|
|
12
|
+
public init(
|
|
13
|
+
context: ComponentFramework.Context<IInputs>,
|
|
14
|
+
notifyOutputChanged: () => void,
|
|
15
|
+
state: ComponentFramework.Dictionary,
|
|
16
|
+
container: HTMLDivElement
|
|
17
|
+
): void {
|
|
18
|
+
this._context = context;
|
|
19
|
+
this._container = container;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public updateView(context: ComponentFramework.Context<IInputs>): void {
|
|
23
|
+
this._context = context;
|
|
24
|
+
ReactDOM.render(
|
|
25
|
+
React.createElement({{componentName}}Component, {
|
|
26
|
+
dataset: context.parameters.dataset
|
|
27
|
+
}),
|
|
28
|
+
this._container
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public getOutputs(): IOutputs {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public destroy(): void {
|
|
37
|
+
ReactDOM.unmountComponentAtNode(this._container);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "{{componentName}} PCF control built with Dynamics UI Kit",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "pcf-scripts build",
|
|
7
|
+
"clean": "pcf-scripts clean",
|
|
8
|
+
"rebuild": "pcf-scripts rebuild",
|
|
9
|
+
"start": "pcf-scripts start",
|
|
10
|
+
"refreshTypes": "pcf-scripts refreshTypes"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@fluentui/react": "^8.110.10",
|
|
14
|
+
"@types/powerapps-component-framework": "^1.3.4"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@microsoft/eslint-config-spfx": "^1.18.2",
|
|
18
|
+
"@types/node": "^18.16.9",
|
|
19
|
+
"@types/react": "^16.14.34",
|
|
20
|
+
"@types/react-dom": "^16.9.17",
|
|
21
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
22
|
+
"pcf-scripts": "^1.0.0",
|
|
23
|
+
"pcf-start": "^1.0.0",
|
|
24
|
+
"typescript": "^4.9.5"
|
|
25
|
+
},
|
|
26
|
+
"browserslist": [
|
|
27
|
+
"last 2 versions",
|
|
28
|
+
"ie 11"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<root>
|
|
3
|
+
<resheader name="resmimetype">
|
|
4
|
+
<value>text/microsoft-resx</value>
|
|
5
|
+
</resheader>
|
|
6
|
+
<resheader name="version">
|
|
7
|
+
<value>2.0</value>
|
|
8
|
+
</resheader>
|
|
9
|
+
<resheader name="reader">
|
|
10
|
+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
11
|
+
</resheader>
|
|
12
|
+
<resheader name="writer">
|
|
13
|
+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
14
|
+
</resheader>
|
|
15
|
+
<data name="{{namespace}}_{{componentName}}" xml:space="preserve">
|
|
16
|
+
<value>{{componentName}}</value>
|
|
17
|
+
<comment>Name of the control</comment>
|
|
18
|
+
</data>
|
|
19
|
+
<data name="{{namespace}}_{{componentName}}_Desc" xml:space="preserve">
|
|
20
|
+
<value>{{componentName}} control built with Dynamics UI Kit</value>
|
|
21
|
+
<comment>Description of the control</comment>
|
|
22
|
+
</data>
|
|
23
|
+
<data name="Value" xml:space="preserve">
|
|
24
|
+
<value>Value</value>
|
|
25
|
+
<comment>Value property</comment>
|
|
26
|
+
</data>
|
|
27
|
+
<data name="Value_Desc" xml:space="preserve">
|
|
28
|
+
<value>The value of the control</value>
|
|
29
|
+
<comment>Description of the Value property</comment>
|
|
30
|
+
</data>
|
|
31
|
+
<data name="Placeholder" xml:space="preserve">
|
|
32
|
+
<value>Placeholder</value>
|
|
33
|
+
<comment>Placeholder property</comment>
|
|
34
|
+
</data>
|
|
35
|
+
<data name="Placeholder_Desc" xml:space="preserve">
|
|
36
|
+
<value>Placeholder text for the control</value>
|
|
37
|
+
<comment>Description of the Placeholder property</comment>
|
|
38
|
+
</data>
|
|
39
|
+
<data name="Disabled" xml:space="preserve">
|
|
40
|
+
<value>Disabled</value>
|
|
41
|
+
<comment>Disabled property</comment>
|
|
42
|
+
</data>
|
|
43
|
+
<data name="Disabled_Desc" xml:space="preserve">
|
|
44
|
+
<value>Whether the control is disabled</value>
|
|
45
|
+
<comment>Description of the Disabled property</comment>
|
|
46
|
+
</data>
|
|
47
|
+
</root>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { DetailsList, IColumn, SelectionMode } from '@fluentui/react/lib/DetailsList';
|
|
3
|
+
import { initializeIcons } from '@fluentui/react/lib/Icons';
|
|
4
|
+
|
|
5
|
+
// Initialize Fluent UI icons
|
|
6
|
+
initializeIcons();
|
|
7
|
+
|
|
8
|
+
export interface I{{componentName}}ComponentProps {
|
|
9
|
+
dataset: ComponentFramework.PropertyTypes.DataSet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Row = Record<string, string>;
|
|
13
|
+
|
|
14
|
+
export const {{componentName}}Component: React.FC<I{{componentName}}ComponentProps> = ({ dataset }) => {
|
|
15
|
+
const columns: IColumn[] = dataset.columns
|
|
16
|
+
.filter((c) => !c.isHidden)
|
|
17
|
+
.sort((a, b) => a.order - b.order)
|
|
18
|
+
.map((c) => ({
|
|
19
|
+
key: c.name,
|
|
20
|
+
name: c.displayName,
|
|
21
|
+
fieldName: c.name,
|
|
22
|
+
minWidth: 100,
|
|
23
|
+
maxWidth: 240,
|
|
24
|
+
isResizable: true,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const items: Row[] = dataset.sortedRecordIds.map((id) => {
|
|
28
|
+
const record = dataset.records[id];
|
|
29
|
+
const row: Row = { key: id };
|
|
30
|
+
for (const col of dataset.columns) {
|
|
31
|
+
row[col.name] = record.getFormattedValue(col.name);
|
|
32
|
+
}
|
|
33
|
+
return row;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<DetailsList items={items} columns={columns} selectionMode={SelectionMode.none} />
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<manifest>
|
|
3
|
+
<control namespace="{{namespace}}" constructor="{{componentName}}" version="1.0.0" display-name-key="{{namespace}}_{{componentName}}" description-key="{{namespace}}_{{componentName}}_Desc" control-type="standard">
|
|
4
|
+
<property name="value" display-name-key="Value" description-key="Value_Desc" of-type="{{propertyType}}" usage="bound" required="true" />
|
|
5
|
+
<property name="placeholder" display-name-key="Placeholder" description-key="Placeholder_Desc" of-type="SingleLine.Text" usage="input" required="false" />
|
|
6
|
+
<property name="disabled" display-name-key="Disabled" description-key="Disabled_Desc" of-type="TwoOptions" usage="input" required="false" default-value="false" />
|
|
7
|
+
<resources>
|
|
8
|
+
<code path="index.ts" order="1"/>
|
|
9
|
+
<platform-library name="React" version="16.8.6" />
|
|
10
|
+
<platform-library name="Fluent" version="8.29.0" />
|
|
11
|
+
<resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
|
|
12
|
+
</resources>
|
|
13
|
+
<feature-usage>
|
|
14
|
+
<uses-feature name="Utility" required="true" />
|
|
15
|
+
</feature-usage>
|
|
16
|
+
</control>
|
|
17
|
+
</manifest>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# {{componentName}} PCF Control
|
|
2
|
+
|
|
3
|
+
A PowerApps Component Framework (PCF) control built with the Dynamics UI Kit and Fluent UI v8.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This control provides a {{componentName}} interface using enterprise-grade patterns from the Dynamics UI Kit.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Built with Fluent UI v8 for consistency with Dynamics 365
|
|
12
|
+
- TypeScript support with full type safety
|
|
13
|
+
- Responsive design
|
|
14
|
+
- Accessibility compliant (ARIA)
|
|
15
|
+
- Enterprise-grade error handling and validation
|
|
16
|
+
|
|
17
|
+
## Development
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
- Node.js (v18 or higher)
|
|
22
|
+
- Power Platform CLI (`pac`)
|
|
23
|
+
|
|
24
|
+
### Getting Started
|
|
25
|
+
|
|
26
|
+
1. Install dependencies:
|
|
27
|
+
```bash
|
|
28
|
+
npm install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Build the control:
|
|
32
|
+
```bash
|
|
33
|
+
npm run build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. Start development server:
|
|
37
|
+
```bash
|
|
38
|
+
npm start
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Building for Production
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Testing
|
|
48
|
+
|
|
49
|
+
The control can be tested using the Power Platform CLI test harness:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm start
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Deployment
|
|
56
|
+
|
|
57
|
+
### To Dynamics 365
|
|
58
|
+
|
|
59
|
+
1. Build the solution:
|
|
60
|
+
```bash
|
|
61
|
+
# publisher-prefix must be 2-8 lowercase alphanumeric chars (e.g. "ctso")
|
|
62
|
+
pac solution init --publisher-name {{namespace}} --publisher-prefix <2-8 chars>
|
|
63
|
+
pac solution add-reference --path .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. Build solution package:
|
|
67
|
+
```bash
|
|
68
|
+
msbuild /p:configuration=Release
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. Import the solution package into your Dynamics 365 environment.
|
|
72
|
+
|
|
73
|
+
### Configuration
|
|
74
|
+
|
|
75
|
+
The control accepts the following properties:
|
|
76
|
+
|
|
77
|
+
- **value**: The current value of the control
|
|
78
|
+
- **placeholder**: Placeholder text to display
|
|
79
|
+
- **disabled**: Whether the control is disabled
|
|
80
|
+
|
|
81
|
+
## Architecture
|
|
82
|
+
|
|
83
|
+
This control follows enterprise patterns:
|
|
84
|
+
|
|
85
|
+
- **Component Separation**: Logic separated from presentation
|
|
86
|
+
- **Type Safety**: Full TypeScript implementation
|
|
87
|
+
- **Error Handling**: Comprehensive error boundaries
|
|
88
|
+
- **Performance**: Optimized rendering with React best practices
|
|
89
|
+
|
|
90
|
+
## Support
|
|
91
|
+
|
|
92
|
+
For issues and questions:
|
|
93
|
+
1. Check the troubleshooting guide
|
|
94
|
+
2. Review the Dynamics UI Kit documentation
|
|
95
|
+
3. Contact your development team
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Toggle } from '@fluentui/react/lib/Toggle';
|
|
3
|
+
|
|
4
|
+
export interface ValueInputProps {
|
|
5
|
+
value: boolean;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange: (value: boolean) => void;
|
|
9
|
+
onBlur: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ValueInput: React.FC<ValueInputProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
disabled,
|
|
15
|
+
onChange,
|
|
16
|
+
onBlur,
|
|
17
|
+
}) => (
|
|
18
|
+
<Toggle
|
|
19
|
+
checked={value}
|
|
20
|
+
disabled={disabled}
|
|
21
|
+
onChange={(_, checked) => onChange(!!checked)}
|
|
22
|
+
onBlur={onBlur}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { DatePicker } from '@fluentui/react/lib/DatePicker';
|
|
3
|
+
|
|
4
|
+
export interface ValueInputProps {
|
|
5
|
+
value: Date | null;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange: (value: Date | null) => void;
|
|
9
|
+
onBlur: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ValueInput: React.FC<ValueInputProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
placeholder,
|
|
15
|
+
disabled,
|
|
16
|
+
onChange,
|
|
17
|
+
onBlur,
|
|
18
|
+
}) => (
|
|
19
|
+
<DatePicker
|
|
20
|
+
value={value ?? undefined}
|
|
21
|
+
placeholder={placeholder}
|
|
22
|
+
disabled={disabled}
|
|
23
|
+
onSelectDate={(d) => onChange(d ?? null)}
|
|
24
|
+
onBlur={onBlur}
|
|
25
|
+
styles={{ root: { width: '100%' } }}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { TextField } from '@fluentui/react/lib/TextField';
|
|
3
|
+
|
|
4
|
+
export interface ValueInputProps {
|
|
5
|
+
value: number | null;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange: (value: number | null) => void;
|
|
9
|
+
onBlur: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ValueInput: React.FC<ValueInputProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
placeholder,
|
|
15
|
+
disabled,
|
|
16
|
+
onChange,
|
|
17
|
+
onBlur,
|
|
18
|
+
}) => (
|
|
19
|
+
<TextField
|
|
20
|
+
type="number"
|
|
21
|
+
value={value === null ? '' : String(value)}
|
|
22
|
+
placeholder={placeholder}
|
|
23
|
+
disabled={disabled}
|
|
24
|
+
onChange={(_, v) => {
|
|
25
|
+
if (!v) {
|
|
26
|
+
onChange(null);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const n = Number(v);
|
|
30
|
+
onChange(Number.isNaN(n) ? null : n);
|
|
31
|
+
}}
|
|
32
|
+
onBlur={onBlur}
|
|
33
|
+
styles={{ root: { width: '100%' } }}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { TextField } from '@fluentui/react/lib/TextField';
|
|
3
|
+
|
|
4
|
+
export interface ValueInputProps {
|
|
5
|
+
value: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange: (value: string) => void;
|
|
9
|
+
onBlur: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ValueInput: React.FC<ValueInputProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
placeholder,
|
|
15
|
+
disabled,
|
|
16
|
+
onChange,
|
|
17
|
+
onBlur,
|
|
18
|
+
}) => (
|
|
19
|
+
<TextField
|
|
20
|
+
value={value}
|
|
21
|
+
placeholder={placeholder}
|
|
22
|
+
disabled={disabled}
|
|
23
|
+
onChange={(_, v) => onChange(v ?? '')}
|
|
24
|
+
onBlur={onBlur}
|
|
25
|
+
styles={{ root: { width: '100%' } }}
|
|
26
|
+
/>
|
|
27
|
+
);
|