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,52 @@
1
+ export class FilterBarModel {
2
+ label: string = 'Filter Bar';
3
+ description: string =
4
+ 'Supports multi-dimensional filtering by date range, owner, business type, etc.';
5
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/filter.svg';
6
+ targetPage: string[] = ['all'];
7
+ targetDevice: string = 'all';
8
+
9
+ defaultComProps = {
10
+ closeDateOptions: [
11
+ { value: 201, label: 'This Week' },
12
+ { value: 301, label: 'This Month' },
13
+ { value: 401, label: 'This Quarter' },
14
+ { value: 'custom', label: 'Custom' },
15
+ ],
16
+ /** When matching the apiKey of an item returned by the business type API, use it as the default Business Type selection */
17
+ defaultBusiType: 'defaultBusiType_1', // New Business
18
+ };
19
+
20
+ /**
21
+ * Declare all events that the current component will trigger (matching @NeoEvent.dispatch method names in the component)
22
+ */
23
+ events = [
24
+ {
25
+ apiKey: 'onFiltersChange',
26
+ label: 'After filter conditions change',
27
+ helpText:
28
+ 'Triggered when any filter option or custom time range changes; event params include closeDate, closeDateCustomRange (for non-custom: current relative period start/end Unix ms timestamps; for custom: RangePicker start/end), opportunityOwner (owner multi-select id array), businessType, businessTypeLabel (business type display name), changesSince, changesSinceCustomTime (for Custom Changes Since: selected date midnight; otherwise for non-custom Close Date: period start midnight)',
29
+ eventParams:
30
+ '[{"apiKey":"eventParam","children":[{"apiKey":"data","label":"Current filter data","type":"Object"}],"label":"Event parameters","type":"Object"}]',
31
+ },
32
+ ];
33
+
34
+ functions = [
35
+ {
36
+ apiKey: 'resetFilters',
37
+ label: 'Reset filters',
38
+ helpTextKey:
39
+ 'Reset all filter conditions to defaults and trigger the "After filter conditions change" event',
40
+ },
41
+ {
42
+ apiKey: 'getFilters',
43
+ label: 'Get filters',
44
+ helpTextKey:
45
+ 'Return current filter snapshot object; when closeDate is not custom, closeDateCustomRange is the relative period start/end timestamps; when custom, it is the custom range; businessTypeLabel is the business type display name; changesSinceCustomTime is the selected date midnight when Changes Since is Custom, otherwise the period start midnight when Close Date is not custom',
46
+ },
47
+ ];
48
+
49
+ propsSchema = [];
50
+ }
51
+
52
+ export default FilterBarModel;
@@ -0,0 +1,119 @@
1
+ .filterBar__c {
2
+ background: #fff;
3
+ border-radius: 8px;
4
+ padding: 16px 20px;
5
+ display: flex;
6
+ flex-wrap: wrap;
7
+ justify-content: flex-start;
8
+ gap: 10px 12px;
9
+ align-items: flex-start;
10
+ align-content: flex-start;
11
+ margin-bottom: 12px;
12
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
13
+
14
+ .filter-block {
15
+ display: flex;
16
+ flex-wrap: nowrap;
17
+ align-items: center;
18
+ gap: 6px 8px;
19
+ flex: 0 1 auto;
20
+ min-width: 0;
21
+ max-width: 100%;
22
+ }
23
+
24
+ .filter-field {
25
+ display: flex;
26
+ flex-wrap: nowrap;
27
+ align-items: center;
28
+ gap: 6px;
29
+ min-width: 0;
30
+
31
+ label {
32
+ font-size: 13px;
33
+ color: #666;
34
+ white-space: nowrap;
35
+ display: inline-flex;
36
+ align-items: center;
37
+ gap: 4px;
38
+ }
39
+
40
+ &.filter-field-range {
41
+ flex: 0 0 auto;
42
+ }
43
+ }
44
+
45
+ .filter-select {
46
+ min-width: 160px;
47
+ max-width: 100%;
48
+ }
49
+
50
+ .filter-select-wide {
51
+ min-width: 220px;
52
+ }
53
+
54
+ .filter-select-spin-wrap {
55
+ min-width: 220px;
56
+ min-height: 32px;
57
+ }
58
+
59
+ .filter-btn {
60
+ padding: 6px 16px;
61
+ border-radius: 6px;
62
+ font-size: 13px;
63
+ cursor: pointer;
64
+ border: 1px solid #ddd;
65
+ background: #fff;
66
+
67
+ &.active {
68
+ background: #6366f1;
69
+ color: #fff;
70
+ border-color: #6366f1;
71
+ }
72
+ }
73
+
74
+ /* Changes Since question mark tooltip */
75
+ .help-tip {
76
+ position: relative;
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ cursor: help;
81
+ color: #999;
82
+ font-size: 12px;
83
+ margin-left: 2px;
84
+ width: 16px;
85
+ height: 16px;
86
+ border-radius: 50%;
87
+ border: 1px solid #ddd;
88
+
89
+ .help-tip-text {
90
+ display: none;
91
+ position: absolute;
92
+ bottom: calc(100% + 6px);
93
+ left: 50%;
94
+ transform: translateX(-50%);
95
+ background: #333;
96
+ color: #fff;
97
+ padding: 6px 10px;
98
+ border-radius: 6px;
99
+ font-size: 11px;
100
+ white-space: nowrap;
101
+ z-index: 100;
102
+ font-weight: 400;
103
+
104
+ &::after {
105
+ content: '';
106
+ position: absolute;
107
+ top: 100%;
108
+ left: 50%;
109
+ transform: translateX(-50%);
110
+ border: 5px solid transparent;
111
+ border-top-color: #333;
112
+ }
113
+ }
114
+
115
+ &:hover .help-tip-text {
116
+ display: block;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,39 @@
1
+ # PipelineFunnel Component
2
+
3
+ Pipeline funnel chart component that displays the funnel conversion of the sales pipeline.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ import PipelineFunnel from './components/pipelineFunnel__c';
9
+
10
+ <PipelineFunnel
11
+ title="Pipeline Funnel"
12
+ totalAmount="$11.1M"
13
+ stages={[
14
+ { name: 'Prospecting', amount: '$3.2M', count: 15, color: '#3b82f6' },
15
+ { name: 'Needs Analysis', amount: '$1.8M', count: 7, conversionRate: '56.3%', color: '#22c55e' },
16
+ ]}
17
+ onStageClick={(name) => console.log('Clicked:', name)}
18
+ />
19
+ ```
20
+
21
+ ## Props
22
+
23
+ | Property | Description | Type | Default |
24
+ |----------|-------------|------|---------|
25
+ | title | Title | string | 'Pipeline Funnel' |
26
+ | totalAmount | Total amount | string | '$11.1M' |
27
+ | stages | Funnel stage data | FunnelStage[] | [] |
28
+ | showAiButton | Show AI button | boolean | true |
29
+ | onStageClick | Stage click callback | (stageName: string) => void | - |
30
+
31
+ ## FunnelStage
32
+
33
+ | Property | Description | Type |
34
+ |----------|-------------|------|
35
+ | name | Stage name | string |
36
+ | amount | Amount | string |
37
+ | count | Count | number |
38
+ | conversionRate | Conversion rate | string |
39
+ | color | Color | string |
@@ -0,0 +1,416 @@
1
+ /**
2
+ * @file Pipeline Funnel Chart Component
3
+ * @description Displays the funnel conversion of the sales pipeline, data from NeoBI queryDataTask
4
+ */
5
+ import * as React from 'react';
6
+ import * as echarts from 'echarts';
7
+ import { Spin } from 'antd';
8
+ // @ts-ignore
9
+ import { request } from 'neo-open-api';
10
+ // @ts-ignore
11
+ import { BaseCmp, NeoEvent } from 'neo-ui-common';
12
+ // @ts-ignore
13
+ import isEqual from 'lodash/isEqual';
14
+ import { filter2chartFilter } from '../../utils/filter2chartFilter';
15
+ import {
16
+ buildQueryDataTaskFormBody,
17
+ formatAmountDisplay,
18
+ getDefaultFilterWhereByProps,
19
+ } from '../../utils/common';
20
+ import {
21
+ buildFunnelChartOption,
22
+ filterOutClosedLostRows,
23
+ isTruthyProp,
24
+ parseRowsToStages,
25
+ type FunnelStage,
26
+ } from '../../utils/pipelineFunnel';
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 PipelineFunnelProps {
35
+ title?: string;
36
+ /** View ID, used in queryDataTask */
37
+ viewId?: string;
38
+ /** View type, corresponding to request parameter type */
39
+ viewType?: string;
40
+ /**
41
+ * When true, layer width decreases with stage order (classic funnel), layer height proportional to each stage's amount;
42
+ * When false (default), layer width is proportional to each stage's actual amount */
43
+ useClassicFunnelShape?: boolean;
44
+ /** Neo injected, contains current user */
45
+ data?: {
46
+ __NeoCurrentUser?: { id?: string | number };
47
+ };
48
+ showAiButton?: boolean;
49
+ onStageClick?: (stageName: string) => void;
50
+ className?: string;
51
+ style?: React.CSSProperties;
52
+ }
53
+
54
+ interface PipelineFunnelState {
55
+ loading: boolean;
56
+ error: string | null;
57
+ stages: FunnelStage[];
58
+ /** Sum of amount values across all stages, used for percentage calculation */
59
+ totalAmountNum: number;
60
+ /** Total amount label for top display */
61
+ totalAmountLabel: string;
62
+ /** Filter for request body, can be updated by component action setFilter */
63
+ filter: any;
64
+ }
65
+
66
+ class PipelineFunnel extends BaseCmp<PipelineFunnelProps, PipelineFunnelState> {
67
+ private chartRef = React.createRef<HTMLDivElement>();
68
+
69
+ private chartInstance: echarts.ECharts | null = null;
70
+
71
+ private resizeObserver: ResizeObserver | null = null;
72
+
73
+ constructor(props: PipelineFunnelProps) {
74
+ super(props);
75
+ this.state = {
76
+ loading: false,
77
+ error: null,
78
+ stages: [],
79
+ totalAmountNum: 0,
80
+ totalAmountLabel: formatAmountDisplay(0),
81
+ filter: getDefaultFilterWhereByProps(props),
82
+ };
83
+
84
+ this.initChart = this.initChart.bind(this);
85
+ this.bindResize = this.bindResize.bind(this);
86
+ this.unbindResize = this.unbindResize.bind(this);
87
+ this.handleWindowResize = this.handleWindowResize.bind(this);
88
+ this.updateChart = this.updateChart.bind(this);
89
+ this.fetchChartData = this.fetchChartData.bind(this);
90
+ this.refreshData = this.refreshData.bind(this);
91
+ this.setFilter = this.setFilter.bind(this);
92
+ }
93
+
94
+ componentDidMount() {
95
+ this.fetchChartData();
96
+ this.initChart();
97
+ this.bindResize();
98
+
99
+ /*
100
+ // Listen to a broadcast event
101
+ NeoEvent.listen('updateFilterData', (filterData: any) => {
102
+ console.log('PipelineFunnel received broadcast event updateFilterData: ', filterData);
103
+ this.setFilter(filterData);
104
+ });
105
+ */
106
+ }
107
+
108
+ componentDidUpdate(
109
+ prevProps: PipelineFunnelProps,
110
+ prevState: PipelineFunnelState,
111
+ ) {
112
+ const uid = this.props.data?.__NeoCurrentUser?.id;
113
+ const puid = prevProps.data?.__NeoCurrentUser?.id;
114
+ if (
115
+ this.props.viewId !== prevProps.viewId ||
116
+ this.props.viewType !== prevProps.viewType ||
117
+ uid !== puid
118
+ ) {
119
+ this.fetchChartData();
120
+ }
121
+
122
+ if (
123
+ prevState.stages !== this.state.stages ||
124
+ prevState.loading !== this.state.loading ||
125
+ isTruthyProp(prevProps.useClassicFunnelShape) !==
126
+ isTruthyProp(this.props.useClassicFunnelShape)
127
+ ) {
128
+ this.updateChart();
129
+ }
130
+
131
+ if (!this.chartInstance && this.chartRef.current) {
132
+ this.initChart();
133
+ }
134
+ }
135
+
136
+ componentWillUnmount() {
137
+ this.unbindResize();
138
+ if (this.chartInstance) {
139
+ this.chartInstance.dispose();
140
+ this.chartInstance = null;
141
+ }
142
+ }
143
+
144
+ initChart() {
145
+ if (!this.chartRef.current || this.chartInstance) return;
146
+ this.chartInstance = echarts.init(this.chartRef.current);
147
+ this.updateChart();
148
+ }
149
+
150
+ bindResize() {
151
+ window.addEventListener('resize', this.handleWindowResize);
152
+ if (typeof ResizeObserver === 'undefined') return;
153
+ this.resizeObserver = new ResizeObserver(() => {
154
+ this.chartInstance?.resize();
155
+ if (
156
+ isTruthyProp(this.props.useClassicFunnelShape) &&
157
+ this.state.stages.length > 0
158
+ ) {
159
+ this.updateChart();
160
+ }
161
+ });
162
+ const el = this.chartRef.current;
163
+ if (el) this.resizeObserver.observe(el);
164
+ }
165
+
166
+ unbindResize() {
167
+ window.removeEventListener('resize', this.handleWindowResize);
168
+ this.resizeObserver?.disconnect();
169
+ this.resizeObserver = null;
170
+ }
171
+
172
+ handleWindowResize() {
173
+ this.chartInstance?.resize();
174
+ if (
175
+ isTruthyProp(this.props.useClassicFunnelShape) &&
176
+ this.state.stages.length > 0
177
+ ) {
178
+ this.updateChart();
179
+ }
180
+ }
181
+
182
+ updateChart() {
183
+ if (!this.chartInstance) return;
184
+ const { stages } = this.state;
185
+ const { onStageClick, useClassicFunnelShape } = this.props;
186
+ const classicShape = isTruthyProp(useClassicFunnelShape);
187
+ const el = this.chartRef.current;
188
+ const fallbackH = Math.max(stages.length * 52, 220);
189
+ const chartViewHeightPx = Math.max(el?.clientHeight ?? 0, fallbackH);
190
+
191
+ try {
192
+ if (stages.length === 0) {
193
+ this.chartInstance.clear();
194
+ } else {
195
+ const option = buildFunnelChartOption(stages, classicShape, {
196
+ chartViewHeightPx: classicShape ? chartViewHeightPx : undefined,
197
+ funnelGap: 2,
198
+ });
199
+ console.log('[PipelineFunnel__c] updateChart option:', option);
200
+ this.chartInstance.setOption(option, true);
201
+ this.chartInstance.off('click');
202
+ this.chartInstance.on(
203
+ 'click',
204
+ (params: {
205
+ componentType?: string;
206
+ seriesType?: string;
207
+ seriesName?: string;
208
+ dataIndex?: number;
209
+ }) => {
210
+ if (
211
+ params.componentType === 'series' &&
212
+ params.seriesType === 'funnel' &&
213
+ params.seriesName !== 'PipelineName'
214
+ ) {
215
+ const idx =
216
+ typeof params.dataIndex === 'number' ? params.dataIndex : -1;
217
+ const name = stages[idx]?.name;
218
+ if (name) {
219
+ this.handleStageClick(name);
220
+ onStageClick?.(name);
221
+ }
222
+ }
223
+ },
224
+ );
225
+ }
226
+ requestAnimationFrame(() => this.chartInstance?.resize());
227
+ } catch (e) {
228
+ console.error('PipelineFunnel ECharts update failed:', e);
229
+ }
230
+ }
231
+
232
+ async fetchChartData() {
233
+ const { viewId, viewType } = this.props;
234
+ const userId = this.props.data?.__NeoCurrentUser?.id;
235
+
236
+ if (viewId == null || viewId === '' || userId == null || userId === '') {
237
+ this.setState({
238
+ loading: false,
239
+ error: 'Missing viewId or current user id (data.__NeoCurrentUser.id)',
240
+ stages: [],
241
+ totalAmountNum: 0,
242
+ totalAmountLabel: formatAmountDisplay(0),
243
+ });
244
+ return;
245
+ }
246
+
247
+ this.setState({ loading: true, error: null });
248
+
249
+ try {
250
+ const filterObj =
251
+ this.state.filter &&
252
+ typeof this.state.filter === 'object' &&
253
+ !Array.isArray(this.state.filter)
254
+ ? this.state.filter
255
+ : {};
256
+
257
+ const res = await request({
258
+ url: QUERY_DATA_TASK_URL,
259
+ method: 'POST',
260
+ data: buildQueryDataTaskFormBody({
261
+ viewId: String(viewId),
262
+ userId,
263
+ type: viewType ?? 'sync',
264
+ filter: filterObj,
265
+ }),
266
+ headers: {
267
+ 'Content-Type': FORM_URLENCODED_UTF8,
268
+ },
269
+ });
270
+
271
+ const status = res?.status;
272
+ const table = res?.data;
273
+
274
+ if (status !== 0 || !Array.isArray(table)) {
275
+ this.setState({
276
+ loading: false,
277
+ error: res?.message || res?.msg || 'Failed to query chart data',
278
+ stages: [],
279
+ totalAmountNum: 0,
280
+ totalAmountLabel: formatAmountDisplay(0),
281
+ });
282
+ return;
283
+ }
284
+
285
+ const filteredTable = filterOutClosedLostRows(table as unknown[][]);
286
+ const { stages, totalAmountNum } = parseRowsToStages(filteredTable);
287
+
288
+ this.setState({
289
+ loading: false,
290
+ error: null,
291
+ stages,
292
+ totalAmountNum,
293
+ totalAmountLabel: formatAmountDisplay(totalAmountNum),
294
+ });
295
+ } catch (e: any) {
296
+ console.error('PipelineFunnel queryDataTask failed:', e);
297
+ this.setState({
298
+ loading: false,
299
+ error: e?.message || 'Network request failed',
300
+ stages: [],
301
+ totalAmountNum: 0,
302
+ totalAmountLabel: formatAmountDisplay(0),
303
+ });
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Refresh funnel chart data (bindable in designer)
309
+ */
310
+ @NeoEvent.function
311
+ async refreshData() {
312
+ await this.fetchChartData();
313
+ }
314
+
315
+ /**
316
+ * Set filter conditions and re-fetch data (bindable in designer)
317
+ * @param filter Filter object, used as the filter field of queryDataTask
318
+ */
319
+ @NeoEvent.function
320
+ setFilter(filter?: any) {
321
+ if (!filter) {
322
+ return;
323
+ }
324
+ const nextFilter = filter2chartFilter(filter);
325
+ console.log('[PipelineFunnel__c] setFilter:', filter, nextFilter);
326
+ if (!isEqual(nextFilter, this.state.filter)) {
327
+ this.setState({ filter: nextFilter }, () => {
328
+ this.fetchChartData();
329
+ });
330
+ }
331
+ }
332
+
333
+ @NeoEvent.dispatch
334
+ onActiveStageChange(eventData?: any) {}
335
+
336
+ // Update the current active sales stage when clicking a stage
337
+ handleStageClick(stageName: string) {
338
+ this.onActiveStageChange({
339
+ activeStage: stageName,
340
+ });
341
+ }
342
+
343
+ render() {
344
+ const {
345
+ title = 'Pipeline Funnel',
346
+ // showAiButton = false,
347
+ className,
348
+ style,
349
+ } = this.props;
350
+ const showAiButton = false;
351
+
352
+ const { loading, error, stages, totalAmountLabel } = this.state;
353
+ const chartHeight = Math.max(stages.length * 52, 200);
354
+
355
+ return (
356
+ <div
357
+ className={`pipelineFunnel__c ${className || ''}`}
358
+ style={style}
359
+ data-time="2026.4.17 01"
360
+ >
361
+ <Spin spinning={loading}>
362
+ <div className="funnel-header">
363
+ <h3 className="funnel-title">{title}</h3>
364
+ {showAiButton && (
365
+ <span
366
+ className="ai-btn"
367
+ onClick={() => console.log('AI Analysis clicked')}
368
+ title="AI Analysis"
369
+ >
370
+
371
+ </span>
372
+ )}
373
+ </div>
374
+
375
+ {error ? (
376
+ <div
377
+ className="funnel-error"
378
+ style={{ color: '#cf1322', fontSize: 12, marginBottom: 8 }}
379
+ >
380
+ {error}
381
+ </div>
382
+ ) : null}
383
+
384
+ <div className="funnel-total">
385
+ <div className="funnel-total-label">Sales Amount</div>
386
+ <div className="funnel-total-value">{totalAmountLabel}</div>
387
+ </div>
388
+
389
+ <div className="funnel-body">
390
+ <div className="funnel-chart-wrap" style={{ height: chartHeight }}>
391
+ <div
392
+ ref={this.chartRef}
393
+ className="funnel-chart"
394
+ style={{ width: '100%', height: chartHeight }}
395
+ />
396
+ </div>
397
+ </div>
398
+
399
+ <div className="funnel-legend">
400
+ {stages.map((stage, index) => (
401
+ <span key={index} className="legend-item">
402
+ <span
403
+ className="legend-dot"
404
+ style={{ backgroundColor: stage.color }}
405
+ />
406
+ {stage.name}
407
+ </span>
408
+ ))}
409
+ </div>
410
+ </Spin>
411
+ </div>
412
+ );
413
+ }
414
+ }
415
+
416
+ export default PipelineFunnel;
@@ -0,0 +1,80 @@
1
+ export class PipelineFunnelModel {
2
+ label: string = 'Pipeline Funnel Chart';
3
+ description: string =
4
+ 'Displays the funnel conversion of the sales pipeline, visually showing amount and count at each stage';
5
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/funnel.svg';
6
+ targetPage: string[] = ['all'];
7
+ targetDevice: string = 'all';
8
+
9
+ defaultComProps = {
10
+ title: 'Pipeline Funnel',
11
+ viewId: '4264464770007375',
12
+ viewType: 'sync',
13
+ showAiButton: true,
14
+ /** When true, layer width decreases with stage order (classic shape), layer height proportional to amount; default false, layer width proportional to actual amount */
15
+ useClassicFunnelShape: false,
16
+ };
17
+
18
+ events = [
19
+ {
20
+ apiKey: 'onActiveStageChange',
21
+ label: 'Triggered on stage click',
22
+ helpText:
23
+ 'Triggered when clicking a funnel stage; event params include activeStage (current active stage)',
24
+ eventParams:
25
+ '[{"apiKey":"eventParam","children":[{"apiKey":"activeStage","label":"Current active stage","type":"String"}],"label":"Event parameters","type":"Object"}]',
26
+ },
27
+ ];
28
+
29
+ functions = [
30
+ {
31
+ apiKey: 'refreshData',
32
+ label: 'Refresh data',
33
+ helpTextKey: 'Re-request queryDataTask to refresh funnel chart data',
34
+ },
35
+ {
36
+ apiKey: 'setFilter',
37
+ label: 'Set filter conditions',
38
+ helpTextKey: 'Set the report component filter and re-fetch data',
39
+ funcInParams: [
40
+ {
41
+ apiKey: 'filter',
42
+ label: 'Filter conditions',
43
+ type: 'Object',
44
+ required: false,
45
+ },
46
+ ],
47
+ },
48
+ ];
49
+
50
+ propsSchema = [
51
+ {
52
+ type: 'panelInput',
53
+ name: 'title',
54
+ label: 'Title',
55
+ },
56
+ {
57
+ type: 'panelInput',
58
+ name: 'viewId',
59
+ label: 'View ID',
60
+ },
61
+ {
62
+ type: 'panelInput',
63
+ name: 'viewType',
64
+ label: 'View type (request type)',
65
+ },
66
+ {
67
+ type: 'panelSelect',
68
+ name: 'useClassicFunnelShape',
69
+ label: 'Funnel shape',
70
+ value: false,
71
+ options: [
72
+ { label: 'Shape based on actual amount', value: false },
73
+ { label: 'Classic funnel (layer height by value)', value: true },
74
+ ],
75
+ clearable: false,
76
+ },
77
+ ];
78
+ }
79
+
80
+ export default PipelineFunnelModel;