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.
- package/README.md +2 -1
- package/dist/index2.js +1 -1
- package/dist/main2.js +1 -1
- package/dist/neo/env.js +1 -1
- package/dist/neo/neoLogin.js +1 -1
- package/dist/neo/pushCmp.js +1 -1
- package/dist/package.json.js +1 -1
- package/package.json +3 -2
- package/template/antd-custom-cmp-template/package.json +1 -1
- package/template/asset-manage-template/docs/README.md +1 -232
- package/template/asset-manage-template/package.json +2 -2
- package/template/echarts-custom-cmp-template/package.json +1 -1
- package/template/empty-custom-cmp-template/package.json +2 -2
- package/template/map-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/neo.config.js +7 -1
- package/template/neo-bi-cmps/package.json +8 -7
- package/template/neo-bi-cmps/public/403.html +77 -0
- package/template/neo-bi-cmps/public/demo.html +2453 -0
- package/template/neo-bi-cmps/src/assets/icon/barChart.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/card.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/filter.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/funnel.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/tab.svg +1 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/README.md +3 -14
- package/template/neo-bi-cmps/src/components/filterBar__c/common.scss +29 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +668 -146
- package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +26 -48
- package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +46 -139
- package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +11 -10
- package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +9 -16
- package/template/neo-bi-cmps/src/components/targetNumber__c/model.ts +1 -1
- package/template/neo-bi-cmps/src/utils/common.ts +231 -0
- package/template/neo-bi-cmps/src/utils/filter2chartFilter.ts +268 -0
- package/template/neo-bi-cmps/src/utils/filterBar.ts +140 -0
- package/template/neo-bi-cmps/src/utils/pipelineFunnel.ts +341 -0
- package/template/{neo-h5-cmps/src/utils/queryObjectData.ts → neo-bi-cmps/src/utils/queryByCustomSQL.ts} +18 -13
- package/template/neo-bi-cmps/src/utils/requestDebounce.ts +22 -0
- package/template/neo-bi-cmps/src/utils/simpleTable.tsx +344 -0
- package/template/neo-bi-cmps/src/utils/stageSwitch.ts +15 -0
- package/template/neo-bi-cmps/src/utils/stageTimeChart.ts +90 -0
- package/template/neo-bi-cmps/src/utils/targetNumber.ts +12 -0
- package/template/neo-custom-cmp-template/docs/README.md +0 -231
- package/template/neo-custom-cmp-template/package.json +2 -2
- package/template/neo-h5-cmps/package.json +2 -2
- package/template/neo-h5-cmps/src/components/entityList__c/index.tsx +1 -2
- package/template/neo-h5-cmps/src/components/entityTabs__c/index.tsx +1 -1
- package/template/neo-h5-cmps/src/components/globalSearchInput__c/index.tsx +1 -1
- package/template/neo-h5-cmps/src/components/openChatPageBtn__c/index.tsx +1 -2
- package/template/neo-order-cmps/package.json +2 -2
- package/template/neo-pipeline-cmps/.prettierrc.js +12 -0
- package/template/neo-pipeline-cmps/@types/neo-ui-common.d.ts +36 -0
- package/template/neo-pipeline-cmps/README.md +99 -0
- package/template/neo-pipeline-cmps/commitlint.config.js +59 -0
- package/template/neo-pipeline-cmps/neo.config.js +135 -0
- package/template/neo-pipeline-cmps/package.json +66 -0
- package/template/neo-pipeline-cmps/public/403.html +77 -0
- package/template/neo-pipeline-cmps/public/css/base.css +283 -0
- package/template/neo-pipeline-cmps/public/demo.html +2453 -0
- package/template/neo-pipeline-cmps/public/scripts/app/bluebird.js +6679 -0
- package/template/neo-pipeline-cmps/public/template.html +13 -0
- package/template/neo-pipeline-cmps/src/assets/css/common.scss +127 -0
- package/template/neo-pipeline-cmps/src/assets/css/mixin.scss +47 -0
- package/template/neo-pipeline-cmps/src/assets/icon/barChart.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/card.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/filter.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/funnel.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/tab.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/AIBtn.gif +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/NeoCRM.jpg +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/aiLogo.png +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/card-list.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/contact-form.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/custom-form.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/custom-widget.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/data-list.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/detail.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/favicon.png +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/map.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/search.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/table.svg +1 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/README.md +24 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/common.scss +29 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/index.tsx +731 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/model.ts +52 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/style.scss +119 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/README.md +39 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/index.tsx +416 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/model.ts +80 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/style.scss +83 -0
- package/template/neo-pipeline-cmps/src/components/showHealthResult__c/index.tsx +470 -0
- package/template/neo-pipeline-cmps/src/components/showHealthResult__c/model.ts +45 -0
- package/template/neo-pipeline-cmps/src/components/showHealthResult__c/style.scss +137 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/README.md +89 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/common.scss +195 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/index.tsx +667 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/model.ts +124 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/style.scss +192 -0
- package/template/neo-pipeline-cmps/src/components/stageSwitch__c/README.md +36 -0
- package/template/neo-pipeline-cmps/src/components/stageSwitch__c/index.tsx +513 -0
- package/template/neo-pipeline-cmps/src/components/stageSwitch__c/model.ts +71 -0
- package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/style.scss +4 -2
- package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/README.md +37 -0
- package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/index.tsx +455 -0
- package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/model.ts +106 -0
- package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss +3 -2
- package/template/neo-pipeline-cmps/src/utils/common.ts +229 -0
- package/template/neo-pipeline-cmps/src/utils/filter2chartFilter.ts +266 -0
- package/template/neo-pipeline-cmps/src/utils/filterBar.ts +140 -0
- package/template/neo-pipeline-cmps/src/utils/pipelineFunnel.ts +343 -0
- package/template/neo-pipeline-cmps/src/utils/queryByCustomSQL.ts +121 -0
- package/template/neo-pipeline-cmps/src/utils/requestDebounce.ts +22 -0
- package/template/neo-pipeline-cmps/src/utils/simpleTable.tsx +349 -0
- package/template/neo-pipeline-cmps/src/utils/stageSwitch.ts +15 -0
- package/template/neo-pipeline-cmps/src/utils/stageTimeChart.ts +90 -0
- package/template/neo-pipeline-cmps/src/utils/targetNumber.ts +12 -0
- package/template/neo-pipeline-cmps/tsconfig.json +40 -0
- package/template/neo-web-entity-grid/package.json +2 -2
- package/template/neo-web-form/package.json +2 -2
- package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +161 -41
- package/template/neo-web-form/src/components/batchAddTable__c/model.ts +4 -2
- package/template/react-custom-cmp-template/package.json +1 -1
- package/template/react-ts-custom-cmp-template/package.json +1 -1
- package/template/vue2-custom-cmp-template/package.json +1 -1
- package/template/asset-manage-template/src/utils/axiosFetcher.ts +0 -37
- package/template/asset-manage-template/src/utils/queryObjectData.ts +0 -112
- package/template/asset-manage-template/src/utils/xobjects.ts +0 -162
- package/template/neo-bi-cmps/.npmrc copy +0 -1
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +0 -52
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +0 -183
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +0 -90
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +0 -218
- package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +0 -31
- package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +0 -158
- package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +0 -40
- package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +0 -154
- package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +0 -36
- package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +0 -86
- package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +0 -62
- package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +0 -48
- package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +0 -24
- package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +0 -100
- package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +0 -46
- package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +0 -60
- package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +0 -35
- package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +0 -70
- package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +0 -50
- package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +0 -33
- package/template/neo-bi-cmps/src/components/oppList__c/README.md +0 -52
- package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +0 -285
- package/template/neo-bi-cmps/src/components/oppList__c/model.ts +0 -86
- package/template/neo-bi-cmps/src/components/oppList__c/style.scss +0 -133
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/README.md +0 -39
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +0 -130
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +0 -66
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +0 -133
- package/template/neo-bi-cmps/src/components/stageSwitch__c/README.md +0 -36
- package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +0 -118
- package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +0 -92
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/README.md +0 -37
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +0 -126
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +0 -57
- package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +0 -37
- package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +0 -80
- package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +0 -45
- package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +0 -37
- package/template/neo-bi-cmps/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-bi-cmps/src/utils/queryObjectData.ts +0 -76
- package/template/neo-bi-cmps/src/utils/xobjects.ts +0 -162
- package/template/neo-custom-cmp-template/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-custom-cmp-template/src/utils/queryObjectData.ts +0 -112
- package/template/neo-custom-cmp-template/src/utils/xobjects.ts +0 -162
- package/template/neo-h5-cmps/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-h5-cmps/src/utils/xobjects.ts +0 -167
- package/template/neo-order-cmps/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-order-cmps/src/utils/queryObjectData.ts +0 -112
- package/template/neo-order-cmps/src/utils/xobjects.ts +0 -162
- package/template/neo-web-entity-grid/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-web-entity-grid/src/utils/queryObjectData.ts +0 -112
- package/template/neo-web-entity-grid/src/utils/xobjects.ts +0 -167
- package/template/neo-web-form/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-web-form/src/utils/queryObjectData.ts +0 -112
- 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;
|
package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
.
|
|
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;
|