neo-cmp-cli 1.13.16 → 1.13.18

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 (182) hide show
  1. package/README.md +2 -1
  2. package/dist/index2.js +1 -1
  3. package/dist/main2.js +1 -1
  4. package/dist/neo/env.js +1 -1
  5. package/dist/neo/neoLogin.js +1 -1
  6. package/dist/neo/pushCmp.js +1 -1
  7. package/dist/package.json.js +1 -1
  8. package/package.json +3 -2
  9. package/template/antd-custom-cmp-template/package.json +1 -1
  10. package/template/asset-manage-template/docs/README.md +1 -232
  11. package/template/asset-manage-template/package.json +2 -2
  12. package/template/echarts-custom-cmp-template/package.json +1 -1
  13. package/template/empty-custom-cmp-template/package.json +2 -2
  14. package/template/map-custom-cmp-template/package.json +1 -1
  15. package/template/neo-bi-cmps/neo.config.js +7 -1
  16. package/template/neo-bi-cmps/package.json +8 -7
  17. package/template/neo-bi-cmps/public/403.html +77 -0
  18. package/template/neo-bi-cmps/public/demo.html +2453 -0
  19. package/template/neo-bi-cmps/src/assets/icon/barChart.svg +1 -0
  20. package/template/neo-bi-cmps/src/assets/icon/card.svg +1 -0
  21. package/template/neo-bi-cmps/src/assets/icon/filter.svg +1 -0
  22. package/template/neo-bi-cmps/src/assets/icon/funnel.svg +1 -0
  23. package/template/neo-bi-cmps/src/assets/icon/tab.svg +1 -0
  24. package/template/neo-bi-cmps/src/components/filterBar__c/README.md +3 -14
  25. package/template/neo-bi-cmps/src/components/filterBar__c/common.scss +29 -0
  26. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +668 -146
  27. package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +26 -48
  28. package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +46 -139
  29. package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +11 -10
  30. package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +9 -16
  31. package/template/neo-bi-cmps/src/components/targetNumber__c/model.ts +1 -1
  32. package/template/neo-bi-cmps/src/utils/common.ts +231 -0
  33. package/template/neo-bi-cmps/src/utils/filter2chartFilter.ts +268 -0
  34. package/template/neo-bi-cmps/src/utils/filterBar.ts +140 -0
  35. package/template/neo-bi-cmps/src/utils/pipelineFunnel.ts +341 -0
  36. package/template/{neo-h5-cmps/src/utils/queryObjectData.ts → neo-bi-cmps/src/utils/queryByCustomSQL.ts} +18 -13
  37. package/template/neo-bi-cmps/src/utils/requestDebounce.ts +22 -0
  38. package/template/neo-bi-cmps/src/utils/simpleTable.tsx +344 -0
  39. package/template/neo-bi-cmps/src/utils/stageSwitch.ts +15 -0
  40. package/template/neo-bi-cmps/src/utils/stageTimeChart.ts +90 -0
  41. package/template/neo-bi-cmps/src/utils/targetNumber.ts +12 -0
  42. package/template/neo-custom-cmp-template/docs/README.md +0 -231
  43. package/template/neo-custom-cmp-template/package.json +2 -2
  44. package/template/neo-h5-cmps/package.json +2 -2
  45. package/template/neo-h5-cmps/src/components/entityList__c/index.tsx +1 -2
  46. package/template/neo-h5-cmps/src/components/entityTabs__c/index.tsx +1 -1
  47. package/template/neo-h5-cmps/src/components/globalSearchInput__c/index.tsx +1 -1
  48. package/template/neo-h5-cmps/src/components/openChatPageBtn__c/index.tsx +1 -2
  49. package/template/neo-order-cmps/package.json +2 -2
  50. package/template/neo-pipeline-cmps/.prettierrc.js +12 -0
  51. package/template/neo-pipeline-cmps/@types/neo-ui-common.d.ts +36 -0
  52. package/template/neo-pipeline-cmps/README.md +99 -0
  53. package/template/neo-pipeline-cmps/commitlint.config.js +59 -0
  54. package/template/neo-pipeline-cmps/neo.config.js +135 -0
  55. package/template/neo-pipeline-cmps/package.json +66 -0
  56. package/template/neo-pipeline-cmps/public/403.html +77 -0
  57. package/template/neo-pipeline-cmps/public/css/base.css +283 -0
  58. package/template/neo-pipeline-cmps/public/demo.html +2453 -0
  59. package/template/neo-pipeline-cmps/public/scripts/app/bluebird.js +6679 -0
  60. package/template/neo-pipeline-cmps/public/template.html +13 -0
  61. package/template/neo-pipeline-cmps/src/assets/css/common.scss +127 -0
  62. package/template/neo-pipeline-cmps/src/assets/css/mixin.scss +47 -0
  63. package/template/neo-pipeline-cmps/src/assets/icon/barChart.svg +1 -0
  64. package/template/neo-pipeline-cmps/src/assets/icon/card.svg +1 -0
  65. package/template/neo-pipeline-cmps/src/assets/icon/filter.svg +1 -0
  66. package/template/neo-pipeline-cmps/src/assets/icon/funnel.svg +1 -0
  67. package/template/neo-pipeline-cmps/src/assets/icon/tab.svg +1 -0
  68. package/template/neo-pipeline-cmps/src/assets/img/AIBtn.gif +0 -0
  69. package/template/neo-pipeline-cmps/src/assets/img/NeoCRM.jpg +0 -0
  70. package/template/neo-pipeline-cmps/src/assets/img/aiLogo.png +0 -0
  71. package/template/neo-pipeline-cmps/src/assets/img/card-list.svg +1 -0
  72. package/template/neo-pipeline-cmps/src/assets/img/contact-form.svg +1 -0
  73. package/template/neo-pipeline-cmps/src/assets/img/custom-form.svg +1 -0
  74. package/template/neo-pipeline-cmps/src/assets/img/custom-widget.svg +1 -0
  75. package/template/neo-pipeline-cmps/src/assets/img/data-list.svg +1 -0
  76. package/template/neo-pipeline-cmps/src/assets/img/detail.svg +1 -0
  77. package/template/neo-pipeline-cmps/src/assets/img/favicon.png +0 -0
  78. package/template/neo-pipeline-cmps/src/assets/img/map.svg +1 -0
  79. package/template/neo-pipeline-cmps/src/assets/img/search.svg +1 -0
  80. package/template/neo-pipeline-cmps/src/assets/img/table.svg +1 -0
  81. package/template/neo-pipeline-cmps/src/components/filterBar__c/README.md +24 -0
  82. package/template/neo-pipeline-cmps/src/components/filterBar__c/common.scss +29 -0
  83. package/template/neo-pipeline-cmps/src/components/filterBar__c/index.tsx +731 -0
  84. package/template/neo-pipeline-cmps/src/components/filterBar__c/model.ts +52 -0
  85. package/template/neo-pipeline-cmps/src/components/filterBar__c/style.scss +119 -0
  86. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/README.md +39 -0
  87. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/index.tsx +416 -0
  88. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/model.ts +80 -0
  89. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/style.scss +83 -0
  90. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/index.tsx +470 -0
  91. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/model.ts +45 -0
  92. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/style.scss +137 -0
  93. package/template/neo-pipeline-cmps/src/components/simpleTable__c/README.md +89 -0
  94. package/template/neo-pipeline-cmps/src/components/simpleTable__c/common.scss +195 -0
  95. package/template/neo-pipeline-cmps/src/components/simpleTable__c/index.tsx +667 -0
  96. package/template/neo-pipeline-cmps/src/components/simpleTable__c/model.ts +124 -0
  97. package/template/neo-pipeline-cmps/src/components/simpleTable__c/style.scss +192 -0
  98. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/README.md +36 -0
  99. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/index.tsx +513 -0
  100. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/model.ts +71 -0
  101. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/style.scss +4 -2
  102. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/README.md +37 -0
  103. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/index.tsx +455 -0
  104. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/model.ts +106 -0
  105. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss +3 -2
  106. package/template/neo-pipeline-cmps/src/utils/common.ts +229 -0
  107. package/template/neo-pipeline-cmps/src/utils/filter2chartFilter.ts +266 -0
  108. package/template/neo-pipeline-cmps/src/utils/filterBar.ts +140 -0
  109. package/template/neo-pipeline-cmps/src/utils/pipelineFunnel.ts +343 -0
  110. package/template/neo-pipeline-cmps/src/utils/queryByCustomSQL.ts +121 -0
  111. package/template/neo-pipeline-cmps/src/utils/requestDebounce.ts +22 -0
  112. package/template/neo-pipeline-cmps/src/utils/simpleTable.tsx +349 -0
  113. package/template/neo-pipeline-cmps/src/utils/stageSwitch.ts +15 -0
  114. package/template/neo-pipeline-cmps/src/utils/stageTimeChart.ts +90 -0
  115. package/template/neo-pipeline-cmps/src/utils/targetNumber.ts +12 -0
  116. package/template/neo-pipeline-cmps/tsconfig.json +40 -0
  117. package/template/neo-web-entity-grid/package.json +2 -2
  118. package/template/neo-web-form/package.json +2 -2
  119. package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +161 -41
  120. package/template/neo-web-form/src/components/batchAddTable__c/model.ts +4 -2
  121. package/template/react-custom-cmp-template/package.json +1 -1
  122. package/template/react-ts-custom-cmp-template/package.json +1 -1
  123. package/template/vue2-custom-cmp-template/package.json +1 -1
  124. package/template/asset-manage-template/src/utils/axiosFetcher.ts +0 -37
  125. package/template/asset-manage-template/src/utils/queryObjectData.ts +0 -112
  126. package/template/asset-manage-template/src/utils/xobjects.ts +0 -162
  127. package/template/neo-bi-cmps/.npmrc copy +0 -1
  128. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +0 -52
  129. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +0 -183
  130. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +0 -90
  131. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +0 -218
  132. package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +0 -31
  133. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +0 -158
  134. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +0 -40
  135. package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +0 -154
  136. package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +0 -36
  137. package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +0 -86
  138. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +0 -62
  139. package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +0 -48
  140. package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +0 -24
  141. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +0 -100
  142. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +0 -46
  143. package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +0 -60
  144. package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +0 -35
  145. package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +0 -70
  146. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +0 -50
  147. package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +0 -33
  148. package/template/neo-bi-cmps/src/components/oppList__c/README.md +0 -52
  149. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +0 -285
  150. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +0 -86
  151. package/template/neo-bi-cmps/src/components/oppList__c/style.scss +0 -133
  152. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/README.md +0 -39
  153. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +0 -130
  154. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +0 -66
  155. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +0 -133
  156. package/template/neo-bi-cmps/src/components/stageSwitch__c/README.md +0 -36
  157. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +0 -118
  158. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +0 -92
  159. package/template/neo-bi-cmps/src/components/stageTimeChart__c/README.md +0 -37
  160. package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +0 -126
  161. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +0 -57
  162. package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +0 -37
  163. package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +0 -80
  164. package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +0 -45
  165. package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +0 -37
  166. package/template/neo-bi-cmps/src/utils/axiosFetcher.ts +0 -37
  167. package/template/neo-bi-cmps/src/utils/queryObjectData.ts +0 -76
  168. package/template/neo-bi-cmps/src/utils/xobjects.ts +0 -162
  169. package/template/neo-custom-cmp-template/src/utils/axiosFetcher.ts +0 -37
  170. package/template/neo-custom-cmp-template/src/utils/queryObjectData.ts +0 -112
  171. package/template/neo-custom-cmp-template/src/utils/xobjects.ts +0 -162
  172. package/template/neo-h5-cmps/src/utils/axiosFetcher.ts +0 -37
  173. package/template/neo-h5-cmps/src/utils/xobjects.ts +0 -167
  174. package/template/neo-order-cmps/src/utils/axiosFetcher.ts +0 -37
  175. package/template/neo-order-cmps/src/utils/queryObjectData.ts +0 -112
  176. package/template/neo-order-cmps/src/utils/xobjects.ts +0 -162
  177. package/template/neo-web-entity-grid/src/utils/axiosFetcher.ts +0 -37
  178. package/template/neo-web-entity-grid/src/utils/queryObjectData.ts +0 -112
  179. package/template/neo-web-entity-grid/src/utils/xobjects.ts +0 -167
  180. package/template/neo-web-form/src/utils/axiosFetcher.ts +0 -37
  181. package/template/neo-web-form/src/utils/queryObjectData.ts +0 -112
  182. package/template/neo-web-form/src/utils/xobjects.ts +0 -167
