@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.
Files changed (210) hide show
  1. package/README.md +74 -0
  2. package/dist/artifacts/registry.d.ts +18 -0
  3. package/dist/artifacts/registry.d.ts.map +1 -0
  4. package/dist/artifacts/registry.js +340 -0
  5. package/dist/artifacts/registry.js.map +1 -0
  6. package/dist/artifacts/types.d.ts +122 -0
  7. package/dist/artifacts/types.d.ts.map +1 -0
  8. package/dist/artifacts/types.js +7 -0
  9. package/dist/artifacts/types.js.map +1 -0
  10. package/dist/artifacts/validators.d.ts +16 -0
  11. package/dist/artifacts/validators.d.ts.map +1 -0
  12. package/dist/artifacts/validators.js +45 -0
  13. package/dist/artifacts/validators.js.map +1 -0
  14. package/dist/fromDesign.d.ts +5 -0
  15. package/dist/fromDesign.d.ts.map +1 -0
  16. package/dist/fromDesign.js +98 -0
  17. package/dist/fromDesign.js.map +1 -0
  18. package/dist/index.js +129 -177
  19. package/dist/index.js.map +1 -1
  20. package/dist/injectDevTools.d.ts +28 -0
  21. package/dist/injectDevTools.d.ts.map +1 -0
  22. package/dist/injectDevTools.js +148 -0
  23. package/dist/injectDevTools.js.map +1 -0
  24. package/dist/scaffold.d.ts +48 -0
  25. package/dist/scaffold.d.ts.map +1 -0
  26. package/dist/scaffold.js +180 -0
  27. package/dist/scaffold.js.map +1 -0
  28. package/dist/templatePlan.d.ts +3 -0
  29. package/dist/templatePlan.d.ts.map +1 -0
  30. package/dist/templatePlan.js +43 -0
  31. package/dist/templatePlan.js.map +1 -0
  32. package/dist/utils/copyTemplate.d.ts +13 -1
  33. package/dist/utils/copyTemplate.d.ts.map +1 -1
  34. package/dist/utils/copyTemplate.js +98 -4
  35. package/dist/utils/copyTemplate.js.map +1 -1
  36. package/dist/utils/updatePackageJson.d.ts +11 -1
  37. package/dist/utils/updatePackageJson.d.ts.map +1 -1
  38. package/dist/utils/updatePackageJson.js +12 -10
  39. package/dist/utils/updatePackageJson.js.map +1 -1
  40. package/package.json +10 -7
  41. package/templates/_shared/dev-tools/auth/get-token.js +72 -0
  42. package/templates/_shared/dev-tools/dev/mock-xrm.js +42 -0
  43. package/templates/_shared/dev-tools/metadata-sync/index.js +152 -0
  44. package/templates/_shared/dev-tools/smoke/test-retrieve.js +44 -0
  45. package/templates/dialog-form/README.md +27 -0
  46. package/templates/dialog-form/_variants/App.v8.tsx +39 -0
  47. package/templates/dialog-form/_variants/App.v9.tsx +41 -0
  48. package/templates/dialog-form/gitignore +5 -0
  49. package/templates/dialog-form/package.json +27 -0
  50. package/templates/dialog-form/public/index.html +11 -0
  51. package/templates/dialog-form/src/index.tsx +10 -0
  52. package/templates/dialog-form/src/services/dataverse.ts +30 -0
  53. package/templates/dialog-form/tsconfig.json +15 -0
  54. package/templates/dialog-form/webpack.config.js +17 -0
  55. package/templates/grid-customizer/README.md +28 -0
  56. package/templates/grid-customizer/gitignore +4 -0
  57. package/templates/grid-customizer/package.json +25 -0
  58. package/templates/grid-customizer/src/GridCustomizer.ts +28 -0
  59. package/templates/grid-customizer/src/cell-renderers.tsx +35 -0
  60. package/templates/grid-customizer/src/index.ts +4 -0
  61. package/templates/grid-customizer/src/types/grid-types.ts +30 -0
  62. package/templates/grid-customizer/src/utils/color-utils.ts +24 -0
  63. package/templates/grid-customizer/tsconfig.json +15 -0
  64. package/templates/grid-customizer/webpack.config.js +17 -0
  65. package/templates/pcf-dataset/ControlManifest.Input.xml +16 -0
  66. package/templates/pcf-dataset/README.md +21 -0
  67. package/templates/pcf-dataset/gitignore +5 -0
  68. package/templates/pcf-dataset/index.ts +39 -0
  69. package/templates/pcf-dataset/package.json +30 -0
  70. package/templates/pcf-dataset/strings/{{componentName}}.1033.resx +47 -0
  71. package/templates/pcf-dataset/tsconfig.json +8 -0
  72. package/templates/pcf-dataset/{{componentName}}Component.tsx +39 -0
  73. package/templates/pcf-field/ControlManifest.Input.xml +17 -0
  74. package/templates/pcf-field/README.md +95 -0
  75. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +24 -0
  76. package/templates/pcf-field/_variants/ValueInput.date.tsx +27 -0
  77. package/templates/pcf-field/_variants/ValueInput.number.tsx +35 -0
  78. package/templates/pcf-field/_variants/ValueInput.text.tsx +27 -0
  79. package/templates/pcf-field/gitignore +5 -0
  80. package/templates/pcf-field/index.ts +61 -0
  81. package/templates/pcf-field/package.json +30 -0
  82. package/templates/pcf-field/strings/{{componentName}}.1033.resx +47 -0
  83. package/templates/pcf-field/tsconfig.json +8 -0
  84. package/templates/pcf-field/{{componentName}}Component.tsx +35 -0
  85. package/templates/power-pages-starter/gitignore +5 -0
  86. package/templates/react-custom-page/gitignore +5 -0
  87. package/templates/{dynamics-365-starter → react-custom-page}/package.json +3 -3
  88. package/templates/react-custom-page/tools/metadata-sync/index.js +152 -0
  89. package/templates/static-web-app/README.md +36 -0
  90. package/templates/static-web-app/_variants/App.v8.tsx +32 -0
  91. package/templates/static-web-app/_variants/App.v9.tsx +31 -0
  92. package/templates/static-web-app/api/host.json +12 -0
  93. package/templates/static-web-app/api/package.json +19 -0
  94. package/templates/static-web-app/api/src/functions/hello.ts +16 -0
  95. package/templates/static-web-app/api/tsconfig.json +14 -0
  96. package/templates/static-web-app/frontend/index.html +12 -0
  97. package/templates/static-web-app/frontend/package.json +23 -0
  98. package/templates/static-web-app/frontend/src/index.tsx +8 -0
  99. package/templates/static-web-app/frontend/tsconfig.json +16 -0
  100. package/templates/static-web-app/frontend/vite.config.ts +13 -0
  101. package/templates/static-web-app/gitignore +8 -0
  102. package/templates/static-web-app/package.json +15 -0
  103. package/templates/static-web-app/staticwebapp.config.json +7 -0
  104. package/templates/teams-app/README.md +27 -0
  105. package/templates/teams-app/_variants/graph.off.ts +7 -0
  106. package/templates/teams-app/_variants/graph.on.ts +22 -0
  107. package/templates/teams-app/appPackage/manifest.json +26 -0
  108. package/templates/teams-app/gitignore +5 -0
  109. package/templates/teams-app/index.html +12 -0
  110. package/templates/teams-app/package.json +26 -0
  111. package/templates/teams-app/src/App.tsx +25 -0
  112. package/templates/teams-app/src/index.tsx +8 -0
  113. package/templates/teams-app/tsconfig.json +16 -0
  114. package/templates/teams-app/vite.config.ts +9 -0
  115. package/templates/web-resource/README.md +39 -0
  116. package/templates/web-resource/_variants/App.v8.tsx +29 -0
  117. package/templates/web-resource/_variants/App.v9.tsx +28 -0
  118. package/templates/web-resource/gitignore +5 -0
  119. package/templates/web-resource/package.json +27 -0
  120. package/templates/web-resource/public/index.html +11 -0
  121. package/templates/web-resource/src/index.tsx +10 -0
  122. package/templates/web-resource/src/services/dataverse.ts +30 -0
  123. package/templates/web-resource/tsconfig.json +15 -0
  124. package/templates/web-resource/webpack.config.js +17 -0
  125. package/dist/utils/consultingHelpers.d.ts +0 -13
  126. package/dist/utils/consultingHelpers.d.ts.map +0 -1
  127. package/dist/utils/consultingHelpers.js +0 -569
  128. package/dist/utils/consultingHelpers.js.map +0 -1
  129. package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +0 -302
  130. package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +0 -305
  131. package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +0 -507
  132. package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +0 -372
  133. package/templates/dynamics-365-starter/deployment/pipelines/README.md +0 -375
  134. package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +0 -330
  135. package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +0 -422
  136. package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +0 -636
  137. package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +0 -417
  138. package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +0 -582
  139. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +0 -486
  140. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +0 -567
  141. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +0 -703
  142. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +0 -671
  143. package/templates/dynamics-365-starter/docs/team-standards/README.md +0 -273
  144. package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +0 -577
  145. package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +0 -359
  146. package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +0 -700
  147. package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +0 -736
  148. package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +0 -727
  149. package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +0 -758
  150. package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +0 -878
  151. package/templates/dynamics-365-starter/src/client-project-template/README.md +0 -234
  152. package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +0 -114
  153. package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +0 -186
  154. package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +0 -667
  155. package/templates/dynamics-365-starter/src/examples/README.md +0 -52
  156. package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +0 -625
  157. package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +0 -545
  158. package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +0 -722
  159. package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +0 -662
  160. package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +0 -519
  161. package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +0 -456
  162. package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +0 -406
  163. package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +0 -578
  164. package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +0 -629
  165. package/templates/dynamics-365-starter/tools/entity-generator/index.js +0 -168
  166. package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +0 -124
  167. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +0 -283
  168. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +0 -275
  169. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +0 -204
  170. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +0 -413
  171. package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +0 -250
  172. package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +0 -410
  173. package/templates/dynamics-365-starter/tools/metadata-sync/index.js +0 -512
  174. package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +0 -675
  175. /package/templates/{dynamics-365-starter → react-custom-page}/README.md +0 -0
  176. /package/templates/{dynamics-365-starter → react-custom-page}/deployment/README.md +0 -0
  177. /package/templates/{dynamics-365-starter → react-custom-page}/docs/ARCHITECTURE_OVERVIEW.md +0 -0
  178. /package/templates/{dynamics-365-starter → react-custom-page}/docs/BEST_PRACTICES.md +0 -0
  179. /package/templates/{dynamics-365-starter → react-custom-page}/docs/MIGRATION_GUIDE.md +0 -0
  180. /package/templates/{dynamics-365-starter → react-custom-page}/public/index.html +0 -0
  181. /package/templates/{dynamics-365-starter → react-custom-page}/scripts/custom-build.js +0 -0
  182. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.css +0 -0
  183. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.tsx +0 -0
  184. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.css +0 -0
  185. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.tsx +0 -0
  186. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.css +0 -0
  187. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.tsx +0 -0
  188. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.css +0 -0
  189. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.tsx +0 -0
  190. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LogDialog.tsx +0 -0
  191. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingContext.tsx +0 -0
  192. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.css +0 -0
  193. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.tsx +0 -0
  194. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingProvider.tsx +0 -0
  195. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/logger.ts +0 -0
  196. /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/account.ts +0 -0
  197. /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/contact.ts +0 -0
  198. /package/templates/{dynamics-365-starter → react-custom-page}/src/index.tsx +0 -0
  199. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Account.ts +0 -0
  200. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/BaseEntity.ts +0 -0
  201. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Contact.ts +0 -0
  202. /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/ContactControlWrapper.tsx +0 -0
  203. /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/MultiEntityControlWrapper.tsx +0 -0
  204. /package/templates/{dynamics-365-starter → react-custom-page}/src/providers/DynamicsProvider.tsx +0 -0
  205. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/MockApiService.ts +0 -0
  206. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/ServiceFactory.ts +0 -0
  207. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/XrmApiService.ts +0 -0
  208. /package/templates/{dynamics-365-starter → react-custom-page}/src/styles/index.css +0 -0
  209. /package/templates/{dynamics-365-starter → react-custom-page}/tsconfig.json +0 -0
  210. /package/templates/{dynamics-365-starter → react-custom-page}/webpack.config.js +0 -0
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ *.log
4
+ .env
@@ -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,4 @@
1
+ export { {{componentName}} } from './GridCustomizer';
2
+ export * from './cell-renderers';
3
+ export * from './types/grid-types';
4
+ export * from './utils/color-utils';
@@ -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,5 @@
1
+ node_modules/
2
+ out/
3
+ generated/
4
+ *.log
5
+ .env
@@ -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,8 @@
1
+ {
2
+ "extends": "./node_modules/pcf-scripts/tsconfig_base.json",
3
+ "compilerOptions": {
4
+ "typeRoots": ["node_modules/@types"],
5
+ "jsx": "react",
6
+ "experimentalDecorators": true
7
+ }
8
+ }
@@ -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
+ );
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ out/
3
+ generated/
4
+ *.log
5
+ .env