@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,625 +0,0 @@
1
- /**
2
- * Example: Creating a custom management component
3
- *
4
- * This example demonstrates how to create a management component
5
- * for a custom entity following the established patterns from
6
- * AccountManagement and ContactManagement.
7
- *
8
- * @example
9
- * ```typescript
10
- * // Usage in your application
11
- * import { OpportunityManagement } from './examples/component-examples/opportunity-management';
12
- *
13
- * function App() {
14
- * return (
15
- * <DynamicsProvider>
16
- * <OpportunityManagement />
17
- * </DynamicsProvider>
18
- * );
19
- * }
20
- * ```
21
- */
22
-
23
- import React, { useState, useEffect, useCallback } from 'react';
24
- import {
25
- Button,
26
- TextField,
27
- DetailsList,
28
- Dialog,
29
- Panel,
30
- Dropdown,
31
- DynamicsColumn,
32
- } from '@khester/dynamics-ui-components';
33
- import {
34
- SelectionMode,
35
- DetailsListLayoutMode,
36
- CheckboxVisibility,
37
- } from '@fluentui/react';
38
- import { useDynamicsApi } from '../../providers/DynamicsProvider';
39
- import { Logger } from '../../components/Logging/logger';
40
- import {
41
- Opportunity,
42
- OpportunityConstants,
43
- SalesStageCode_OptionSet,
44
- } from '../entity-examples/opportunity-model';
45
-
46
- /**
47
- * Opportunity Management Component Props
48
- */
49
- interface OpportunityManagementProps {
50
- /** Optional account ID to filter opportunities */
51
- accountId?: string;
52
- /** Whether to show the create button */
53
- showCreateButton?: boolean;
54
- /** Custom title for the component */
55
- title?: string;
56
- }
57
-
58
- /**
59
- * Column configuration for the opportunities list
60
- * Following the same pattern as AccountManagement and ContactManagement
61
- */
62
- const columns: DynamicsColumn[] = [
63
- {
64
- key: OpportunityConstants.PrimaryName,
65
- name: 'Opportunity Name',
66
- fieldName: OpportunityConstants.PrimaryName,
67
- minWidth: 200,
68
- maxWidth: 300,
69
- isResizable: true,
70
- },
71
- {
72
- key: OpportunityConstants.EstimatedValue,
73
- name: 'Est. Value',
74
- fieldName: OpportunityConstants.EstimatedValue,
75
- minWidth: 120,
76
- maxWidth: 150,
77
- isResizable: true,
78
- onRender: (item: Opportunity) => {
79
- return item.estimatedvalue
80
- ? `$${item.estimatedvalue.toLocaleString()}`
81
- : '';
82
- },
83
- },
84
- {
85
- key: OpportunityConstants.CloseProbability,
86
- name: 'Close %',
87
- fieldName: OpportunityConstants.CloseProbability,
88
- minWidth: 80,
89
- maxWidth: 100,
90
- isResizable: true,
91
- onRender: (item: Opportunity) => {
92
- return item.closeprobability ? `${item.closeprobability}%` : '';
93
- },
94
- },
95
- {
96
- key: OpportunityConstants.EstimatedCloseDate,
97
- name: 'Close Date',
98
- fieldName: OpportunityConstants.EstimatedCloseDate,
99
- minWidth: 120,
100
- maxWidth: 150,
101
- isResizable: true,
102
- onRender: (item: Opportunity) => {
103
- return item.estimatedclosedate
104
- ? new Date(item.estimatedclosedate).toLocaleDateString()
105
- : '';
106
- },
107
- },
108
- {
109
- key: OpportunityConstants.SalesStageCode,
110
- name: 'Stage',
111
- fieldName: OpportunityConstants.SalesStageCode,
112
- minWidth: 100,
113
- maxWidth: 120,
114
- isResizable: true,
115
- onRender: (item: Opportunity) => {
116
- const stageMap: Record<number, string> = {
117
- [SalesStageCode_OptionSet.Qualify]: 'Qualify',
118
- [SalesStageCode_OptionSet.Develop]: 'Develop',
119
- [SalesStageCode_OptionSet.Propose]: 'Propose',
120
- [SalesStageCode_OptionSet.Close]: 'Close',
121
- };
122
- return item.salesstagecode !== undefined
123
- ? stageMap[item.salesstagecode] || 'Unknown'
124
- : '';
125
- },
126
- },
127
- {
128
- key: OpportunityConstants.CreatedOn,
129
- name: 'Created',
130
- fieldName: OpportunityConstants.CreatedOn,
131
- minWidth: 120,
132
- maxWidth: 150,
133
- isResizable: true,
134
- },
135
- ];
136
-
137
- /**
138
- * Sales stage filter options
139
- */
140
- const salesStageOptions = [
141
- { key: 'all', text: 'All Stages' },
142
- { key: SalesStageCode_OptionSet.Qualify, text: 'Qualify' },
143
- { key: SalesStageCode_OptionSet.Develop, text: 'Develop' },
144
- { key: SalesStageCode_OptionSet.Propose, text: 'Propose' },
145
- { key: SalesStageCode_OptionSet.Close, text: 'Close' },
146
- ];
147
-
148
- /**
149
- * OpportunityManagement Component
150
- *
151
- * Provides complete CRUD interface for opportunities with:
152
- * - List view with sorting and filtering
153
- * - Search functionality
154
- * - Create, edit, and delete operations
155
- * - Sales stage filtering
156
- * - Integration with logging system
157
- *
158
- * @param props - Component properties
159
- * @returns JSX.Element
160
- *
161
- * @example
162
- * ```typescript
163
- * // Basic usage
164
- * <OpportunityManagement />
165
- *
166
- * // With account filter
167
- * <OpportunityManagement
168
- * accountId="account-guid-here"
169
- * title="Account Opportunities"
170
- * />
171
- *
172
- * // Read-only view
173
- * <OpportunityManagement
174
- * showCreateButton={false}
175
- * />
176
- * ```
177
- */
178
- export const OpportunityManagement: React.FC<OpportunityManagementProps> = ({
179
- accountId,
180
- showCreateButton = true,
181
- title = 'Opportunity Management',
182
- }) => {
183
- const { apiService, isEnvironmentMock, environmentType } = useDynamicsApi();
184
-
185
- // State management
186
- const [opportunities, setOpportunities] = useState<Opportunity[]>([]);
187
- const [filteredOpportunities, setFilteredOpportunities] = useState<
188
- Opportunity[]
189
- >([]);
190
- const [loading, setLoading] = useState(false);
191
- const [searchText, setSearchText] = useState('');
192
- const [selectedOpportunity, setSelectedOpportunity] =
193
- useState<Opportunity | null>(null);
194
- const [salesStageFilter, setSalesStageFilter] = useState<string>('all');
195
-
196
- // Panel and dialog state
197
- const [showNewOpportunityPanel, setShowNewOpportunityPanel] = useState(false);
198
- const [showEditOpportunityPanel, setShowEditOpportunityPanel] =
199
- useState(false);
200
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
201
- const [opportunityToDelete, setOpportunityToDelete] =
202
- useState<Opportunity | null>(null);
203
-
204
- /**
205
- * Log component initialization
206
- */
207
- useEffect(() => {
208
- Logger.userAction(
209
- 'OpportunityManagement loaded',
210
- { environmentType, isEnvironmentMock, accountId },
211
- 'OpportunityManagement'
212
- );
213
- }, [environmentType, isEnvironmentMock, accountId]);
214
-
215
- /**
216
- * Load opportunities from the API
217
- * Handles both general and account-specific loading
218
- */
219
- const loadOpportunities = useCallback(async () => {
220
- if (!apiService) {
221
- Logger.error(
222
- 'API service not available',
223
- 'OpportunityManagement.loadOpportunities'
224
- );
225
- return;
226
- }
227
-
228
- setLoading(true);
229
- Logger.log(
230
- 'Loading opportunities',
231
- 'OpportunityManagement.loadOpportunities'
232
- );
233
-
234
- try {
235
- let opportunitiesData: Opportunity[];
236
-
237
- if (accountId) {
238
- // Load opportunities for specific account
239
- opportunitiesData = await Opportunity.retrieveByAccount(
240
- apiService,
241
- accountId
242
- );
243
- } else {
244
- // Load all active opportunities
245
- opportunitiesData =
246
- await Opportunity.retrieveActiveOpportunities(apiService);
247
- }
248
-
249
- // Process opportunities for display
250
- const opportunitiesWithDisplayData = opportunitiesData.map(
251
- (opportunity) => {
252
- const opportunityInstance = new Opportunity(opportunity);
253
- return {
254
- ...opportunityInstance,
255
- createdon: opportunity.createdon
256
- ? new Date(opportunity.createdon).toLocaleDateString()
257
- : '',
258
- } as Opportunity;
259
- }
260
- );
261
-
262
- setOpportunities(opportunitiesWithDisplayData);
263
- setFilteredOpportunities(opportunitiesWithDisplayData);
264
-
265
- Logger.log(
266
- `Successfully loaded ${opportunitiesWithDisplayData.length} opportunities`,
267
- 'OpportunityManagement.loadOpportunities'
268
- );
269
- } catch (error) {
270
- Logger.error(
271
- 'Failed to load opportunities',
272
- 'OpportunityManagement.loadOpportunities',
273
- error
274
- );
275
- } finally {
276
- setLoading(false);
277
- }
278
- }, [apiService, accountId]);
279
-
280
- /**
281
- * Initial data load
282
- */
283
- useEffect(() => {
284
- loadOpportunities();
285
- }, [loadOpportunities]);
286
-
287
- /**
288
- * Filter opportunities based on search text and sales stage
289
- */
290
- useEffect(() => {
291
- let filtered = opportunities;
292
-
293
- // Apply search filter
294
- if (searchText) {
295
- filtered = filtered.filter(
296
- (opportunity) =>
297
- opportunity.name?.toLowerCase().includes(searchText.toLowerCase()) ||
298
- opportunity.description
299
- ?.toLowerCase()
300
- .includes(searchText.toLowerCase())
301
- );
302
- }
303
-
304
- // Apply sales stage filter
305
- if (salesStageFilter !== 'all') {
306
- const stageValue = parseInt(salesStageFilter);
307
- filtered = filtered.filter(
308
- (opportunity) => opportunity.salesstagecode === stageValue
309
- );
310
- }
311
-
312
- setFilteredOpportunities(filtered);
313
- }, [opportunities, searchText, salesStageFilter]);
314
-
315
- /**
316
- * Handle opportunity item selection/invocation
317
- */
318
- const handleItemInvoked = useCallback(
319
- (item: Opportunity) => {
320
- Logger.userAction(
321
- 'Opportunity item invoked',
322
- { opportunityId: item.opportunityid, opportunityName: item.name },
323
- 'OpportunityManagement.handleItemInvoked'
324
- );
325
-
326
- if (selectedOpportunity?.opportunityid === item.opportunityid) {
327
- setShowEditOpportunityPanel(true);
328
- } else {
329
- setSelectedOpportunity(item);
330
- }
331
- },
332
- [selectedOpportunity]
333
- );
334
-
335
- /**
336
- * Handle new opportunity creation
337
- */
338
- const handleNewOpportunity = useCallback(() => {
339
- Logger.userAction(
340
- 'New opportunity panel opened',
341
- {},
342
- 'OpportunityManagement.handleNewOpportunity'
343
- );
344
- setShowNewOpportunityPanel(true);
345
- }, []);
346
-
347
- /**
348
- * Handle opportunity editing
349
- */
350
- const handleEditOpportunity = useCallback(() => {
351
- if (selectedOpportunity) {
352
- Logger.userAction(
353
- 'Edit opportunity panel opened',
354
- {
355
- opportunityId: selectedOpportunity.opportunityid,
356
- opportunityName: selectedOpportunity.name,
357
- },
358
- 'OpportunityManagement.handleEditOpportunity'
359
- );
360
- setShowEditOpportunityPanel(true);
361
- }
362
- }, [selectedOpportunity]);
363
-
364
- /**
365
- * Handle opportunity deletion
366
- */
367
- const handleDeleteOpportunity = useCallback(() => {
368
- if (selectedOpportunity) {
369
- Logger.userAction(
370
- 'Delete opportunity dialog opened',
371
- {
372
- opportunityId: selectedOpportunity.opportunityid,
373
- opportunityName: selectedOpportunity.name,
374
- },
375
- 'OpportunityManagement.handleDeleteOpportunity'
376
- );
377
- setOpportunityToDelete(selectedOpportunity);
378
- setShowDeleteDialog(true);
379
- }
380
- }, [selectedOpportunity]);
381
-
382
- /**
383
- * Confirm opportunity deletion
384
- */
385
- const confirmDelete = useCallback(async () => {
386
- if (opportunityToDelete && apiService) {
387
- try {
388
- Logger.userAction(
389
- 'Opportunity deletion confirmed',
390
- {
391
- opportunityId: opportunityToDelete.opportunityid,
392
- opportunityName: opportunityToDelete.name,
393
- },
394
- 'OpportunityManagement.confirmDelete'
395
- );
396
-
397
- await Opportunity.delete(
398
- apiService,
399
- opportunityToDelete.opportunityid!
400
- );
401
- await loadOpportunities();
402
-
403
- setShowDeleteDialog(false);
404
- setOpportunityToDelete(null);
405
- setSelectedOpportunity(null);
406
-
407
- Logger.log(
408
- `Successfully deleted opportunity: ${opportunityToDelete.name}`,
409
- 'OpportunityManagement.confirmDelete'
410
- );
411
- } catch (error) {
412
- Logger.error(
413
- `Failed to delete opportunity: ${opportunityToDelete.name}`,
414
- 'OpportunityManagement.confirmDelete',
415
- error
416
- );
417
- }
418
- }
419
- }, [opportunityToDelete, apiService, loadOpportunities]);
420
-
421
- /**
422
- * Handle opportunity save (create or update)
423
- */
424
- const handleOpportunitySaved = useCallback(() => {
425
- Logger.userAction(
426
- 'Opportunity saved, refreshing list',
427
- {},
428
- 'OpportunityManagement.handleOpportunitySaved'
429
- );
430
- loadOpportunities();
431
- setShowNewOpportunityPanel(false);
432
- setShowEditOpportunityPanel(false);
433
- setSelectedOpportunity(null);
434
- }, [loadOpportunities]);
435
-
436
- /**
437
- * Handle sales stage filter change
438
- */
439
- const handleSalesStageFilterChange = useCallback(
440
- (
441
- _event: React.FormEvent<HTMLDivElement>,
442
- option?: { key: string | number | undefined; text: string }
443
- ) => {
444
- if (option) {
445
- const newStage = option.key?.toString() || 'all';
446
- Logger.userAction(
447
- 'Sales stage filter changed',
448
- { from: salesStageFilter, to: newStage },
449
- 'OpportunityManagement.handleSalesStageFilterChange'
450
- );
451
- setSalesStageFilter(newStage);
452
- }
453
- },
454
- [salesStageFilter]
455
- );
456
-
457
- return (
458
- <div className="opportunity-management">
459
- <div className="opportunity-management__header">
460
- <h2>{title}</h2>
461
- <div className="opportunity-management__actions">
462
- <TextField
463
- placeholder="Search opportunities..."
464
- value={searchText}
465
- onChange={(_, value) => setSearchText(value || '')}
466
- iconProps={{ iconName: 'Search' }}
467
- />
468
- <Dropdown
469
- placeholder="Filter by stage"
470
- options={salesStageOptions}
471
- selectedKey={salesStageFilter}
472
- onChange={handleSalesStageFilterChange}
473
- />
474
- {showCreateButton && (
475
- <Button
476
- text="New Opportunity"
477
- variant="primary"
478
- onClick={handleNewOpportunity}
479
- iconProps={{ iconName: 'Add' }}
480
- />
481
- )}
482
- </div>
483
- </div>
484
-
485
- <div className="opportunity-management__toolbar">
486
- <Button
487
- text="Edit"
488
- onClick={handleEditOpportunity}
489
- disabled={!selectedOpportunity}
490
- iconProps={{ iconName: 'Edit' }}
491
- />
492
- <Button
493
- text="Delete"
494
- onClick={handleDeleteOpportunity}
495
- disabled={!selectedOpportunity}
496
- iconProps={{ iconName: 'Delete' }}
497
- />
498
- <Button
499
- text="Refresh"
500
- onClick={loadOpportunities}
501
- iconProps={{ iconName: 'Refresh' }}
502
- />
503
- </div>
504
-
505
- <div className="opportunity-management__list">
506
- <DetailsList
507
- items={filteredOpportunities}
508
- columns={columns}
509
- onItemInvoked={handleItemInvoked}
510
- selectionMode={SelectionMode.single}
511
- layoutMode={DetailsListLayoutMode.justified}
512
- checkboxVisibility={CheckboxVisibility.onHover}
513
- />
514
-
515
- {loading && (
516
- <div className="opportunity-management__loading">
517
- Loading opportunities...
518
- </div>
519
- )}
520
-
521
- {!loading && filteredOpportunities.length === 0 && (
522
- <div className="opportunity-management__empty">
523
- {searchText || salesStageFilter !== 'all'
524
- ? 'No opportunities found matching your filters.'
525
- : accountId
526
- ? 'No opportunities found for this account. Create your first opportunity!'
527
- : 'No opportunities found. Create your first opportunity!'}
528
- </div>
529
- )}
530
- </div>
531
-
532
- {/* Delete Confirmation Dialog */}
533
- <Dialog
534
- hidden={!showDeleteDialog}
535
- onDismiss={() => setShowDeleteDialog(false)}
536
- title="Confirm Delete"
537
- content={`Are you sure you want to delete "${opportunityToDelete?.name}"? This action cannot be undone.`}
538
- actions={[
539
- { text: 'Delete', onClick: confirmDelete, primary: true },
540
- { text: 'Cancel', onClick: () => setShowDeleteDialog(false) },
541
- ]}
542
- />
543
-
544
- {/* Note: OpportunityForm would need to be created following the AccountForm/ContactForm pattern */}
545
- {/* This example focuses on the management component structure */}
546
- </div>
547
- );
548
- };
549
-
550
- /**
551
- * CSS classes for styling (would be in a separate .css file)
552
- */
553
- export const opportunityManagementStyles = `
554
- .opportunity-management {
555
- padding: 16px;
556
- }
557
-
558
- .opportunity-management__header {
559
- display: flex;
560
- justify-content: space-between;
561
- align-items: center;
562
- margin-bottom: 16px;
563
- flex-wrap: wrap;
564
- gap: 16px;
565
- }
566
-
567
- .opportunity-management__header h2 {
568
- margin: 0;
569
- font-size: 20px;
570
- font-weight: 600;
571
- color: #323130;
572
- }
573
-
574
- .opportunity-management__actions {
575
- display: flex;
576
- gap: 12px;
577
- align-items: center;
578
- flex-wrap: wrap;
579
- }
580
-
581
- .opportunity-management__toolbar {
582
- display: flex;
583
- gap: 8px;
584
- margin-bottom: 16px;
585
- padding: 8px 0;
586
- border-bottom: 1px solid #edebe9;
587
- }
588
-
589
- .opportunity-management__list {
590
- min-height: 400px;
591
- border: 1px solid #edebe9;
592
- border-radius: 4px;
593
- }
594
-
595
- .opportunity-management__loading,
596
- .opportunity-management__empty {
597
- display: flex;
598
- justify-content: center;
599
- align-items: center;
600
- height: 200px;
601
- color: #605e5c;
602
- font-style: italic;
603
- text-align: center;
604
- padding: 16px;
605
- }
606
-
607
- @media (max-width: 768px) {
608
- .opportunity-management {
609
- padding: 8px;
610
- }
611
-
612
- .opportunity-management__header {
613
- flex-direction: column;
614
- align-items: stretch;
615
- }
616
-
617
- .opportunity-management__actions {
618
- justify-content: stretch;
619
- }
620
-
621
- .opportunity-management__toolbar {
622
- flex-wrap: wrap;
623
- }
624
- }
625
- `;