@@ -0,0 +1,455 @@
1
+ /**
2
+ * @file Stage Time Component
3
+ * @description Displays the average time spent in each sales stage; chart data from NeoBI queryDataTask, target/limit from XObject customItem3/4 (days)
4
+ */
5
+ import * as React from 'react';
6
+ import { Spin } from 'antd';
7
+ // @ts-ignore
8
+ import { request, xObject } from 'neo-open-api';
9
+ // @ts-ignore
10
+ import { BaseCmp, NeoEvent } from 'neo-ui-common';
11
+ // @ts-ignore
12
+ import isEqual from 'lodash/isEqual';
13
+ import { filter2chartFilter } from '../../utils/filter2chartFilter';
14
+ import {
15
+ buildQueryDataTaskFormBody,
16
+ extractStageKeyFromStageName,
17
+ parseNumberOrNaN,
18
+ getDefaultFilterWhereByProps,
19
+ } from '../../utils/common';
20
+ import {
21
+ buildStageTimeItems,
22
+ isClosedOutcomeStage,
23
+ parseDurationToDays,
24
+ type StageTimeItem,
25
+ type StageTimeRow,
26
+ } from '../../utils/stageTimeChart';
27
+
28
+ import './style.scss';
29
+
30
+ const QUERY_DATA_TASK_URL = '/rest/neobi/v2.0/bestpractices/queryDataTask';
31
+
32
+ const FORM_URLENCODED_UTF8 = 'application/x-www-form-urlencoded;charset=UTF-8';
33
+
34
+ interface ThresholdsXObjectApi {
35
+ xObjectApiKey?: string;
36
+ fields?: string[];
37
+ page?: number;
38
+ pageSize?: number;
39
+ }
40
+
41
+ interface StageTimeChartProps {
42
+ title?: string;
43
+ viewId?: string;
44
+ viewType?: string;
45
+ data?: {
46
+ __NeoCurrentUser?: { id?: string | number };
47
+ };
48
+ thresholdsXObjectApi?: ThresholdsXObjectApi;
49
+ showAiButton?: boolean;
50
+ onStageClick?: (stageName: string) => void;
51
+ className?: string;
52
+ style?: React.CSSProperties;
53
+ }
54
+
55
+ /** Threshold row: only normalize customItem3/4 to number, keep other fields as-is */
56
+ interface ThresholdRecordRow extends Record<string, unknown> {
57
+ customItem3__c: number;
58
+ customItem4__c: number;
59
+ }
60
+
61
+ interface StageTimeChartState {
62
+ loading: boolean;
63
+ error: string | null;
64
+ items: StageTimeItem[];
65
+ filter: any;
66
+ /** Matches threshold row customItem2__c, default New Business (can be updated by setFilter) */
67
+ oppType: string;
68
+ /** Full list fetched by fetchThresholdRecords (customItem3/4 converted to number) */
69
+ thresholdRecords: ThresholdRecordRow[];
70
+ }
71
+
72
+ class StageTimeChart extends BaseCmp<StageTimeChartProps, StageTimeChartState> {
73
+ /** Initialize state */
74
+ constructor(props: StageTimeChartProps) {
75
+ super(props);
76
+ const initialOppType =
77
+ String(
78
+ (props as StageTimeChartProps & { oppType?: string }).oppType ?? '',
79
+ ).trim() || 'New Business';
80
+ this.state = {
81
+ loading: false,
82
+ error: null,
83
+ items: [],
84
+ filter: getDefaultFilterWhereByProps(props),
85
+ oppType: initialOppType,
86
+ thresholdRecords: [],
87
+ };
88
+
89
+ this.getThresholdRecord = this.getThresholdRecord.bind(this);
90
+ this.fetchThresholdRecords = this.fetchThresholdRecords.bind(this);
91
+ this.fetchChartRows = this.fetchChartRows.bind(this);
92
+ this.fetchAllData = this.fetchAllData.bind(this);
93
+ this.refreshData = this.refreshData.bind(this);
94
+ this.setFilter = this.setFilter.bind(this);
95
+ }
96
+
97
+ /** Fetch thresholds and chart data after mount */
98
+ componentDidMount() {
99
+ this.fetchAllData();
100
+
101
+ /*
102
+ // Listen to a broadcast event
103
+ NeoEvent.listen('updateFilterData', (filterData: any) => {
104
+ console.log('StageTimeChart received broadcast event updateFilterData: ', filterData);
105
+ this.setFilter(filterData);
106
+ });
107
+ */
108
+ }
109
+
110
+ /** Re-fetch data when key props change */
111
+ componentDidUpdate(
112
+ prevProps: StageTimeChartProps,
113
+ prevState: StageTimeChartState,
114
+ ) {
115
+ const uid = this.props.data?.__NeoCurrentUser?.id;
116
+ const puid = prevProps.data?.__NeoCurrentUser?.id;
117
+ const api = this.props.thresholdsXObjectApi;
118
+ const papi = prevProps.thresholdsXObjectApi;
119
+ const apiKeyChanged =
120
+ api?.xObjectApiKey !== papi?.xObjectApiKey ||
121
+ JSON.stringify(api?.fields ?? []) !== JSON.stringify(papi?.fields ?? []);
122
+ const filterChanged = !isEqual(this.state.filter, prevState.filter);
123
+ const oppTypeChanged = this.state.oppType !== prevState.oppType;
124
+
125
+ if (
126
+ this.props.viewId !== prevProps.viewId ||
127
+ this.props.viewType !== prevProps.viewType ||
128
+ uid !== puid ||
129
+ apiKeyChanged ||
130
+ filterChanged ||
131
+ oppTypeChanged
132
+ ) {
133
+ this.fetchAllData();
134
+ }
135
+ }
136
+
137
+ /** Find a single row in the list by customItem1__c / customItem2__c; customItem2 defaults to state.oppType */
138
+ getThresholdRecord(
139
+ customItem1__c: string = String(this.state.oppType ?? 'New Business'),
140
+ customItem2__c: string,
141
+ source: ThresholdRecordRow[] = this.state.thresholdRecords,
142
+ ): ThresholdRecordRow | undefined {
143
+ const k1 = String(customItem1__c ?? '').trim();
144
+ const k2 = String(customItem2__c ?? '').trim();
145
+ return source.find((r) => {
146
+ return (
147
+ String(r.customItem1__c ?? '').trim() === k1 &&
148
+ String(r.customItem2__c ?? '').trim() === k2
149
+ );
150
+ });
151
+ }
152
+
153
+ /** Fetch threshold list, only convert customItem3/4 to number, and write to state.thresholdRecords */
154
+ async fetchThresholdRecords(): Promise<ThresholdRecordRow[]> {
155
+ const { thresholdsXObjectApi } = this.props;
156
+ const apiKey = thresholdsXObjectApi?.xObjectApiKey;
157
+ if (!apiKey || String(apiKey).trim() === '') {
158
+ this.setState({ thresholdRecords: [] });
159
+ return [];
160
+ }
161
+
162
+ const defaultFields = [
163
+ 'customItem1__c',
164
+ 'customItem2__c',
165
+ 'customItem3__c',
166
+ 'customItem4__c',
167
+ ];
168
+ const fields = thresholdsXObjectApi?.fields?.length
169
+ ? [...thresholdsXObjectApi.fields]
170
+ : defaultFields;
171
+ const uniq = Array.from(new Set(fields));
172
+
173
+ try {
174
+ const result = await xObject.query({
175
+ xObjectApiKey: apiKey,
176
+ fields: uniq,
177
+ page: thresholdsXObjectApi?.page ?? 1,
178
+ pageSize: thresholdsXObjectApi?.pageSize ?? 500,
179
+ });
180
+
181
+ if (!result?.status) {
182
+ console.warn(
183
+ 'StageTimeChart xObject.query(thresholds) not successful:',
184
+ result?.msg ?? result,
185
+ );
186
+ this.setState({ thresholdRecords: [] });
187
+ return [];
188
+ }
189
+
190
+ const records = (result.data ?? []) as Record<string, unknown>[];
191
+ const thresholdRecords: ThresholdRecordRow[] = records
192
+ .filter((r) => r && typeof r === 'object')
193
+ .map((r) => ({
194
+ ...r,
195
+ customItem3__c: parseNumberOrNaN(r.customItem3__c),
196
+ customItem4__c: parseNumberOrNaN(r.customItem4__c),
197
+ }));
198
+
199
+ this.setState({ thresholdRecords });
200
+ return thresholdRecords;
201
+ } catch (e) {
202
+ console.error('StageTimeChart failed to load threshold data:', e);
203
+ this.setState({ thresholdRecords: [] });
204
+ return [];
205
+ }
206
+ }
207
+
208
+ /** Request queryDataTask, parse table rows and attach target/limit for each row (from threshold row customItem3/4) */
209
+ async fetchChartRows(
210
+ thresholdRecords: ThresholdRecordRow[],
211
+ ): Promise<{ rows: StageTimeRow[]; error: string | null }> {
212
+ const { viewId, viewType } = this.props;
213
+ const oppType = this.state.oppType ?? 'New Business';
214
+ const userId = this.props.data?.__NeoCurrentUser?.id;
215
+
216
+ if (viewId == null || viewId === '' || userId == null || userId === '') {
217
+ return {
218
+ rows: [],
219
+ error: 'Missing viewId or current user id (data.__NeoCurrentUser.id)',
220
+ };
221
+ }
222
+
223
+ const filterObj =
224
+ this.state.filter &&
225
+ typeof this.state.filter === 'object' &&
226
+ !Array.isArray(this.state.filter)
227
+ ? this.state.filter
228
+ : {};
229
+
230
+ try {
231
+ const res = await request({
232
+ url: QUERY_DATA_TASK_URL,
233
+ method: 'POST',
234
+ data: buildQueryDataTaskFormBody({
235
+ viewId: String(viewId),
236
+ userId,
237
+ type: viewType ?? 'sync',
238
+ filter: filterObj,
239
+ }),
240
+ headers: {
241
+ 'Content-Type': FORM_URLENCODED_UTF8,
242
+ },
243
+ });
244
+
245
+ const status = res?.status;
246
+ const table = res?.data;
247
+
248
+ if (status !== 0 || !Array.isArray(table)) {
249
+ return {
250
+ rows: [],
251
+ error: res?.message || res?.msg || 'Failed to query chart data',
252
+ };
253
+ }
254
+
255
+ const rowsMatrix = table as unknown[][];
256
+ if (!Array.isArray(rowsMatrix) || rowsMatrix.length < 2) {
257
+ return { rows: [], error: null };
258
+ }
259
+
260
+ const dataRows = rowsMatrix.slice(1);
261
+ const rows: StageTimeRow[] = [];
262
+ for (let i = 0; i < dataRows.length; i++) {
263
+ const row = dataRows[i];
264
+ if (!Array.isArray(row) || row.length < 3) continue;
265
+ const raw = row[2];
266
+ const actualTimeDisplay = String(raw ?? '');
267
+ const actualTime =
268
+ typeof raw === 'number' && Number.isFinite(raw)
269
+ ? raw
270
+ : parseDurationToDays(raw);
271
+ const stageName = String(row[0] ?? '');
272
+ if (isClosedOutcomeStage(stageName)) continue;
273
+ const stageKey = extractStageKeyFromStageName(stageName);
274
+ const th = this.getThresholdRecord(oppType, stageKey, thresholdRecords);
275
+ const targetPercent = th?.customItem3__c ?? NaN;
276
+ const limitPercent = th?.customItem4__c ?? NaN;
277
+
278
+ rows.push({
279
+ stageName,
280
+ actualTime,
281
+ actualTimeDisplay,
282
+ targetPercent: Number.isFinite(targetPercent) ? targetPercent : NaN,
283
+ limitPercent: Number.isFinite(limitPercent) ? limitPercent : NaN,
284
+ });
285
+ }
286
+
287
+ return { rows, error: null };
288
+ } catch (e: any) {
289
+ console.error('StageTimeChart queryDataTask failed:', e);
290
+ return {
291
+ rows: [],
292
+ error: e?.message || 'Network request failed',
293
+ };
294
+ }
295
+ }
296
+
297
+ /** 先拉阈值再拉图表,合并为 items */
298
+ async fetchAllData() {
299
+ this.setState({ loading: true, error: null });
300
+
301
+ const thresholdRecords = await this.fetchThresholdRecords();
302
+ const chartResult = await this.fetchChartRows(thresholdRecords);
303
+
304
+ if (chartResult.error) {
305
+ this.setState({
306
+ loading: false,
307
+ error: chartResult.error,
308
+ items: [],
309
+ });
310
+ return;
311
+ }
312
+
313
+ const items = buildStageTimeItems(chartResult.rows);
314
+
315
+ this.setState({
316
+ loading: false,
317
+ error: null,
318
+ items,
319
+ });
320
+ }
321
+
322
+ /** 刷新图表与阈值(设计器可绑定) */
323
+ @NeoEvent.function
324
+ async refreshData() {
325
+ await this.fetchAllData();
326
+ }
327
+
328
+ /** 设置 queryDataTask 的 filter 并重新拉数(设计器可绑定) */
329
+ @NeoEvent.function
330
+ setFilter(filter?: any) {
331
+ if (!filter) {
332
+ return;
333
+ }
334
+ const nextFilter = filter2chartFilter(filter);
335
+ console.log('[StageTimeChart__c] setFilter:', filter, nextFilter);
336
+ if (!isEqual(nextFilter, this.state.filter)) {
337
+ const nextOppType =
338
+ filter.businessTypeLabel != null &&
339
+ String(filter.businessTypeLabel).trim() !== ''
340
+ ? String(filter.businessTypeLabel).trim()
341
+ : this.state.oppType;
342
+ this.setState({ filter: nextFilter, oppType: nextOppType });
343
+ }
344
+ }
345
+
346
+ @NeoEvent.dispatch
347
+ onActiveStageChange(eventData?: any) {}
348
+
349
+ // 点击阶段切换时,更新当前激活的销售阶段
350
+ handleStageClick(stageName: string) {
351
+ this.onActiveStageChange({
352
+ activeStage: stageName,
353
+ });
354
+ }
355
+
356
+ /** 渲染标题、图例与阶段条形图 */
357
+ render() {
358
+ const {
359
+ title = 'Avg. Time in Stage',
360
+ // showAiButton = false,
361
+ onStageClick,
362
+ className,
363
+ style,
364
+ } = this.props;
365
+ const showAiButton = false;
366
+
367
+ const { loading, error, items } = this.state;
368
+
369
+ return (
370
+ <div
371
+ className={`stageTimeChart__c ${className || ''}`}
372
+ style={style}
373
+ data-time="2026.4.17 01"
374
+ >
375
+ <Spin spinning={loading}>
376
+ <div className="chart-header">
377
+ <h3 className="chart-title">{title}</h3>
378
+ {showAiButton && (
379
+ <span
380
+ className="ai-btn"
381
+ onClick={() => console.log('AI Analysis clicked')}
382
+ title="AI Analysis"
383
+ >
384
+
385
+ </span>
386
+ )}
387
+ </div>
388
+
389
+ {error ? (
390
+ <div
391
+ className="stage-time-chart-error"
392
+ style={{ color: '#cf1322', fontSize: 12, marginBottom: 8 }}
393
+ >
394
+ {error}
395
+ </div>
396
+ ) : null}
397
+
398
+ <div className="chart-legend">
399
+ <span className="legend-item">
400
+ <span className="legend-actual" />
401
+ Actual
402
+ </span>
403
+ <span className="legend-item">
404
+ <span className="legend-target" />
405
+ Target
406
+ </span>
407
+ <span className="legend-item">
408
+ <span className="legend-limit" />
409
+ Limit
410
+ </span>
411
+ </div>
412
+
413
+ <div className="chart-body">
414
+ {items.map((item, index) => (
415
+ <div
416
+ key={`${item.stageName}-${index}`}
417
+ className="stage-time-item"
418
+ onClick={() => this.handleStageClick(item.stageName)}
419
+ >
420
+ <div className="stage-time-header">
421
+ <span className="stage-name">{item.stageName}</span>
422
+ <span
423
+ className="stage-time-value"
424
+ style={{ color: item.actualColor }}
425
+ >
426
+ {item.actualTime}
427
+ </span>
428
+ </div>
429
+ <div className="stage-time-bar">
430
+ <div
431
+ className="stage-time-actual"
432
+ style={{
433
+ width: `${item.barActualWidthPct}%`,
434
+ backgroundColor: item.actualColor,
435
+ }}
436
+ />
437
+ <div
438
+ className="stage-time-target"
439
+ style={{ left: `${item.barTargetLeftPct}%` }}
440
+ />
441
+ <div
442
+ className="stage-time-limit"
443
+ style={{ left: `${item.barLimitLeftPct}%` }}
444
+ />
445
+ </div>
446
+ </div>
447
+ ))}
448
+ </div>
449
+ </Spin>
450
+ </div>
451
+ );
452
+ }
453
+ }
454
+
455
+ export default StageTimeChart;
@@ -0,0 +1,106 @@
1
+ export class StageTimeChartModel {
2
+ label: string = 'Stage Time';
3
+ description: string =
4
+ 'Displays the average time spent in each sales stage; chart data from NeoBI queryDataTask, target/limit from XObject customItem3/4 (days)';
5
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/barChart.svg';
6
+ targetPage: string[] = ['all'];
7
+ targetDevice: string = 'all';
8
+
9
+ defaultComProps = {
10
+ title: 'Avg. Time in Stage',
11
+ viewId: '4264466340118875',
12
+ viewType: 'sync',
13
+ showAiButton: true,
14
+ /** Opportunity type matching threshold record customItem2__c, default New Business */
15
+ oppType: 'New Business',
16
+ /** Thresholds: customItem1__c stage key, customItem2__c opportunity type, customItem3__c/4 target and limit (days) */
17
+ thresholdsXObjectApi: {
18
+ xObjectApiKey: 'customEntity105__c',
19
+ fields: [
20
+ 'customItem1__c',
21
+ 'customItem2__c',
22
+ 'customItem3__c',
23
+ 'customItem4__c',
24
+ ],
25
+ page: 1,
26
+ pageSize: 1000,
27
+ },
28
+ };
29
+
30
+ events = [
31
+ {
32
+ apiKey: 'onActiveStageChange',
33
+ label: 'Triggered on stage bar click',
34
+ helpText:
35
+ 'Triggered when clicking a stage bar; event params include activeStage (current active stage)',
36
+ eventParams:
37
+ '[{"apiKey":"eventParam","children":[{"apiKey":"activeStage","label":"Current active stage","type":"String"}],"label":"Event parameters","type":"Object"}]',
38
+ },
39
+ ];
40
+
41
+ functions = [
42
+ {
43
+ apiKey: 'refreshData',
44
+ label: 'Refresh data',
45
+ helpTextKey:
46
+ 'Re-request queryDataTask and threshold config to refresh stage time',
47
+ },
48
+ {
49
+ apiKey: 'setFilter',
50
+ label: 'Set filter conditions',
51
+ helpTextKey: 'Set the queryDataTask filter and re-fetch chart data',
52
+ funcInParams: [
53
+ {
54
+ apiKey: 'filter',
55
+ label: 'Filter conditions',
56
+ type: 'Object',
57
+ required: false,
58
+ },
59
+ ],
60
+ },
61
+ ];
62
+
63
+ propsSchema = [
64
+ {
65
+ type: 'panelInput',
66
+ name: 'title',
67
+ label: 'Title',
68
+ },
69
+ {
70
+ type: 'panelInput',
71
+ name: 'viewId',
72
+ label: 'View ID',
73
+ },
74
+ {
75
+ type: 'panelInput',
76
+ name: 'viewType',
77
+ label: 'View type (request type)',
78
+ },
79
+ {
80
+ type: 'panelInput',
81
+ name: 'oppType',
82
+ label: 'Opportunity type (matching customItem2__c)',
83
+ },
84
+ /*
85
+ {
86
+ type: 'xObjectDataApi',
87
+ name: 'thresholdsXObjectApi',
88
+ label: 'Stage target/limit data source',
89
+ value: {
90
+ xObjectApiKey: '',
91
+ fields: [
92
+ 'customItem1__c',
93
+ 'customItem2__c',
94
+ 'customItem3__c',
95
+ 'customItem4__c',
96
+ ],
97
+ },
98
+ placeholder:
99
+ 'Entity must contain customItem1__c (stage), customItem2__c (opportunity type), customItem3/4 (target/limit days)',
100
+ custom: true,
101
+ },
102
+ */
103
+ ];
104
+ }
105
+
106
+ export default StageTimeChartModel;
@@ -1,10 +1,11 @@
1
- .stage-time-chart__c {
1
+ .stageTimeChart__c {
2
2
  background: #fff;
3
3
  border-radius: 8px;
4
- padding: 20px;
4
+ padding: 12px 20px;
5
5
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
6
6
  display: flex;
7
7
  flex-direction: column;
8
+ min-height: 400px;
8
9
 
9
10
  .chart-header {
10
11
  display: flex;