feat: CRM-统计: 过程指标
parent
cc3d0c19ad
commit
e5ff51ae63
@ -1,85 +0,0 @@
|
||||
import React, { Children, useContext, useState } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { stores_Context } from '../config';
|
||||
import moment from 'moment';
|
||||
import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined } from '@ant-design/icons';
|
||||
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd';
|
||||
import SearchForm from './../components/search/SearchForm';
|
||||
|
||||
export default observer((props) => {
|
||||
const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context);
|
||||
|
||||
const { formValues, siderBroken } = searchFormStore;
|
||||
|
||||
const activityTableProps = {
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'op', key: 'op' }, // 维度: 顾问
|
||||
{
|
||||
title: '顾问动作',
|
||||
key: 'date',
|
||||
children: [
|
||||
{ title: '首次响应率24H', dataIndex: 'action', key: 'action' },
|
||||
{ title: '48H内报价率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '一次报价率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '二次报价率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '>50条会话', dataIndex: 'action', key: 'action' },
|
||||
{ title: '违规数', dataIndex: 'action', key: 'action' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '客人回复',
|
||||
key: 'department',
|
||||
children: [
|
||||
{ title: '首次回复率24H', dataIndex: 'action', key: 'action' },
|
||||
{ title: '48H内报价回复率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '一次报价回复率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '二次报价回复率', dataIndex: 'action', key: 'action' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const riskTableProps = {
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'date', key: 'date' }, // 维度
|
||||
{ title: '>24H回复', dataIndex: 'action', key: 'action' },
|
||||
{ title: '首次报价周期>48h', dataIndex: 'action', key: 'action' },
|
||||
{ title: '报价次数<1次', dataIndex: 'action' },
|
||||
{ title: '报价次数<2次', dataIndex: 'action' },
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
|
||||
<Col md={24} lg={24} xxl={24}>
|
||||
<SearchForm
|
||||
defaultValue={{
|
||||
initialValue: {
|
||||
...formValues,
|
||||
...sale_store.searchValues,
|
||||
},
|
||||
shows: ['DepartmentList', 'WebCode', 'dates'],
|
||||
fieldProps: {
|
||||
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
|
||||
WebCode: { show_all: false, mode: 'multiple', col: 5 },
|
||||
dates: { hide_vs: true },
|
||||
},
|
||||
}}
|
||||
onSubmit={(_err, obj, form, str) => {
|
||||
sale_store.setSearchValues(obj, form);
|
||||
// pageRefresh(obj);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<section>
|
||||
<Table {...activityTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>未成行订单</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
@ -0,0 +1,186 @@
|
||||
import React, { Children, useContext, useState } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { stores_Context } from '../config';
|
||||
import moment from 'moment';
|
||||
import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined } from '@ant-design/icons';
|
||||
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd';
|
||||
import SearchForm from '../components/search/SearchForm';
|
||||
import { cloneDeep, fixTo2Decimals, sortBy, isEmpty, pick } from '../utils/commons';
|
||||
|
||||
const COLOR_SETS = [
|
||||
"#FFFFFF",
|
||||
// "#5B8FF9",
|
||||
// "#FF6B3B",
|
||||
"#9FB40F",
|
||||
"#76523B",
|
||||
"#DAD5B5",
|
||||
"#E19348",
|
||||
"#F383A2",
|
||||
];
|
||||
|
||||
const transparentHex = '1A';
|
||||
|
||||
export default observer((props) => {
|
||||
const { SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
|
||||
|
||||
// const { formValues, siderBroken } = searchFormStore;
|
||||
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
|
||||
const _formValuesToSub = pick(formValuesToSub, ['DepartmentList', 'WebCode']);
|
||||
const { resetData, process } = SalesCRMDataStore;
|
||||
const operatorObjects = process.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel }));
|
||||
|
||||
const pageRefresh = async (obj) => {
|
||||
resetData();
|
||||
Promise.allSettled([
|
||||
SalesCRMDataStore.getProcessData({
|
||||
...(obj || _formValuesToSub),
|
||||
// ...subParam,
|
||||
// groupType: 'overview',
|
||||
groupType: 'dept',
|
||||
groupDateType: '',
|
||||
}),
|
||||
SalesCRMDataStore.getProcessData({
|
||||
...(obj || _formValuesToSub),
|
||||
// ...subParam,
|
||||
groupType: 'operator',
|
||||
groupDateType: '',
|
||||
}),
|
||||
]);
|
||||
};
|
||||
|
||||
const tableSorter = (a, b, colName) => (a.groupType !== 'operator' ? -1 : b.groupType !== 'operator' ? 0 : a[colName] - b[colName]);
|
||||
const percentageRender = val => val ? `${val}%` : '';
|
||||
|
||||
const activityTableProps = {
|
||||
rowKey: 'groupsKey',
|
||||
pagination: { pageSize: 10, showSizeChanger: false },
|
||||
size: 'small',
|
||||
rowClassName: (record, rowIndex) => {
|
||||
return record.groupType === 'operator' ? '': 'ant-tag-blue';
|
||||
},
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem',
|
||||
filterSearch: true,
|
||||
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
|
||||
}, // 维度: 顾问
|
||||
{
|
||||
title: '顾问动作',
|
||||
key: 'date',
|
||||
children: [
|
||||
{ title: '首次响应率24H', dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender,
|
||||
sorter: (a, b) => tableSorter(a, b, 'firstTouch24'),
|
||||
},
|
||||
{ title: '48H内报价率', dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'firstQuote48'), },
|
||||
{ title: '一次报价率', dataIndex: 'quote1', key: 'quote1',render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'quote1'), },
|
||||
{ title: '二次报价率', dataIndex: 'quote2', key: 'quote2',render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'quote2'), },
|
||||
{ title: '>50条会话', dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'turnsGT50'), },
|
||||
{ title: '违规数', dataIndex: 'violations', key: 'violations',
|
||||
sorter: (a, b) => tableSorter(a, b, 'violations'), },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '客人回复',
|
||||
key: 'department',
|
||||
children: [
|
||||
{
|
||||
title: '首次回复率24H', dataIndex: 'firstReply24', key: 'firstReply24',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'firstReply24'), },
|
||||
{
|
||||
title: '48H内报价回复率', dataIndex: 'replyQuote48', key: 'replyQuote48',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'replyQuote48'), },
|
||||
{
|
||||
title: '一次报价回复率', dataIndex: 'replyQuote1', key: 'replyQuote1',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'replyQuote1'), },
|
||||
{
|
||||
title: '二次报价回复率', dataIndex: 'replyQuote2', key: 'replyQuote2',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'replyQuote2'), },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const riskTableProps = {
|
||||
rowKey: 'groupsKey',
|
||||
pagination: { pageSize: 10, showSizeChanger: false },
|
||||
size: 'small',
|
||||
rowClassName: (record, rowIndex) => {
|
||||
return record.groupType === 'operator' ? '': 'ant-tag-blue';
|
||||
},
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem',
|
||||
filterSearch: true,
|
||||
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
|
||||
// render: (text, record) => { // todo: 点击查看不成行订单明细
|
||||
// },
|
||||
}, // 维度: 顾问
|
||||
{ title: '>24H回复', dataIndex: 'lostTouch24', key: 'lostTouch24',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostTouch24'),
|
||||
},
|
||||
{ title: '首次报价周期>48h', dataIndex: 'lostQuote48', key: 'lostQuote48',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostQuote48'),
|
||||
},
|
||||
{ title: '报价次数<1次', dataIndex: 'lostQuote1', key: 'lostQuote1',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostQuote1'),
|
||||
},
|
||||
{ title: '报价次数<2次', dataIndex: 'lostQuote2', key: 'lostQuote2',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostQuote2'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
|
||||
<Col md={24} lg={24} xxl={24}>
|
||||
<SearchForm
|
||||
defaultValue={{
|
||||
initialValue: {
|
||||
...formValues,
|
||||
...SalesCRMDataStore.searchValues,
|
||||
},
|
||||
shows: ['DepartmentList', 'WebCode', 'DateType', 'dates'],
|
||||
fieldProps: {
|
||||
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
|
||||
WebCode: { show_all: false, mode: 'multiple', col: 5 },
|
||||
dates: { hide_vs: true },
|
||||
},
|
||||
}}
|
||||
onSubmit={(_err, obj, form, str) => {
|
||||
SalesCRMDataStore.setSearchValues(obj, form);
|
||||
pageRefresh(obj);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<section>
|
||||
<Table {...activityTableProps} bordered dataSource={[...process.dataSource, ...process.details]} loading={process.loading} sticky />
|
||||
</section>
|
||||
<section>
|
||||
<h3>未成行订单 数量</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={[...process.dataSource, ...process.details]} loading={process.loading} sticky />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue