fear: CRM统计: 个人看板; 违规明细
parent
ccbd0be1cd
commit
a02fa37f17
@ -1,69 +0,0 @@
|
||||
import React, { 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 riskTableProps = {
|
||||
loading: false,
|
||||
// sticky: true,
|
||||
// scroll: { x: 1000, y: 400 },
|
||||
pagination: false,
|
||||
columns: [
|
||||
{ title: '客人姓名', dataIndex: 'WebCode', key: 'WebCode' },
|
||||
{ title: '团号', dataIndex: 'o_id', key: 'o_id' },
|
||||
{ title: '表单内容', dataIndex: 'COLI_LineClass', key: 'COLI_LineClass' },
|
||||
{ title: '顾问', dataIndex: 'SourceType', key: 'SourceType' },
|
||||
{ title: '预定时间', dataIndex: 'applyDate', key: 'applyDate' },
|
||||
],
|
||||
};
|
||||
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>
|
||||
<h3>>24H回复</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>首次报价周期>48h</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<1</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<2</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
@ -0,0 +1,397 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { NavLink, useParams } from 'react-router-dom';
|
||||
import { observer } from 'mobx-react';
|
||||
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../../config';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Row, Col, Table, Divider, Button, Popover, Tooltip } from 'antd';
|
||||
import { fixTo2Decimals } from '../../utils/commons';
|
||||
import MixFieldsDetail from '../../components/MixFieldsDetail';
|
||||
|
||||
const COLOR_SETS = [
|
||||
'#FFFFFF',
|
||||
// "#5B8FF9",
|
||||
// "#FF6B3B",
|
||||
'#9FB40F',
|
||||
'#76523B',
|
||||
'#DAD5B5',
|
||||
'#E19348',
|
||||
'#F383A2',
|
||||
];
|
||||
const transparentHex = '1A';
|
||||
const percentageRender = (val) => (val ? `${val}%` : '');
|
||||
const numberConvert10K = (number, scale = 10) => {
|
||||
return fixTo2Decimals((number/(1000*scale)));
|
||||
};
|
||||
const retPropsMinRatesSet = { 'CH': 12, 'AH': 8, 'default': 10 }; //
|
||||
|
||||
export default observer((props) => {
|
||||
const { opisn, opi_name } = useParams();
|
||||
|
||||
const { sale_store, SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
|
||||
|
||||
const { formValues, siderBroken } = searchFormStore;
|
||||
const { searchValues, searchValuesToSub, resetData, results, process, risk } = SalesCRMDataStore;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Promise.allSettled([
|
||||
// 结果指标
|
||||
SalesCRMDataStore.get90n180Data({ opisn, groupType: 'operator', groupDateType: '' }),
|
||||
// 结果: 全年业绩
|
||||
SalesCRMDataStore.getResultData({
|
||||
opisn,
|
||||
Date1: searchValues.date.clone().startOf('year').format(DATE_FORMAT),
|
||||
Date2: searchValues.date.clone().endOf('year').format(SMALL_DATETIME_FORMAT),
|
||||
groupType: 'overview',
|
||||
// groupType: 'operator',
|
||||
groupDateType: 'month',
|
||||
}),
|
||||
// 过程
|
||||
SalesCRMDataStore.getProcessData({ opisn, groupType: 'operator', groupDateType: '' }),
|
||||
// 违规明细
|
||||
SalesCRMDataStore.getRiskDetailData({ opisn }),
|
||||
]);
|
||||
}, [opisn]);
|
||||
|
||||
const dataFields = (suffix, colRootIndex) => [
|
||||
{
|
||||
key: 'ConfirmRates' + suffix,
|
||||
title: '成行率',
|
||||
dataIndex: [suffix, 'ConfirmRates'],
|
||||
width: '5em',
|
||||
// CH个人成行率<12%, AH个人成行率<8%刷红
|
||||
render: (val, r) => ({
|
||||
props: { style: { color: val < (retPropsMinRatesSet?.[r?.retProps || 'default'] || retPropsMinRatesSet.default) ? 'red' : 'green', backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
|
||||
children: val ? `${val}%` : '',
|
||||
}),
|
||||
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmRates - b[suffix].ConfirmRates),
|
||||
},
|
||||
{
|
||||
key: 'SumML' + suffix,
|
||||
title: '业绩/万',
|
||||
dataIndex: [suffix, 'SumML'],
|
||||
width: '5em',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
|
||||
children: numberConvert10K(_),
|
||||
}),
|
||||
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumML - b[suffix].SumML),
|
||||
},
|
||||
{
|
||||
key: 'ConfirmOrder' + suffix,
|
||||
title: '团数',
|
||||
dataIndex: [suffix, 'ConfirmOrder'],
|
||||
width: '5em',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
|
||||
children: _,
|
||||
}),
|
||||
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmOrder - b[suffix].ConfirmOrder),
|
||||
},
|
||||
{
|
||||
key: 'SumOrder' + suffix,
|
||||
title: '订单数',
|
||||
dataIndex: [suffix, 'SumOrder'],
|
||||
width: '5em',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
|
||||
children: _,
|
||||
}),
|
||||
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumOrder - b[suffix].SumOrder),
|
||||
},
|
||||
{
|
||||
key: 'ResumeOrder' + suffix,
|
||||
title: '老客户团数',
|
||||
dataIndex: [suffix, 'ResumeOrder'],
|
||||
width: '5em',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
|
||||
children: _,
|
||||
}),
|
||||
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeOrder - b[suffix].ResumeOrder),
|
||||
},
|
||||
{
|
||||
key: 'ResumeRates' + suffix,
|
||||
title: '老客户成行率',
|
||||
dataIndex: [suffix, 'ResumeRates'],
|
||||
width: '5em',
|
||||
render: (val, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
|
||||
children: val ? `${val}%` : ''
|
||||
}),
|
||||
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeRates - b[suffix].ResumeRates),
|
||||
},
|
||||
];
|
||||
const dashboardTableProps = {
|
||||
rowKey: 'groupsKey',
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
showSorterTooltip: false,
|
||||
columns: [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'groupsLabel',
|
||||
key: 'name',
|
||||
width: '5em',
|
||||
},
|
||||
{
|
||||
title: () => (<>前90 -30天<br/>{searchValues.date90.Date1} 至 {searchValues.date90.Date2}</>),
|
||||
key: 'date',
|
||||
children: dataFields('result90', 0),
|
||||
},
|
||||
{
|
||||
title: () => (<>前180 -50天<br/>{searchValues.date180.Date1} 至 {searchValues.date180.Date2}</>),
|
||||
key: 'department',
|
||||
children: dataFields('result180', 1),
|
||||
},
|
||||
],
|
||||
rowClassName: (record, rowIndex) => {
|
||||
return record.groupType === 'overview' ? 'ant-tag-blue' : '';
|
||||
},
|
||||
};
|
||||
const activityTableProps = {
|
||||
rowKey: 'groupsKey',
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
rowClassName: (record, rowIndex) => {
|
||||
return record.groupType === 'operator' ? '' : 'ant-tag-blue';
|
||||
},
|
||||
showSorterTooltip: false,
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem' }, // 维度: 顾问
|
||||
{
|
||||
title: '顾问动作',
|
||||
key: 'date',
|
||||
children: [
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
首次响应率24H{' '}
|
||||
<Tooltip title="达到24H内回复的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'firstTouch24',
|
||||
key: 'firstTouch24',
|
||||
render: percentageRender,
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
48H内报价率{' '}
|
||||
<Tooltip title="达到48H内报价的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'firstQuote48',
|
||||
key: 'firstQuote48',
|
||||
render: percentageRender,
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
一次报价率{' '}
|
||||
<Tooltip title="首次报价的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'quote1',
|
||||
key: 'quote1',
|
||||
render: percentageRender,
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
二次报价率{' '}
|
||||
<Tooltip title="二次报价的订单数/一次报价后回复数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'quote2',
|
||||
key: 'quote2',
|
||||
render: percentageRender,
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
>50条会话{' '}
|
||||
<Tooltip title=">50条会话的订单数/总订单">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'turnsGT50',
|
||||
key: 'turnsGT50',
|
||||
render: percentageRender,
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
违规数{' '}
|
||||
<Tooltip title="未遵循24H回复的订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'violations',
|
||||
key: 'violations',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '客人回复',
|
||||
key: 'department',
|
||||
children: [
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
首次回复率24H{' '}
|
||||
<Tooltip title="达到24H内回复的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'firstReply24',
|
||||
key: 'firstReply24',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
48H内报价回复率{' '}
|
||||
<Tooltip title="48H内报价后的回复数/报价的订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'replyQuote48',
|
||||
key: 'replyQuote48',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
一次报价回复率{' '}
|
||||
<Tooltip title="一次报价后回复率/订单总数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'replyQuote1',
|
||||
key: 'replyQuote1',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
二次报价回复率{' '}
|
||||
<Tooltip title="二次报价后回复数/一次报价后回复数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
dataIndex: 'replyQuote2',
|
||||
key: 'replyQuote2',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const riskTableProps = {
|
||||
loading: risk.loading,
|
||||
sticky: true,
|
||||
// scroll: { x: 1000, y: 400 },
|
||||
pagination: false,
|
||||
rowKey: (row) => row.coli_id,
|
||||
columns: [
|
||||
{ title: '客人姓名', dataIndex: 'guest_name', key: 'guest_name', width: '6rem' },
|
||||
{ title: '团号', dataIndex: 'coli_id', key: 'coli_id', width: '6rem' },
|
||||
{
|
||||
title: '表单内容',
|
||||
dataIndex: 'coli_contents',
|
||||
key: 'coli_contents',
|
||||
width: '6rem',
|
||||
render: (text, record) => (
|
||||
<Popover
|
||||
title={`${record.coli_id} ${record.guest_name}`}
|
||||
content={<pre dangerouslySetInnerHTML={{ __html: text }} style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word' }} />}
|
||||
trigger={['click']}
|
||||
placement="right"
|
||||
overlayStyle={{ width: '500px', maxHeight: '500px' }}
|
||||
autoAdjustOverflow={false}
|
||||
>
|
||||
<Button type="link" size="small">
|
||||
表单内容
|
||||
</Button>
|
||||
</Popover>
|
||||
),
|
||||
},
|
||||
{ title: '顾问', dataIndex: 'opi_name', key: 'opi_name', width: '6rem' },
|
||||
{ title: '预定时间', dataIndex: 'coli_applydate', key: 'coli_applydate', width: '6rem' },
|
||||
],
|
||||
};
|
||||
|
||||
const chartsConfig = {
|
||||
colFields: ['ConfirmOrder', 'SumOrder'],
|
||||
lineFields: ['SumML', 'ConfirmRates'],
|
||||
seriesField: null,
|
||||
xField: 'groupDateVal',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
|
||||
<Col md={24} lg={12} xxl={14}>
|
||||
<NavLink to={-1}>返回</NavLink>
|
||||
</Col>
|
||||
</Row>
|
||||
<section>
|
||||
<h2>结果指标 @ {searchValues.date.format(DATE_FORMAT)}</h2>
|
||||
<Table {...dashboardTableProps} bordered dataSource={results[`operator_${opisn}`]} loading={results.loading} sticky />
|
||||
</section>
|
||||
<section>
|
||||
<h3>全年每月业绩 @ {searchValues.date.format('YYYY')}</h3>
|
||||
<MixFieldsDetail {...chartsConfig} dataSource={results[`operator_byDate_${opisn}`] || []} />
|
||||
</section>
|
||||
<Divider />
|
||||
<section>
|
||||
<h2>过程指标 @ {searchValuesToSub.Date1} 至 {searchValuesToSub.Date2}</h2>
|
||||
<Table {...activityTableProps} bordered dataSource={process[`operator_${opisn}`]} loading={process.loading} sticky />
|
||||
</section>
|
||||
<section>
|
||||
<h2>违规明细</h2>
|
||||
<h3>>24H回复</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostTouch24 || []} />
|
||||
</section>
|
||||
<section>
|
||||
<h3>首次报价周期>48h</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote48 || []} />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<1</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote1 || []} />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<2</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote2 || []} />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue