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,349 @@
1
+ /**
2
+ * Table simpleTable specific: formatting, history snapshots, trends and cell rendering
3
+ */
4
+ import * as React from 'react';
5
+ import { Card, Popover } from 'antd';
6
+ import moment from 'moment';
7
+
8
+ import { toFiniteNumber } from './common';
9
+
10
+ /** Period-start opportunity snapshot (associated with current row id) */
11
+ export interface HistoryOppSnap {
12
+ id: string;
13
+ closeDate: unknown;
14
+ opportunityName: string;
15
+ saleStageId: string;
16
+ money: unknown;
17
+ }
18
+
19
+ export interface FieldInfo {
20
+ name: string;
21
+ label: string;
22
+ apiKey: string;
23
+ type: string;
24
+ itemType: string;
25
+ checkitem: any[];
26
+ selectitem?: any[];
27
+ required: boolean;
28
+ }
29
+
30
+ function parseNumberForCompare(value: unknown): number | null {
31
+ if (value == null || value === '') return null;
32
+ const n = parseFloat(String(value).replace(/,/g, '').trim());
33
+ return Number.isFinite(n) ? n : null;
34
+ }
35
+
36
+ function parseCloseDateMs(value: unknown): number | null {
37
+ if (value == null || value === '') return null;
38
+ const m = moment(value);
39
+ return m.isValid() ? m.valueOf() : null;
40
+ }
41
+
42
+ type Trend = 'up' | 'down' | 'same';
43
+
44
+ function moneyTrend(
45
+ current: unknown,
46
+ history: HistoryOppSnap | null | undefined,
47
+ ): Trend | null {
48
+ if (!history) return null;
49
+ const cur = parseNumberForCompare(current);
50
+ const prev = parseNumberForCompare(history.money);
51
+ if (cur == null || prev == null) return null;
52
+ if (cur > prev) return 'up';
53
+ if (cur < prev) return 'down';
54
+ return 'same';
55
+ }
56
+
57
+ function closeDateTrend(
58
+ current: unknown,
59
+ history: HistoryOppSnap | null | undefined,
60
+ ): Trend | null {
61
+ if (!history) return null;
62
+ const cur = parseCloseDateMs(current);
63
+ const prev = parseCloseDateMs(history.closeDate);
64
+ if (cur == null || prev == null) return null;
65
+ if (cur > prev) return 'up';
66
+ if (cur < prev) return 'down';
67
+ return 'same';
68
+ }
69
+
70
+ /** Custom SQL return row: array column order matches HISTORY_QUERY_FIELDS; or object fields */
71
+ export function historyRowToSnap(row: unknown): HistoryOppSnap | null {
72
+ if (row == null) return null;
73
+ if (Array.isArray(row)) {
74
+ if (row.length < 5) return null;
75
+ return {
76
+ id: String(row[0] ?? '').trim(),
77
+ closeDate: row[1],
78
+ opportunityName: row[2] != null ? String(row[2]) : '',
79
+ saleStageId: row[3] != null ? String(row[3]) : '',
80
+ money: row[4],
81
+ };
82
+ }
83
+ if (typeof row === 'object') {
84
+ const o = row as Record<string, unknown>;
85
+ const id = o.id ?? o.opportunity_1_id;
86
+ if (id == null || id === '') return null;
87
+ return {
88
+ id: String(id).trim(),
89
+ closeDate:
90
+ o.opportunity_1_closeDate ?? o.closeDate ?? o.opportunity_1_close_date,
91
+ opportunityName: String(
92
+ o.opportunity_1_opportunityName ?? o.opportunityName ?? '',
93
+ ),
94
+ saleStageId: String(o.opportunity_1_saleStageId ?? o.saleStageId ?? ''),
95
+ money: o.opportunity_1_money ?? o.money,
96
+ };
97
+ }
98
+ return null;
99
+ }
100
+
101
+ export function buildHistoryMap(rows: unknown[]): Map<string, HistoryOppSnap> {
102
+ const map = new Map<string, HistoryOppSnap>();
103
+ for (const raw of rows) {
104
+ const snap = historyRowToSnap(raw);
105
+ if (snap?.id) map.set(snap.id, snap);
106
+ }
107
+ return map;
108
+ }
109
+
110
+ /** closeDate:YYYY-MM-DD */
111
+ export function formatCloseDate(value: unknown): string {
112
+ if (value == null || value === '') return '—';
113
+ const m = moment(value);
114
+ return m.isValid() ? m.format('YYYY-MM-DD') : String(value);
115
+ }
116
+
117
+ /** recentActivityRecordTime:YYYY-MM-DD HH:mm:ss */
118
+ export function formatActivityDateTime(value: unknown): string {
119
+ if (value == null || value === '') return '—';
120
+ const m = moment(value);
121
+ return m.isValid() ? m.format('YYYY-MM-DD HH:mm:ss') : String(value);
122
+ }
123
+
124
+ /** customItem246/247: change record time (ms/s timestamp or moment-parseable value) -> YYYY-MM-DD */
125
+ export function formatChangeRecordedAt(value: unknown): string {
126
+ if (value == null || value === '') return '—';
127
+ const n = toFiniteNumber(value);
128
+ if (n != null) {
129
+ const ms = n < 1e12 ? n * 1000 : n;
130
+ const m = moment(ms);
131
+ if (m.isValid()) return m.format('YYYY-MM-DD');
132
+ }
133
+ const m = moment(value);
134
+ return m.isValid() ? m.format('YYYY-MM-DD') : String(value);
135
+ }
136
+
137
+ /** Money column: $ prefix + thousands separator (no abbreviation) */
138
+ export function formatMoneyCell(value: unknown): string {
139
+ if (value == null || value === '') return '—';
140
+ const n = parseFloat(String(value).replace(/,/g, '').trim());
141
+ if (!Number.isFinite(n)) return String(value);
142
+ const part = Number.isInteger(n)
143
+ ? n.toLocaleString('en-US')
144
+ : n.toLocaleString('en-US', {
145
+ minimumFractionDigits: 0,
146
+ maximumFractionDigits: 2,
147
+ });
148
+ return `$${part}`;
149
+ }
150
+
151
+ /** oppHealthAssessmentLevel: numeric value -> health label (with colored background) */
152
+ export function renderOppHealthAssessmentLevel(
153
+ value: unknown,
154
+ ): React.ReactNode {
155
+ if (value == null || value === '') return '—';
156
+ const n = toFiniteNumber(value);
157
+ if (n === 5) {
158
+ return (
159
+ <span className="opp-health-tag opp-health-tag--green">Excellent</span>
160
+ );
161
+ }
162
+ if (n === 6) {
163
+ return (
164
+ <span className="opp-health-tag opp-health-tag--yellow">Health</span>
165
+ );
166
+ }
167
+ if (n === 7) {
168
+ return <span className="opp-health-tag opp-health-tag--red">Risk</span>;
169
+ }
170
+ if (n === 8) {
171
+ return <span className="opp-health-tag opp-health-tag--red">Problem</span>;
172
+ }
173
+ return '—';
174
+ }
175
+
176
+ /**
177
+ * Percentage column: appends %; API commonly returns 0-1 decimal or 0-100 numeric, both displayed in readable format
178
+ */
179
+ export function formatPercentCell(value: unknown): string {
180
+ if (value == null || value === '') return '—';
181
+ const raw = String(value).replace(/,/g, '').trim();
182
+ const n = parseFloat(raw.replace(/%/g, ''));
183
+ if (!Number.isFinite(n)) return String(value);
184
+ let pct = n;
185
+ if (n > 0 && n <= 1) {
186
+ pct = n * 100;
187
+ }
188
+ const text = Number.isInteger(pct)
189
+ ? String(pct)
190
+ : pct.toLocaleString('en-US', {
191
+ minimumFractionDigits: 0,
192
+ maximumFractionDigits: 2,
193
+ });
194
+ return `${text}%`;
195
+ }
196
+
197
+ export function moneyChangeTooltipTitle(
198
+ record: any,
199
+ current: unknown,
200
+ ): React.ReactNode {
201
+ const preset = record?.customItem246__c;
202
+ const h = record?.historyData as HistoryOppSnap | undefined;
203
+ if (!h) return null;
204
+ return (
205
+ <div className="simpleTable-change-tip">
206
+ <div className="simpleTable-change-tip__title">Amount</div>
207
+ <div className="simpleTable-change-tip__detail">
208
+ {formatMoneyCell(h.money)}
209
+ {' → '}
210
+ <span className="simpleTable-change-tip__to">
211
+ {formatMoneyCell(current)}
212
+ </span>
213
+ </div>
214
+ <div className="simpleTable-change-tip__meta">
215
+ Changed on {formatChangeRecordedAt(preset)}
216
+ </div>
217
+ </div>
218
+ );
219
+ }
220
+
221
+ export function closeDateChangeTooltipTitle(
222
+ record: any,
223
+ current: unknown,
224
+ ): React.ReactNode {
225
+ const preset = record?.customItem247__c;
226
+ const h = record?.historyData as HistoryOppSnap | undefined;
227
+ if (!h) return null;
228
+ return (
229
+ <div className="simpleTable-change-tip">
230
+ <div className="simpleTable-change-tip__title">Close Date</div>
231
+ <div className="simpleTable-change-tip__detail">
232
+ {formatCloseDate(h.closeDate)}
233
+ {' → '}
234
+ <span className="simpleTable-change-tip__to">
235
+ {formatCloseDate(current)}
236
+ </span>
237
+ </div>
238
+ <div className="simpleTable-change-tip__meta">
239
+ Changed on {formatChangeRecordedAt(preset)}
240
+ </div>
241
+ </div>
242
+ );
243
+ }
244
+
245
+ export function renderTrendCell(args: {
246
+ displayText: string;
247
+ trend: Trend | null;
248
+ tooltip: React.ReactNode;
249
+ }): React.ReactNode {
250
+ const { displayText, trend, tooltip } = args;
251
+ const showArrow = trend === 'up' || trend === 'down';
252
+ const arrow =
253
+ trend === 'up' ? (
254
+ <span className="simpleTable-trend simpleTable-trend--up">↑</span>
255
+ ) : trend === 'down' ? (
256
+ <span className="simpleTable-trend simpleTable-trend--down">↓</span>
257
+ ) : null;
258
+
259
+ return (
260
+ <span className="simpleTable-cell-trend-wrap">
261
+ <span className="simpleTable-cell-trend-wrap__text">{displayText}</span>
262
+ {showArrow && arrow ? (
263
+ <Popover
264
+ content={tooltip}
265
+ trigger="hover"
266
+ placement="top"
267
+ overlayClassName="simpleTable-change-popover"
268
+ mouseEnterDelay={0.08}
269
+ >
270
+ <span className="simpleTable-cell-trend-wrap__arrow">{arrow}</span>
271
+ </Popover>
272
+ ) : null}
273
+ </span>
274
+ );
275
+ }
276
+
277
+ /** demo.html winRateTooltip style */
278
+ export function renderWinRateHoverCard(record: any): React.ReactNode {
279
+ const baseline = record?.customItem244__c;
280
+ const positives = record?.customItem241__c;
281
+ const negatives = record?.customItem242__c;
282
+ const winRate = record?.customItem239__c;
283
+
284
+ const fmtLine = (v: unknown) => (v == null || v === '' ? '—' : String(v));
285
+
286
+ return (
287
+ <Card size="small" bordered={false} className="simpleTable-winrate-card">
288
+ <div className="simpleTable-winrate-card__baseline">
289
+ Baseline Probability{' '}
290
+ <span className="simpleTable-winrate-card__baseline-val">
291
+ {formatPercentCell(baseline)}
292
+ </span>
293
+ </div>
294
+ <hr className="simpleTable-winrate-card__hr" />
295
+ <div className="simpleTable-winrate-card__section-title">
296
+ Positive Factors
297
+ </div>
298
+ <div className="simpleTable-winrate-card__positives">
299
+ {fmtLine(positives)}
300
+ </div>
301
+ <hr className="simpleTable-winrate-card__hr" />
302
+ <div className="simpleTable-winrate-card__section-title">
303
+ Negative Factors
304
+ </div>
305
+ <div className="simpleTable-winrate-card__negatives">
306
+ {fmtLine(negatives)}
307
+ </div>
308
+ <hr className="simpleTable-winrate-card__hr" />
309
+ <div className="simpleTable-winrate-card__footer">
310
+ <span>AI Win Rate</span>
311
+ <span className="simpleTable-winrate-card__winrate-val">
312
+ {formatPercentCell(winRate)}
313
+ </span>
314
+ </div>
315
+ </Card>
316
+ );
317
+ }
318
+
319
+ /** Summary amount display (number format consistent with formatMoneyCell) */
320
+ export function formatMoneySummaryAmount(n: number): string {
321
+ const part = Number.isInteger(n)
322
+ ? n.toLocaleString('en-US')
323
+ : n.toLocaleString('en-US', {
324
+ minimumFractionDigits: 0,
325
+ maximumFractionDigits: 2,
326
+ });
327
+ return `$${part}`;
328
+ }
329
+
330
+ /** Sum row amounts by field apiKey (parsing rules consistent with money column) */
331
+ export function sumMoneyRows(rows: any[], moneyFieldKey: string): number {
332
+ let total = 0;
333
+ for (const row of rows) {
334
+ const v = row?.[moneyFieldKey];
335
+ if (v == null || v === '') continue;
336
+ const n = parseFloat(String(v).replace(/,/g, '').trim());
337
+ if (Number.isFinite(n)) total += n;
338
+ }
339
+ return total;
340
+ }
341
+
342
+ export function isMoneyField(field: FieldInfo): boolean {
343
+ const k = field.apiKey?.toLowerCase?.() ?? '';
344
+ if (k === 'money' || k === 'amount') return true;
345
+ if (field.type === 'currency' || field.itemType === 'currency') return true;
346
+ return false;
347
+ }
348
+
349
+ export { moneyTrend, closeDateTrend };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Stage switch stageSwitch specific (opportunity row field parsing)
3
+ */
4
+ import { parseNumberOrZero } from './common';
5
+
6
+ export function rowOpportunityStage(row: Record<string, unknown>): string {
7
+ return String(row.customItem248__c ?? '');
8
+ }
9
+
10
+ export function rowMoney(row: Record<string, unknown>): number {
11
+ if (row.money !== undefined && row.money !== null && row.money !== '') {
12
+ return parseNumberOrZero(row.money);
13
+ }
14
+ return parseNumberOrZero(row.amount);
15
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Stage time stageTimeChart specific
3
+ */
4
+ import { extractStageKeyFromStageName } from './common';
5
+
6
+ const COLOR_GREEN = '#22c55e';
7
+ const COLOR_YELLOW = '#eab308';
8
+ const COLOR_RED = '#ef4444';
9
+ const COLOR_NEUTRAL = '#94a3b8';
10
+
11
+ export interface StageTimeRow {
12
+ stageName: string;
13
+ actualTime: number;
14
+ actualTimeDisplay: string;
15
+ targetPercent: number;
16
+ limitPercent: number;
17
+ }
18
+
19
+ export interface StageTimeItem {
20
+ stageName: string;
21
+ actualTime: string;
22
+ actualPercent: number;
23
+ actualColor: string;
24
+ barActualWidthPct: number;
25
+ barTargetLeftPct: number;
26
+ barLimitLeftPct: number;
27
+ }
28
+
29
+ /**
30
+ * Convert duration text like "16 Days 11 Hours" or "8天6时" to days (float), e.g. 16 + 11/24
31
+ */
32
+ export function parseDurationToDays(text: unknown): number {
33
+ const s = String(text ?? '').trim();
34
+ if (!s) return 0;
35
+ let days = 0;
36
+ let hours = 0;
37
+ const dayMatch = s.match(/(\d+(?:\.\d+)?)\s*(天|Day)/);
38
+ if (dayMatch) days = parseFloat(dayMatch[1]) || 0;
39
+ const hourMatch = s.match(/(\d+(?:\.\d+)?)\s*(?:小时|时|Hour)/);
40
+ if (hourMatch) hours = parseFloat(hourMatch[1]) || 0;
41
+ return days + hours / 24;
42
+ }
43
+
44
+ /** Final/closed stages are excluded from "stage time" display */
45
+ export function isClosedOutcomeStage(stageName: string): boolean {
46
+ const key = extractStageKeyFromStageName(stageName).toLowerCase();
47
+ return key === 'closed won' || key === 'closed lost';
48
+ }
49
+
50
+ /** Color: compare actual days with customItem3 (target) / customItem4 (limit) */
51
+ export function resolveActualColor(
52
+ actualDays: number,
53
+ target: number,
54
+ limit: number,
55
+ ): string {
56
+ if (actualDays <= target) return COLOR_GREEN;
57
+ if (actualDays > limit) return COLOR_RED;
58
+ return COLOR_YELLOW;
59
+ }
60
+
61
+ /** Convert parsed rows to chart bar data (color and bar proportions) */
62
+ export function buildStageTimeItems(rows: StageTimeRow[]): StageTimeItem[] {
63
+ return rows.map((row) => {
64
+ const actualPercent = row.actualTime;
65
+ const hasTh =
66
+ Number.isFinite(row.targetPercent) && Number.isFinite(row.limitPercent);
67
+ let targetNum = row.targetPercent;
68
+ let limitNum = row.limitPercent;
69
+ let actualColor: string;
70
+ const scaleMax = 60;
71
+
72
+ if (hasTh) {
73
+ actualColor = resolveActualColor(actualPercent, targetNum, limitNum);
74
+ } else {
75
+ targetNum = 0;
76
+ limitNum = 0;
77
+ actualColor = COLOR_NEUTRAL;
78
+ }
79
+
80
+ return {
81
+ stageName: row.stageName,
82
+ actualTime: row.actualTimeDisplay,
83
+ actualPercent,
84
+ actualColor,
85
+ barActualWidthPct: Math.min(100, (actualPercent / scaleMax) * 100),
86
+ barTargetLeftPct: hasTh ? (targetNum / scaleMax) * 100 : 0,
87
+ barLimitLeftPct: hasTh ? (limitNum / scaleMax) * 100 : 0,
88
+ };
89
+ });
90
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Numeric indicator targetNumber: style helpers
3
+ */
4
+
5
+ /** margin/padding string: appends unit when no unit present, returns as-is if px/rem/etc already exists */
6
+ export function formatCssSpacing(value: string, unit: string): string {
7
+ if (!value || value === '0') return '0';
8
+ if (/\d+(px|rem|em|%)$/.test(value.trim())) {
9
+ return value;
10
+ }
11
+ return `${value}${unit}`;
12
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "experimentalDecorators": true,
4
+ "target": "esnext",
5
+ "module": "esnext",
6
+ "allowJs": false,
7
+ "jsx": "react", // preserve
8
+ "declaration": false,
9
+ "noEmit": false,
10
+ "importHelpers": true,
11
+ "isolatedModules": false,
12
+ "strict": false,
13
+ "noImplicitAny": true,
14
+ "strictNullChecks": true,
15
+ "noImplicitThis": true,
16
+ "noUnusedLocals": false,
17
+ "noImplicitReturns": true,
18
+ "moduleResolution": "node",
19
+ "baseUrl": "./",
20
+ "paths": {
21
+ "@": [
22
+ "./src"
23
+ ]
24
+ },
25
+ "typeRoots": [
26
+ "./@types",
27
+ "./node_modules/@types"
28
+ ],
29
+ "allowSyntheticDefaultImports": true,
30
+ "esModuleInterop": true,
31
+ "forceConsistentCasingInFileNames": true
32
+ },
33
+ "include": [
34
+ "src",
35
+ "test"
36
+ ],
37
+ "exclude": [
38
+ "node_modules"
39
+ ]
40
+ }
@@ -37,7 +37,7 @@
37
37
  "antd": "^4.9.4",
38
38
  "axios": "^1.7.0",
39
39
  "lodash": "^4.17.23",
40
- "neo-open-api": "^1.2.3",
40
+ "neo-open-api": "^1.2.7",
41
41
  "neo-register": "^1.2.0",
42
42
  "react": "^16.9.0",
43
43
  "react-dom": "^16.9.0",
@@ -52,7 +52,7 @@
52
52
  "@types/react-dom": "^16.9.15",
53
53
  "husky": "^4.2.5",
54
54
  "lint-staged": "^10.2.9",
55
- "neo-cmp-cli": "^1.13.16",
55
+ "neo-cmp-cli": "^1.13.17",
56
56
  "prettier": "^2.0.5"
57
57
  },
58
58
  "overrides": {
@@ -37,7 +37,7 @@
37
37
  "antd": "^4.9.4",
38
38
  "axios": "^1.7.0",
39
39
  "lodash": "^4.17.23",
40
- "neo-open-api": "^1.2.3",
40
+ "neo-open-api": "^1.2.7",
41
41
  "neo-register": "^1.2.0",
42
42
  "react": "^16.9.0",
43
43
  "react-dom": "^16.9.0",
@@ -53,7 +53,7 @@
53
53
  "@types/react-dom": "^16.9.15",
54
54
  "husky": "^4.2.5",
55
55
  "lint-staged": "^10.2.9",
56
- "neo-cmp-cli": "^1.13.16",
56
+ "neo-cmp-cli": "^1.13.18",
57
57
  "prettier": "^2.0.5"
58
58
  },
59
59
  "overrides": {