@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,629 +0,0 @@
1
- import React, { useState, useCallback } from 'react';
2
- import {
3
- Stack,
4
- Text,
5
- SearchBox,
6
- Pivot,
7
- PivotItem,
8
- DetailsList,
9
- IColumn,
10
- SelectionMode,
11
- MessageBar,
12
- MessageBarType,
13
- Spinner,
14
- SpinnerSize,
15
- CommandBar,
16
- ICommandBarItemProps,
17
- Dropdown,
18
- IDropdownOption,
19
- Checkbox,
20
- DefaultButton,
21
- Facepile,
22
- IFacepilePersona,
23
- } from '@fluentui/react';
24
- import { BaseEntity } from '../models/BaseEntity';
25
- import { useDynamicsApi } from '../providers/DynamicsProvider';
26
- import { Logger } from '../components/Logging/logger';
27
-
28
- interface EntitySearchConfig<T extends BaseEntity> {
29
- key: string;
30
- title: string;
31
- entityLogicalName: string;
32
- entityClass: any;
33
- columns: IColumn[];
34
- searchFields: string[];
35
- fetchXmlTemplate: string; // Should include {{searchQuery}} placeholder
36
- icon?: string;
37
- color?: string;
38
- }
39
-
40
- interface SearchFilter {
41
- key: string;
42
- title: string;
43
- type: 'dropdown' | 'checkbox' | 'date';
44
- options?: IDropdownOption[];
45
- value?: any;
46
- }
47
-
48
- interface SearchPageProps {
49
- title?: string;
50
- entityConfigs: EntitySearchConfig<any>[];
51
- globalSearchFields?: string[];
52
- filters?: SearchFilter[];
53
- onItemSelected?: (item: any, entityType: string) => void;
54
- onCreateNew?: (entityType: string) => void;
55
- enableAdvancedSearch?: boolean;
56
- enableSavedSearches?: boolean;
57
- placeholder?: string;
58
- minSearchLength?: number;
59
- }
60
-
61
- /**
62
- * Generic search page template for Dynamics 365
63
- * Supports cross-entity search with advanced filtering and saved searches
64
- */
65
- export function SearchPage({
66
- title = 'Search',
67
- entityConfigs,
68
- globalSearchFields = [],
69
- filters = [],
70
- onItemSelected,
71
- onCreateNew,
72
- enableAdvancedSearch = true,
73
- enableSavedSearches = false,
74
- placeholder = 'Search across all entities...',
75
- minSearchLength = 3,
76
- }: SearchPageProps) {
77
- const { apiService } = useDynamicsApi();
78
- const [searchQuery, setSearchQuery] = useState('');
79
- const [searchResults, setSearchResults] = useState<{ [key: string]: any[] }>(
80
- {}
81
- );
82
- const [loading, setLoading] = useState(false);
83
- const [error, setError] = useState<string | null>(null);
84
- const [selectedTab, setSelectedTab] = useState('all');
85
- const [activeFilters, setActiveFilters] = useState<{ [key: string]: any }>(
86
- {}
87
- );
88
- const [showAdvancedSearch, setShowAdvancedSearch] = useState(false);
89
- const [searchHistory, setSearchHistory] = useState<string[]>([]);
90
- const [totalResults, setTotalResults] = useState(0);
91
-
92
- // Perform search across all configured entities
93
- const performSearch = useCallback(
94
- async (query: string) => {
95
- if (!query || query.length < minSearchLength) {
96
- setSearchResults({});
97
- setTotalResults(0);
98
- return;
99
- }
100
-
101
- if (!apiService) {
102
- setError('API service not available');
103
- return;
104
- }
105
-
106
- try {
107
- setLoading(true);
108
- setError(null);
109
- Logger.log(`Searching for: "${query}"`);
110
-
111
- const results: { [key: string]: any[] } = {};
112
- let total = 0;
113
-
114
- // Search each entity type
115
- for (const config of entityConfigs) {
116
- try {
117
- // Build search conditions
118
- const searchConditions = config.searchFields
119
- .map(
120
- (field) =>
121
- `<condition attribute="${field}" operator="like" value="%${query}%" />`
122
- )
123
- .join('\n ');
124
-
125
- // Apply additional filters
126
- const filterConditions = Object.entries(activeFilters)
127
- .filter(
128
- ([_, value]) =>
129
- value !== null && value !== undefined && value !== ''
130
- )
131
- .map(([key, value]) => {
132
- const filter = filters.find((f) => f.key === key);
133
- if (filter?.type === 'checkbox') {
134
- return value
135
- ? `<condition attribute="${key}" operator="eq" value="true" />`
136
- : '';
137
- } else if (filter?.type === 'dropdown') {
138
- return `<condition attribute="${key}" operator="eq" value="${value}" />`;
139
- }
140
- return `<condition attribute="${key}" operator="like" value="%${value}%" />`;
141
- })
142
- .filter((condition) => condition)
143
- .join('\n ');
144
-
145
- const allConditions = [searchConditions, filterConditions]
146
- .filter((c) => c)
147
- .join('\n ');
148
-
149
- const fetchXml = config.fetchXmlTemplate
150
- .replace('{{searchQuery}}', query)
151
- .replace('{{searchConditions}}', allConditions);
152
-
153
- const result = await apiService.retrieveMultipleRecords(
154
- config.entityLogicalName,
155
- fetchXml
156
- );
157
- const entities = (result.entities || []).map(
158
- (data: any) => new config.entityClass(data)
159
- );
160
-
161
- results[config.key] = entities;
162
- total += entities.length;
163
-
164
- Logger.log(`Found ${entities.length} ${config.title} results`);
165
- } catch (err) {
166
- Logger.warn(
167
- `Search failed for ${config.title}:`,
168
- err instanceof Error ? err.message : String(err)
169
- );
170
- results[config.key] = [];
171
- }
172
- }
173
-
174
- setSearchResults(results);
175
- setTotalResults(total);
176
-
177
- // Update search history
178
- if (!searchHistory.includes(query)) {
179
- setSearchHistory((prev) => [query, ...prev.slice(0, 9)]); // Keep last 10 searches
180
- }
181
-
182
- Logger.log(`Search completed. Total results: ${total}`);
183
- } catch (err) {
184
- const errorMessage =
185
- err instanceof Error ? err.message : 'Search failed';
186
- setError(errorMessage);
187
- Logger.error('Search error:', 'SearchPage', err);
188
- } finally {
189
- setLoading(false);
190
- }
191
- },
192
- [
193
- apiService,
194
- entityConfigs,
195
- activeFilters,
196
- filters,
197
- minSearchLength,
198
- searchHistory,
199
- ]
200
- );
201
-
202
- // Handle search input
203
- const handleSearch = (query: string) => {
204
- setSearchQuery(query);
205
- if (query.length >= minSearchLength) {
206
- performSearch(query);
207
- } else {
208
- setSearchResults({});
209
- setTotalResults(0);
210
- }
211
- };
212
-
213
- // Handle filter change
214
- const handleFilterChange = (filterKey: string, value: any) => {
215
- setActiveFilters((prev) => ({
216
- ...prev,
217
- [filterKey]: value,
218
- }));
219
-
220
- // Re-search with new filters
221
- if (searchQuery.length >= minSearchLength) {
222
- performSearch(searchQuery);
223
- }
224
- };
225
-
226
- // Clear all filters
227
- const clearFilters = () => {
228
- setActiveFilters({});
229
- if (searchQuery.length >= minSearchLength) {
230
- performSearch(searchQuery);
231
- }
232
- };
233
-
234
- // Handle item selection
235
- const handleItemClick = (
236
- item: any,
237
- entityConfig: EntitySearchConfig<any>
238
- ) => {
239
- if (onItemSelected) {
240
- onItemSelected(item, entityConfig.key);
241
- }
242
- };
243
-
244
- // Get all results for "All" tab
245
- const getAllResults = () => {
246
- const allResults: any[] = [];
247
- entityConfigs.forEach((config) => {
248
- const configResults = searchResults[config.key] || [];
249
- configResults.forEach((item) => {
250
- allResults.push({
251
- ...item,
252
- _entityType: config.key,
253
- _entityTitle: config.title,
254
- _entityIcon: config.icon,
255
- _entityColor: config.color,
256
- });
257
- });
258
- });
259
- return allResults;
260
- };
261
-
262
- // Create columns for "All" tab
263
- const createAllResultsColumns = (): IColumn[] => [
264
- {
265
- key: 'name',
266
- name: 'Name',
267
- minWidth: 200,
268
- maxWidth: 300,
269
- isResizable: true,
270
- onRender: (item: any) => {
271
- const config = entityConfigs.find((c) => c.key === item._entityType);
272
- const nameField = config?.columns[0]?.fieldName || 'name';
273
- return (
274
- <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 8 }}>
275
- {item._entityIcon && (
276
- <span
277
- style={{ fontSize: 16, color: item._entityColor || '#605e5c' }}
278
- >
279
- {item._entityIcon}
280
- </span>
281
- )}
282
- <span
283
- style={{ color: '#0078d4', cursor: 'pointer', fontWeight: 500 }}
284
- onClick={() => config && handleItemClick(item, config)}
285
- >
286
- {item[nameField] || 'N/A'}
287
- </span>
288
- </Stack>
289
- );
290
- },
291
- },
292
- {
293
- key: 'entityType',
294
- name: 'Type',
295
- minWidth: 100,
296
- maxWidth: 150,
297
- isResizable: true,
298
- onRender: (item: any) => (
299
- <span
300
- style={{
301
- padding: '4px 8px',
302
- borderRadius: 4,
303
- backgroundColor: item._entityColor
304
- ? `${item._entityColor}20`
305
- : '#f3f2f1',
306
- color: item._entityColor || '#605e5c',
307
- fontSize: 12,
308
- fontWeight: 600,
309
- }}
310
- >
311
- {item._entityTitle}
312
- </span>
313
- ),
314
- },
315
- ];
316
-
317
- // Enhanced columns with click handlers
318
- const createEnhancedColumns = (
319
- config: EntitySearchConfig<any>
320
- ): IColumn[] => {
321
- return config.columns.map((column) => ({
322
- ...column,
323
- onRender:
324
- column.onRender ||
325
- ((item: any) => {
326
- const value = item[column.fieldName || ''];
327
- if (column.key === config.columns[0].key) {
328
- // Make first column clickable
329
- return (
330
- <span
331
- style={{ color: '#0078d4', cursor: 'pointer', fontWeight: 500 }}
332
- onClick={() => handleItemClick(item, config)}
333
- >
334
- {value || ''}
335
- </span>
336
- );
337
- }
338
- return <span>{value || ''}</span>;
339
- }),
340
- }));
341
- };
342
-
343
- // Command bar items
344
- const commandBarItems: ICommandBarItemProps[] = [
345
- ...(enableAdvancedSearch
346
- ? [
347
- {
348
- key: 'advanced',
349
- text: showAdvancedSearch ? 'Hide Filters' : 'Show Filters',
350
- iconProps: { iconName: 'Filter' },
351
- onClick: () => setShowAdvancedSearch(!showAdvancedSearch),
352
- },
353
- ]
354
- : []),
355
- ...(Object.keys(activeFilters).length > 0
356
- ? [
357
- {
358
- key: 'clear',
359
- text: 'Clear Filters',
360
- iconProps: { iconName: 'ClearFilter' },
361
- onClick: clearFilters,
362
- },
363
- ]
364
- : []),
365
- ...(onCreateNew
366
- ? [
367
- {
368
- key: 'create',
369
- text: 'Create New',
370
- iconProps: { iconName: 'Add' },
371
- subMenuProps: {
372
- items: entityConfigs.map((config) => ({
373
- key: config.key,
374
- text: config.title,
375
- iconProps: { iconName: 'Add' },
376
- onClick: () => onCreateNew(config.key),
377
- })),
378
- },
379
- },
380
- ]
381
- : []),
382
- ];
383
-
384
- // Get search suggestions
385
- const getSearchSuggestions = (): IFacepilePersona[] => {
386
- return searchHistory.slice(0, 5).map((query, index) => ({
387
- personaName: query,
388
- onClick: () => handleSearch(query),
389
- }));
390
- };
391
-
392
- return (
393
- <div style={{ padding: '20px', maxWidth: '100%' }}>
394
- <Stack tokens={{ childrenGap: 20 }}>
395
- {/* Header */}
396
- <Stack>
397
- <Text variant="xxLarge" style={{ fontWeight: 600, color: '#323130' }}>
398
- {title}
399
- </Text>
400
- <Text variant="medium" style={{ color: '#605e5c' }}>
401
- Search across multiple entity types
402
- </Text>
403
- </Stack>
404
-
405
- {/* Search Box */}
406
- <Stack tokens={{ childrenGap: 12 }}>
407
- <SearchBox
408
- placeholder={placeholder}
409
- value={searchQuery}
410
- onChange={(_, newValue) => handleSearch(newValue || '')}
411
- onSearch={(newValue) => handleSearch(newValue)}
412
- style={{ maxWidth: 600 }}
413
- />
414
-
415
- {/* Search History */}
416
- {searchHistory.length > 0 && (
417
- <Stack>
418
- <Text
419
- variant="small"
420
- style={{ color: '#605e5c', marginBottom: 8 }}
421
- >
422
- Recent searches:
423
- </Text>
424
- <Facepile
425
- personas={getSearchSuggestions()}
426
- maxDisplayablePersonas={5}
427
- styles={{
428
- member: { cursor: 'pointer' },
429
- }}
430
- />
431
- </Stack>
432
- )}
433
- </Stack>
434
-
435
- {/* Advanced Search Filters */}
436
- {enableAdvancedSearch && showAdvancedSearch && filters.length > 0 && (
437
- <Stack
438
- style={{
439
- padding: 16,
440
- border: '1px solid #edebe9',
441
- borderRadius: 4,
442
- backgroundColor: '#faf9f8',
443
- }}
444
- tokens={{ childrenGap: 16 }}
445
- >
446
- <Text variant="medium" style={{ fontWeight: 600 }}>
447
- Advanced Filters
448
- </Text>
449
- <Stack horizontal wrap tokens={{ childrenGap: 16 }}>
450
- {filters.map((filter) => (
451
- <Stack key={filter.key} style={{ minWidth: 200 }}>
452
- {filter.type === 'dropdown' && (
453
- <Dropdown
454
- label={filter.title}
455
- options={filter.options || []}
456
- selectedKey={activeFilters[filter.key]}
457
- onChange={(_, option) =>
458
- handleFilterChange(filter.key, option?.key)
459
- }
460
- />
461
- )}
462
- {filter.type === 'checkbox' && (
463
- <Checkbox
464
- label={filter.title}
465
- checked={activeFilters[filter.key] || false}
466
- onChange={(_, checked) =>
467
- handleFilterChange(filter.key, checked)
468
- }
469
- />
470
- )}
471
- </Stack>
472
- ))}
473
- <DefaultButton
474
- text="Clear Filters"
475
- onClick={clearFilters}
476
- disabled={Object.keys(activeFilters).length === 0}
477
- style={{ alignSelf: 'end' }}
478
- />
479
- </Stack>
480
- </Stack>
481
- )}
482
-
483
- {/* Error Message */}
484
- {error && (
485
- <MessageBar
486
- messageBarType={MessageBarType.error}
487
- onDismiss={() => setError(null)}
488
- >
489
- {error}
490
- </MessageBar>
491
- )}
492
-
493
- {/* Command Bar */}
494
- <CommandBar items={commandBarItems} />
495
-
496
- {/* Results */}
497
- {searchQuery.length > 0 && searchQuery.length < minSearchLength && (
498
- <MessageBar messageBarType={MessageBarType.info}>
499
- Please enter at least {minSearchLength} characters to search
500
- </MessageBar>
501
- )}
502
-
503
- {loading ? (
504
- <div
505
- style={{
506
- display: 'flex',
507
- justifyContent: 'center',
508
- alignItems: 'center',
509
- minHeight: 200,
510
- flexDirection: 'column',
511
- }}
512
- >
513
- <Spinner size={SpinnerSize.large} label="Searching..." />
514
- </div>
515
- ) : totalResults > 0 ? (
516
- <Stack>
517
- <Text
518
- variant="medium"
519
- style={{ color: '#605e5c', marginBottom: 16 }}
520
- >
521
- Found {totalResults} results for "{searchQuery}"
522
- </Text>
523
-
524
- <Pivot
525
- selectedKey={selectedTab}
526
- onLinkClick={(item) =>
527
- setSelectedTab(item?.props.itemKey || 'all')
528
- }
529
- >
530
- {/* All Results Tab */}
531
- <PivotItem headerText={`All (${totalResults})`} itemKey="all">
532
- <div style={{ paddingTop: 16 }}>
533
- <DetailsList
534
- items={getAllResults()}
535
- columns={createAllResultsColumns()}
536
- setKey="all"
537
- layoutMode={0}
538
- selectionMode={SelectionMode.none}
539
- isHeaderVisible={true}
540
- />
541
- </div>
542
- </PivotItem>
543
-
544
- {/* Individual Entity Tabs */}
545
- {entityConfigs.map((config) => {
546
- const results = searchResults[config.key] || [];
547
- return (
548
- <PivotItem
549
- key={config.key}
550
- headerText={`${config.title} (${results.length})`}
551
- itemKey={config.key}
552
- >
553
- <div style={{ paddingTop: 16 }}>
554
- {results.length === 0 ? (
555
- <div
556
- style={{
557
- textAlign: 'center',
558
- padding: '40px 20px',
559
- color: '#605e5c',
560
- }}
561
- >
562
- <Text variant="medium">No {config.title} found</Text>
563
- </div>
564
- ) : (
565
- <DetailsList
566
- items={results}
567
- columns={createEnhancedColumns(config)}
568
- setKey={config.key}
569
- layoutMode={0}
570
- selectionMode={SelectionMode.none}
571
- isHeaderVisible={true}
572
- />
573
- )}
574
- </div>
575
- </PivotItem>
576
- );
577
- })}
578
- </Pivot>
579
- </Stack>
580
- ) : searchQuery.length >= minSearchLength ? (
581
- <div
582
- style={{
583
- textAlign: 'center',
584
- padding: '40px 20px',
585
- color: '#605e5c',
586
- }}
587
- >
588
- <div style={{ fontSize: 48, marginBottom: 16, color: '#a19f9d' }}>
589
- 🔍
590
- </div>
591
- <Text
592
- variant="large"
593
- style={{ fontWeight: 600, marginBottom: 8, color: '#323130' }}
594
- >
595
- No results found
596
- </Text>
597
- <Text variant="medium">
598
- Try adjusting your search terms or filters
599
- </Text>
600
- </div>
601
- ) : null}
602
- </Stack>
603
- </div>
604
- );
605
- }
606
-
607
- // Helper function to create search FetchXML template
608
- export function createSearchFetchXml(
609
- entityName: string,
610
- attributes: string[],
611
- searchFields: string[],
612
- orderBy?: string
613
- ): string {
614
- const attributeElements = attributes
615
- .map((attr) => `<attribute name="${attr}" />`)
616
- .join('\n ');
617
- const orderElement = orderBy ? `<order attribute="${orderBy}" />` : '';
618
-
619
- return `
620
- <fetch top="50">
621
- <entity name="${entityName}">
622
- ${attributeElements}
623
- ${orderElement}
624
- <filter type="or">
625
- {{searchConditions}}
626
- </filter>
627
- </entity>
628
- </fetch>`;
629
- }