@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
@@ -1,519 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
- import {
3
- Stack,
4
- Text,
5
- MessageBar,
6
- MessageBarType,
7
- Spinner,
8
- SpinnerSize,
9
- CommandBar,
10
- ICommandBarItemProps,
11
- Pivot,
12
- PivotItem,
13
- DetailsList,
14
- IColumn,
15
- SelectionMode,
16
- } from '@fluentui/react';
17
- import { BaseEntity } from '../models/BaseEntity';
18
- import { useDynamicsApi } from '../providers/DynamicsProvider';
19
- import { Logger } from '../components/Logging/logger';
20
-
21
- interface KPICard {
22
- title: string;
23
- value: string | number;
24
- trend?: 'up' | 'down' | 'neutral';
25
- trendValue?: string;
26
- color?: string;
27
- icon?: string;
28
- }
29
-
30
- interface ChartData {
31
- title: string;
32
- data: any[];
33
- type: 'bar' | 'line' | 'pie' | 'donut';
34
- }
35
-
36
- interface EntityDashboardProps<T extends BaseEntity> {
37
- title: string;
38
- entityLogicalName: string;
39
- entityClass: any;
40
- kpiQueries: { [key: string]: string }; // FetchXML queries for KPIs
41
- chartQueries?: { [key: string]: string }; // FetchXML queries for charts
42
- recentItemsQuery: string; // FetchXML for recent items
43
- recentItemsColumns: IColumn[];
44
- renderKPICards: (data: { [key: string]: any[] }) => KPICard[];
45
- renderCharts?: (data: { [key: string]: any[] }) => ChartData[];
46
- onItemSelected?: (item: T) => void;
47
- onCreateNew?: () => void;
48
- customCommands?: ICommandBarItemProps[];
49
- refreshInterval?: number; // Auto-refresh interval in seconds
50
- }
51
-
52
- /**
53
- * Generic entity dashboard template for Dynamics 365
54
- * Displays KPIs, charts, and recent items with real-time updates
55
- */
56
- export function EntityDashboard<T extends BaseEntity>({
57
- title,
58
- entityLogicalName,
59
- entityClass,
60
- kpiQueries,
61
- chartQueries = {},
62
- recentItemsQuery,
63
- recentItemsColumns,
64
- renderKPICards,
65
- renderCharts,
66
- onItemSelected,
67
- onCreateNew,
68
- customCommands = [],
69
- refreshInterval = 300, // 5 minutes default
70
- }: EntityDashboardProps<T>) {
71
- const { apiService } = useDynamicsApi();
72
- const [loading, setLoading] = useState(true);
73
- const [error, setError] = useState<string | null>(null);
74
- const [kpiData, setKpiData] = useState<{ [key: string]: any[] }>({});
75
- const [chartData, setChartData] = useState<{ [key: string]: any[] }>({});
76
- const [recentItems, setRecentItems] = useState<T[]>([]);
77
- const [lastRefresh, setLastRefresh] = useState<Date>(new Date());
78
- const [refreshTrigger, setRefreshTrigger] = useState(0);
79
-
80
- // Load all dashboard data
81
- const loadDashboardData = useCallback(async () => {
82
- if (!apiService) {
83
- setError('API service not available');
84
- setLoading(false);
85
- return;
86
- }
87
-
88
- try {
89
- setLoading(true);
90
- setError(null);
91
- Logger.log(`Loading ${title} dashboard...`);
92
-
93
- // Load KPI data
94
- const kpiResults: { [key: string]: any[] } = {};
95
- for (const [key, query] of Object.entries(kpiQueries)) {
96
- try {
97
- const result = await apiService.retrieveMultipleRecords(
98
- entityLogicalName,
99
- query
100
- );
101
- kpiResults[key] = result.entities || [];
102
- } catch (err) {
103
- const errorMessage = err instanceof Error ? err.message : String(err);
104
- Logger.warn(`Failed to load KPI data for ${key}:`, errorMessage);
105
- kpiResults[key] = [];
106
- }
107
- }
108
- setKpiData(kpiResults);
109
-
110
- // Load chart data
111
- const chartResults: { [key: string]: any[] } = {};
112
- for (const [key, query] of Object.entries(chartQueries)) {
113
- try {
114
- const result = await apiService.retrieveMultipleRecords(
115
- entityLogicalName,
116
- query
117
- );
118
- chartResults[key] = result.entities || [];
119
- } catch (err) {
120
- const errorMessage = err instanceof Error ? err.message : String(err);
121
- Logger.warn(`Failed to load chart data for ${key}:`, errorMessage);
122
- chartResults[key] = [];
123
- }
124
- }
125
- setChartData(chartResults);
126
-
127
- // Load recent items
128
- try {
129
- const result = await apiService.retrieveMultipleRecords(
130
- entityLogicalName,
131
- recentItemsQuery
132
- );
133
- const items = (result.entities || []).map(
134
- (data: any) => new entityClass(data)
135
- );
136
- setRecentItems(items);
137
- } catch (err) {
138
- const errorMessage = err instanceof Error ? err.message : String(err);
139
- Logger.warn('Failed to load recent items:', errorMessage);
140
- setRecentItems([]);
141
- }
142
-
143
- setLastRefresh(new Date());
144
- Logger.log(`${title} dashboard loaded successfully`);
145
- } catch (err) {
146
- const errorMessage =
147
- err instanceof Error
148
- ? err.message
149
- : `Failed to load ${title} dashboard`;
150
- setError(errorMessage);
151
- Logger.error(`Error loading ${title} dashboard:`, 'EntityDashboard', err);
152
- } finally {
153
- setLoading(false);
154
- }
155
- }, [
156
- apiService,
157
- title,
158
- entityLogicalName,
159
- entityClass,
160
- kpiQueries,
161
- chartQueries,
162
- recentItemsQuery,
163
- ]);
164
-
165
- // Initial load and auto-refresh
166
- useEffect(() => {
167
- loadDashboardData();
168
-
169
- if (refreshInterval > 0) {
170
- const interval = setInterval(loadDashboardData, refreshInterval * 1000);
171
- return () => clearInterval(interval);
172
- }
173
- }, [loadDashboardData, refreshInterval, refreshTrigger]);
174
-
175
- // Handle manual refresh
176
- const handleRefresh = () => {
177
- setRefreshTrigger((prev) => prev + 1);
178
- };
179
-
180
- // Handle item selection
181
- const handleItemClick = (item: T) => {
182
- if (onItemSelected) {
183
- onItemSelected(item);
184
- }
185
- };
186
-
187
- // Generate KPI cards
188
- const kpiCards = renderKPICards(kpiData);
189
-
190
- // Generate charts
191
- const charts = renderCharts ? renderCharts(chartData) : [];
192
-
193
- // Command bar items
194
- const commandBarItems: ICommandBarItemProps[] = [
195
- {
196
- key: 'refresh',
197
- text: 'Refresh',
198
- iconProps: { iconName: 'Refresh' },
199
- onClick: handleRefresh,
200
- },
201
- ...(onCreateNew
202
- ? [
203
- {
204
- key: 'new',
205
- text: `New ${title}`,
206
- iconProps: { iconName: 'Add' },
207
- onClick: onCreateNew,
208
- },
209
- ]
210
- : []),
211
- ...customCommands,
212
- ];
213
-
214
- // Enhanced columns with click handlers
215
- const enhancedColumns = recentItemsColumns.map((column) => ({
216
- ...column,
217
- onRender:
218
- column.onRender ||
219
- ((item: T) => {
220
- const value = (item as any)[column.fieldName || ''];
221
- if (column.key === recentItemsColumns[0].key) {
222
- // Make first column clickable
223
- return (
224
- <span
225
- style={{ color: '#0078d4', cursor: 'pointer', fontWeight: 500 }}
226
- onClick={() => handleItemClick(item)}
227
- >
228
- {value || ''}
229
- </span>
230
- );
231
- }
232
- return <span>{value || ''}</span>;
233
- }),
234
- }));
235
-
236
- const formatLastRefresh = () => {
237
- const now = new Date();
238
- const diffMs = now.getTime() - lastRefresh.getTime();
239
- const diffMins = Math.floor(diffMs / 60000);
240
-
241
- if (diffMins < 1) return 'Just now';
242
- if (diffMins === 1) return '1 minute ago';
243
- return `${diffMins} minutes ago`;
244
- };
245
-
246
- return (
247
- <div style={{ padding: '20px', maxWidth: '100%' }}>
248
- <Stack tokens={{ childrenGap: 24 }}>
249
- {/* Header */}
250
- <Stack
251
- horizontal
252
- horizontalAlign="space-between"
253
- verticalAlign="center"
254
- >
255
- <Stack>
256
- <Text
257
- variant="xxLarge"
258
- style={{ fontWeight: 600, color: '#323130' }}
259
- >
260
- {title} Dashboard
261
- </Text>
262
- <Text variant="small" style={{ color: '#605e5c' }}>
263
- Last updated: {formatLastRefresh()}
264
- </Text>
265
- </Stack>
266
- </Stack>
267
-
268
- {/* Error Message */}
269
- {error && (
270
- <MessageBar
271
- messageBarType={MessageBarType.error}
272
- onDismiss={() => setError(null)}
273
- >
274
- {error}
275
- </MessageBar>
276
- )}
277
-
278
- {/* Command Bar */}
279
- <CommandBar items={commandBarItems} />
280
-
281
- {loading ? (
282
- <div
283
- style={{
284
- display: 'flex',
285
- justifyContent: 'center',
286
- alignItems: 'center',
287
- minHeight: 300,
288
- flexDirection: 'column',
289
- }}
290
- >
291
- <Spinner
292
- size={SpinnerSize.large}
293
- label={`Loading ${title} dashboard...`}
294
- />
295
- </div>
296
- ) : (
297
- <Pivot defaultSelectedKey="overview">
298
- {/* Overview Tab */}
299
- <PivotItem headerText="Overview" itemKey="overview">
300
- <Stack tokens={{ childrenGap: 20 }}>
301
- {/* KPI Cards */}
302
- <Stack>
303
- <Text
304
- variant="large"
305
- style={{ fontWeight: 600, marginBottom: 16 }}
306
- >
307
- Key Metrics
308
- </Text>
309
- <Stack horizontal wrap tokens={{ childrenGap: 16 }}>
310
- {kpiCards.map((kpi, index) => (
311
- <div
312
- key={index}
313
- style={{
314
- minWidth: 200,
315
- maxWidth: 250,
316
- padding: 16,
317
- border: `2px solid ${kpi.color || '#edebe9'}`,
318
- borderRadius: 4,
319
- backgroundColor: '#ffffff',
320
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
321
- }}
322
- >
323
- <Stack>
324
- <Stack
325
- horizontal
326
- horizontalAlign="space-between"
327
- verticalAlign="center"
328
- >
329
- <Text variant="medium" style={{ color: '#605e5c' }}>
330
- {kpi.title}
331
- </Text>
332
- {kpi.icon && (
333
- <div
334
- style={{
335
- fontSize: 16,
336
- color: kpi.color || '#605e5c',
337
- }}
338
- >
339
- {kpi.icon}
340
- </div>
341
- )}
342
- </Stack>
343
- <Text
344
- variant="xxLarge"
345
- style={{
346
- fontWeight: 700,
347
- color: kpi.color || '#323130',
348
- margin: '8px 0',
349
- }}
350
- >
351
- {kpi.value}
352
- </Text>
353
- {kpi.trend && kpi.trendValue && (
354
- <Stack
355
- horizontal
356
- verticalAlign="center"
357
- tokens={{ childrenGap: 4 }}
358
- >
359
- <span
360
- style={{
361
- fontSize: 12,
362
- color:
363
- kpi.trend === 'up'
364
- ? '#107c10'
365
- : kpi.trend === 'down'
366
- ? '#d13438'
367
- : '#605e5c',
368
- }}
369
- >
370
- {kpi.trend === 'up'
371
- ? '↗️'
372
- : kpi.trend === 'down'
373
- ? '↘️'
374
- : '➡️'}
375
- </span>
376
- <Text
377
- variant="small"
378
- style={{
379
- color:
380
- kpi.trend === 'up'
381
- ? '#107c10'
382
- : kpi.trend === 'down'
383
- ? '#d13438'
384
- : '#605e5c',
385
- }}
386
- >
387
- {kpi.trendValue}
388
- </Text>
389
- </Stack>
390
- )}
391
- </Stack>
392
- </div>
393
- ))}
394
- </Stack>
395
- </Stack>
396
-
397
- {/* Recent Items */}
398
- <Stack>
399
- <Text
400
- variant="large"
401
- style={{ fontWeight: 600, marginBottom: 16 }}
402
- >
403
- Recent {title}
404
- </Text>
405
- {recentItems.length === 0 ? (
406
- <div
407
- style={{
408
- textAlign: 'center',
409
- padding: '40px 20px',
410
- color: '#605e5c',
411
- border: '1px solid #edebe9',
412
- borderRadius: 4,
413
- }}
414
- >
415
- <Text variant="medium">No recent items found</Text>
416
- </div>
417
- ) : (
418
- <DetailsList
419
- items={recentItems}
420
- columns={enhancedColumns}
421
- setKey="recent"
422
- layoutMode={0}
423
- selectionMode={SelectionMode.none}
424
- isHeaderVisible={true}
425
- />
426
- )}
427
- </Stack>
428
- </Stack>
429
- </PivotItem>
430
-
431
- {/* Charts Tab */}
432
- {charts.length > 0 && (
433
- <PivotItem headerText="Analytics" itemKey="analytics">
434
- <Stack tokens={{ childrenGap: 20 }}>
435
- {charts.map((chart, index) => (
436
- <div
437
- key={index}
438
- style={{
439
- padding: 16,
440
- backgroundColor: '#ffffff',
441
- border: '1px solid #edebe9',
442
- borderRadius: 4,
443
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
444
- }}
445
- >
446
- <Stack>
447
- <Text
448
- variant="large"
449
- style={{ fontWeight: 600, marginBottom: 16 }}
450
- >
451
- {chart.title}
452
- </Text>
453
- <div
454
- style={{
455
- minHeight: 300,
456
- display: 'flex',
457
- alignItems: 'center',
458
- justifyContent: 'center',
459
- border: '1px dashed #c8c6c4',
460
- borderRadius: 4,
461
- color: '#605e5c',
462
- }}
463
- >
464
- <Text variant="medium">
465
- Chart visualization for {chart.title} ({chart.type})
466
- </Text>
467
- </div>
468
- </Stack>
469
- </div>
470
- ))}
471
- </Stack>
472
- </PivotItem>
473
- )}
474
- </Pivot>
475
- )}
476
- </Stack>
477
- </div>
478
- );
479
- }
480
-
481
- // Helper functions for common KPI calculations
482
- export const KPIHelpers = {
483
- count: (data: any[]): number => data.length,
484
-
485
- sum: (data: any[], field: string): number =>
486
- data.reduce((sum, item) => sum + (parseFloat(item[field]) || 0), 0),
487
-
488
- average: (data: any[], field: string): number => {
489
- const total = KPIHelpers.sum(data, field);
490
- return data.length > 0 ? total / data.length : 0;
491
- },
492
-
493
- percentage: (part: number, total: number): string =>
494
- total > 0 ? `${Math.round((part / total) * 100)}%` : '0%',
495
-
496
- formatCurrency: (amount: number): string =>
497
- new Intl.NumberFormat('en-US', {
498
- style: 'currency',
499
- currency: 'USD',
500
- }).format(amount),
501
-
502
- formatNumber: (num: number): string =>
503
- new Intl.NumberFormat('en-US').format(num),
504
-
505
- trend: (
506
- current: number,
507
- previous: number
508
- ): { trend: 'up' | 'down' | 'neutral'; value: string } => {
509
- if (previous === 0) return { trend: 'neutral', value: 'N/A' };
510
-
511
- const change = ((current - previous) / previous) * 100;
512
- if (Math.abs(change) < 1) return { trend: 'neutral', value: '0%' };
513
-
514
- return {
515
- trend: change > 0 ? 'up' : 'down',
516
- value: `${Math.abs(Math.round(change))}%`,
517
- };
518
- },
519
- };