|
|
|
import { useEffect, useState, useRef } from 'react';
|
|
|
|
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
|
|
|
import { Dropdown, Input, Button, Empty, Tooltip, Tag, Select, Divider, Radio, Popover } from 'antd';
|
|
|
|
import { PlusOutlined, WhatsAppOutlined, LoadingOutlined, HistoryOutlined, FireOutlined,AudioTwoTone, FilterOutlined, TagsOutlined, TagsTwoTone, FilterTwoTone } from '@ant-design/icons';
|
|
|
|
import { fetchConversationsList, fetchOrderConversationsList, fetchConversationItemClose, fetchConversationsSearch, postNewConversationItem, fetchConversationItemUnread, UNREAD_MARK } from '@/actions/ConversationActions';
|
|
|
|
import { ChatItem } from 'react-chat-elements';
|
|
|
|
import ConversationsNewItem from './ConversationsNewItem';
|
|
|
|
import { isEmpty, olog, stringToColour } from '@/utils/commons';
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
import useAuthStore from '@/stores/AuthStore';
|
|
|
|
import { useVisibilityState } from '@/hooks/useVisibilityState';
|
|
|
|
import { OrderLabelDefaultOptions, OrderStatusDefaultOptions, RemindStateDefaultOptions } from '@/stores/OrderStore'
|
|
|
|
|
|
|
|
const { Option, OptGroup } = Select;
|
|
|
|
|
|
|
|
const TagColorStyle = (tag) => {
|
|
|
|
const color = stringToColour(tag);
|
|
|
|
return { color: `${color}`, borderColor: `${color}66`, backgroundColor: `${color}0D` }
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* []
|
|
|
|
*/
|
|
|
|
const Conversations = ({ mobile }) => {
|
|
|
|
const routerReplace = mobile === undefined ? true : false; // : true;
|
|
|
|
const routePrefix = mobile === undefined ? `/order/chat` : `/m/chat`;
|
|
|
|
const { state: orderRow } = useLocation();
|
|
|
|
const { coli_guest_WhatsApp } = orderRow || {};
|
|
|
|
const { order_sn } = useParams();
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const userId = useAuthStore((state) => state.loginUser.userId);
|
|
|
|
const initialState = useConversationStore((state) => state.initialState);
|
|
|
|
const [currentConversation, setCurrentConversation] = useConversationStore((state) => [state.currentConversation, state.setCurrentConversation]);
|
|
|
|
const conversationsList = useConversationStore((state) => state.conversationsList);
|
|
|
|
const [conversationsListLoading, setConversationsListLoading] = useConversationStore((state) => [state.conversationsListLoading, state.setConversationsListLoading]);
|
|
|
|
const addToConversationList = useConversationStore((state) => state.addToConversationList);
|
|
|
|
const delConversationitem = useConversationStore((state) => state.delConversationitem);
|
|
|
|
|
|
|
|
const closedConversationsList = useConversationStore((state) => state.closedConversationsList);
|
|
|
|
const setClosedConversationList = useConversationStore((state) => state.setClosedConversationList);
|
|
|
|
|
|
|
|
const isVisible = useVisibilityState();
|
|
|
|
|
|
|
|
const [tabSelectedConversation, setTabSelectedConversation] = useState({});
|
|
|
|
const [tabCnt, setTabCnt] = useState(-1);
|
|
|
|
|
|
|
|
async function refreshConversationList() {
|
|
|
|
setConversationsListLoading(mobile !== undefined ? true : false);
|
|
|
|
const _list = await fetchConversationsList({ opisn: userId });
|
|
|
|
addToConversationList(_list);
|
|
|
|
setConversationsListLoading(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (mobile !== undefined) {
|
|
|
|
setCurrentConversation({});
|
|
|
|
}
|
|
|
|
return () => {};
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (isVisible) {
|
|
|
|
refreshConversationList();
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => {};
|
|
|
|
}, [isVisible]);
|
|
|
|
|
|
|
|
const [activeList, setActiveList] = useState(true);
|
|
|
|
|
|
|
|
const [dataSource, setDataSource] = useState(conversationsList);
|
|
|
|
const [listUpdateFlag, setListUpdateFlag] = useState();
|
|
|
|
useEffect(() => {
|
|
|
|
// setDataSource(conversationsList);
|
|
|
|
setDataSource(activeList ? conversationsList: closedConversationsList);
|
|
|
|
return () => {};
|
|
|
|
}, [conversationsList.length, listUpdateFlag, currentConversation.unread_msg_count]);
|
|
|
|
|
|
|
|
const [switchToC, setSwitchToC] = useState({});
|
|
|
|
const [shouldFetchCList, setShouldFetchCList] = useState(true);
|
|
|
|
useEffect(() => {
|
|
|
|
if (order_sn && shouldFetchCList && initialState) {
|
|
|
|
getOrderConversationList(order_sn);
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => {};
|
|
|
|
}, [order_sn, shouldFetchCList, initialState]);
|
|
|
|
|
|
|
|
const getOrderConversationList = async (colisn, WHATSAPP_ID = null) => {
|
|
|
|
const { whatsapp_phone_number } = switchToC;
|
|
|
|
const whatsappID = WHATSAPP_ID || coli_guest_WhatsApp || whatsapp_phone_number || '';
|
|
|
|
// let findCurrentOrderChats = conversationsList.filter((item) => `${item.coli_sn}` === `${colisn}`);
|
|
|
|
// 使用opisn + whatsappID 判断, 解决订单修改whatsappID号码之后获取新会话, 登录账号此处省略
|
|
|
|
let findCurrentOrderChats = conversationsList.filter((item) => `${item.whatsapp_phone_number}` === `${whatsappID}` && `${item.coli_sn}` === `${colisn}`);
|
|
|
|
let findCurrentIndex = isEmpty(findCurrentOrderChats) ? -1 : 0; // findCurrentOrderChats.length-1;
|
|
|
|
let findCurrent = findCurrentOrderChats[findCurrentIndex];
|
|
|
|
if (findCurrentIndex !== -1) {
|
|
|
|
addToConversationList(findCurrentOrderChats);
|
|
|
|
} else if (!isEmpty(whatsappID)) {
|
|
|
|
const data = await fetchOrderConversationsList({ opisn: userId, colisn: colisn, whatsappid: whatsappID });
|
|
|
|
if (!isEmpty(data)) {
|
|
|
|
addToConversationList(data);
|
|
|
|
findCurrentIndex = 0; // data.length-1; // data.lastIndexOf((item) => item.coli_sn === Number(colisn));
|
|
|
|
findCurrent = data[findCurrentIndex];
|
|
|
|
} else {
|
|
|
|
// findCurrentIndex = conversationsList.findIndex((item) => item.coli_sn === Number(colisn)); // data.findIndex((item) => item.sn === currentConversation.sn);
|
|
|
|
}
|
|
|
|
} else if (isEmpty(whatsappID)) {
|
|
|
|
// 刷新页面
|
|
|
|
findCurrentIndex = conversationsList.findIndex((item) => `${item.coli_sn}` === `${colisn}`);
|
|
|
|
findCurrent = conversationsList[findCurrentIndex];
|
|
|
|
}
|
|
|
|
if (findCurrentIndex >= 0) {
|
|
|
|
setCurrentConversation(findCurrent);
|
|
|
|
return findCurrent;
|
|
|
|
} else {
|
|
|
|
// reset chat window
|
|
|
|
setCurrentConversation({ sn: '', customer_name: '', coli_sn: order_sn });
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onSwitchConversation = async (item) => {
|
|
|
|
setCurrentConversation(item);
|
|
|
|
if (isEmpty(item.coli_sn)) {
|
|
|
|
navigate(routePrefix, { replace: true });
|
|
|
|
} else {
|
|
|
|
setSwitchToC(item);
|
|
|
|
setShouldFetchCList(false);
|
|
|
|
navigate(`${routePrefix}/${item.coli_sn}`, { replace: routerReplace });
|
|
|
|
}
|
|
|
|
// if (!isEmpty(item.coli_sn)) {
|
|
|
|
// setSwitchToC(item);
|
|
|
|
// setShouldFetchCList(false);
|
|
|
|
// navigate(`/order/chat/${item.coli_sn}`, { replace: true });
|
|
|
|
// } else {
|
|
|
|
// navigate(`/order/chat`, { replace: true });
|
|
|
|
// }
|
|
|
|
// switchConversation(item);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleConversationItemClose = async (item) => {
|
|
|
|
await fetchConversationItemClose({ conversationid: item.sn, opisn: item.opi_sn });
|
|
|
|
delConversationitem(item);
|
|
|
|
if (String(order_sn) === String(item.coli_sn)) {
|
|
|
|
navigate(routePrefix, { replace: routerReplace });
|
|
|
|
}
|
|
|
|
const _clist = await fetchConversationsSearch({ opisn: userId, session_enable: 0 });
|
|
|
|
setClosedConversationList(_clist);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleConversationItemUnread = async (item) => {
|
|
|
|
await fetchConversationItemUnread({ conversationid: item.sn });
|
|
|
|
await refreshConversationList();
|
|
|
|
setListUpdateFlag(Math.random());
|
|
|
|
}
|
|
|
|
|
|
|
|
const searchInputRef = useRef(null);
|
|
|
|
const [searchContent, setSearchContent] = useState('');
|
|
|
|
const handleSearchConversations = (val) => {
|
|
|
|
const fromSource = activeList ? conversationsList : closedConversationsList;
|
|
|
|
if (val.toLowerCase().trim() !== '') {
|
|
|
|
const res = fromSource.filter(
|
|
|
|
(item) =>
|
|
|
|
item.whatsapp_name.toLowerCase().includes(val.toLowerCase().trim()) ||
|
|
|
|
item.whatsapp_phone_number.toLowerCase().includes(val.toLowerCase().trim()) ||
|
|
|
|
item.coli_id.toLowerCase().includes(val.toLowerCase().trim())
|
|
|
|
);
|
|
|
|
setDataSource(res);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
setDataSource(fromSource);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleRichSearchConvs = (params) => {
|
|
|
|
// alert('1')
|
|
|
|
}
|
|
|
|
|
|
|
|
const [newChatModalVisible, setNewChatModalVisible] = useState(false);
|
|
|
|
|
|
|
|
// const closedVisible = closedConversationsList.length > 0;
|
|
|
|
const toggleClosedConversationsList = () => {
|
|
|
|
const _active = activeList;
|
|
|
|
setDataSource(_active ? closedConversationsList : conversationsList);
|
|
|
|
setActiveList(!activeList);
|
|
|
|
setCurrentConversation({});
|
|
|
|
};
|
|
|
|
|
|
|
|
const filterTags = [
|
|
|
|
{ color: 'volcano', label: '重点', value: '重点' },
|
|
|
|
{ color: 'success', label: '成行', value: '成行' },
|
|
|
|
{ color: 'gold', label: '老客户', value: '老客户' },
|
|
|
|
{ color: 'blue', label: '走团中', value: '走团中' },
|
|
|
|
{ color: 'red-inverse', label: '投诉', value: '投诉' },
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* todo: 弹出, 多选
|
|
|
|
*/
|
|
|
|
const filterTag = (
|
|
|
|
<Select defaultValue="" dropdownStyle={{ width: '10rem', maxHeight: 400, overflow: 'auto' }}>
|
|
|
|
<Option value="">所有</Option>
|
|
|
|
<OptGroup key={'label'} label={'标签'}>
|
|
|
|
{filterTags.map((item, index) => (<Option value={item.value} key={item.value}>{item.label}</Option>))}
|
|
|
|
</OptGroup>
|
|
|
|
<OptGroup key={'remind'} label={'提醒'}>
|
|
|
|
{RemindStateDefaultOptions.map((item, index) => (<Option value={item.value} key={item.value}>{item.label}</Option>))}
|
|
|
|
</OptGroup>
|
|
|
|
<OptGroup key={'stat'} label={'状态'}>
|
|
|
|
{OrderStatusDefaultOptions.map((item, index) => (<Option value={`state.${item.value}`} key={`state.${item.value}`}>{item.label}</Option>))}
|
|
|
|
</OptGroup>
|
|
|
|
</Select>
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className='flex flex-col h-inherit'>
|
|
|
|
<div className='flex gap-1'>
|
|
|
|
<Button onClick={() => setNewChatModalVisible(true)} icon={<PlusOutlined />} type={'primary'} ghost shape={'circle'} />
|
|
|
|
<Input.Search
|
|
|
|
className=''
|
|
|
|
ref={searchInputRef}
|
|
|
|
allowClear
|
|
|
|
value={searchContent}
|
|
|
|
onChange={(e) => {
|
|
|
|
setSearchContent(e.target.value);
|
|
|
|
handleSearchConversations(e.target.value);
|
|
|
|
setTabCnt(-1);
|
|
|
|
setTabSelectedConversation({});
|
|
|
|
}}
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
if (e.key === 'Tab') {
|
|
|
|
e.preventDefault();
|
|
|
|
const _this = tabCnt >= dataSource.length - 1 ? 0 : tabCnt + 1;
|
|
|
|
setTabCnt(_this);
|
|
|
|
setTabSelectedConversation(dataSource[_this]);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
onPressEnter={(e) => {
|
|
|
|
handleSearchConversations(e.target.value);
|
|
|
|
searchInputRef.current.blur();
|
|
|
|
onSwitchConversation(dataSource[tabCnt < 0 ? 0 : tabCnt]);
|
|
|
|
setTabCnt(-1);
|
|
|
|
setTabSelectedConversation({});
|
|
|
|
return false;
|
|
|
|
}}
|
|
|
|
placeholder={`名称/号码/订单号${conversationsListLoading ? '...' : ''}`}
|
|
|
|
onSearch={handleRichSearchConvs}
|
|
|
|
// addonBefore={filterTag}
|
|
|
|
// addonBefore={<FilterOutlined />}
|
|
|
|
// enterButton={'Filter'}
|
|
|
|
/>
|
|
|
|
<Tooltip key={'conversation-list'} title={activeList ? '历史会话' : '活跃会话'}>
|
|
|
|
<Button
|
|
|
|
onClick={toggleClosedConversationsList}
|
|
|
|
icon={activeList ? <HistoryOutlined className='text-neutral-500' /> : <FireOutlined className=' text-orange-500' />}
|
|
|
|
type='text'
|
|
|
|
/>
|
|
|
|
</Tooltip>
|
|
|
|
{mobile === undefined ? (
|
|
|
|
''
|
|
|
|
) : (
|
|
|
|
<AudioTwoTone
|
|
|
|
onClick={() => {
|
|
|
|
navigate(`/callcenter/call`);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className='my-1 flex justify-between items-center '>
|
|
|
|
<Radio.Group
|
|
|
|
optionType={'button'}
|
|
|
|
buttonStyle='solid'
|
|
|
|
size='small'
|
|
|
|
options={[
|
|
|
|
{ label: 'All', value: 'all' },
|
|
|
|
{ label: '重点', value: 'top' },
|
|
|
|
{ label: '次重点', value: 'second' },
|
|
|
|
{ label: '成行', value: 'go' },
|
|
|
|
{ label: '走团中', value: 'runing' },
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
{/* <Dropdown
|
|
|
|
trigger={'click'}
|
|
|
|
placement='bottom'
|
|
|
|
menu={{
|
|
|
|
items: [
|
|
|
|
{ label: '已付款', key: 'p1', value: 'p1', className: `!m-1 underline-offset-4 hover:underline focus:underline active:underline`, style: TagColorStyle('已付款') },
|
|
|
|
{ label: '地接', key: 'p2', value: 'p2', className: `!m-1 underline-offset-4 hover:underline focus:underline active:underline`, style: TagColorStyle('地接') },
|
|
|
|
],
|
|
|
|
}}>
|
|
|
|
<Button
|
|
|
|
onClick={() => {
|
|
|
|
// alert('1')
|
|
|
|
}}
|
|
|
|
icon={<TagsTwoTone />}
|
|
|
|
type='text'
|
|
|
|
size='middle'
|
|
|
|
/>
|
|
|
|
</Dropdown> */}
|
|
|
|
<Popover
|
|
|
|
placement='bottom'
|
|
|
|
trigger={'click'}
|
|
|
|
content={
|
|
|
|
<>
|
|
|
|
标签:
|
|
|
|
{[
|
|
|
|
{ label: '已付款', key: 'p1', value: 'p1', className: ``, style: TagColorStyle('已付款') },
|
|
|
|
{ label: '地接', key: 'p2', value: 'p2', className: ``, style: TagColorStyle('地接') },
|
|
|
|
].map((tag) => (
|
|
|
|
<Tag.CheckableTag key={tag.key} color={tag.color} style={tag.style} className={tag.className}>
|
|
|
|
{tag.label}
|
|
|
|
</Tag.CheckableTag>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
}>
|
|
|
|
<Button
|
|
|
|
onClick={() => {
|
|
|
|
// alert('1')
|
|
|
|
}}
|
|
|
|
icon={<FilterTwoTone />}
|
|
|
|
type='text'
|
|
|
|
size='middle'
|
|
|
|
/>
|
|
|
|
</Popover>
|
|
|
|
</div>
|
|
|
|
<div className='flex-1 overflow-x-hidden overflow-y-auto relative'>
|
|
|
|
{conversationsListLoading && dataSource.length === 0 ? (
|
|
|
|
<div className='text-center py-2'>
|
|
|
|
<LoadingOutlined className='text-primary ' />
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{dataSource.map((item) => (
|
|
|
|
<Dropdown
|
|
|
|
key={item.sn}
|
|
|
|
menu={{
|
|
|
|
items: [
|
|
|
|
{ label: '置顶会话', key: 'top' },
|
|
|
|
{ label: '标记为未读', key: 'unread' },
|
|
|
|
{ label: '设置标签', key: 'tags' }, // selection
|
|
|
|
{ label: '编辑联系人', key: 'remark' },
|
|
|
|
{ label: <Divider className='my-0' />, key: 'd2' },
|
|
|
|
{ label: '隐藏会话', key: 'close', danger: true },
|
|
|
|
],
|
|
|
|
onClick: ({ key, domEvent }) => {
|
|
|
|
domEvent.stopPropagation();
|
|
|
|
switch (key) {
|
|
|
|
case 'close':
|
|
|
|
return handleConversationItemClose(item);
|
|
|
|
case 'unread':
|
|
|
|
return handleConversationItemUnread(item);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
trigger={['contextMenu']}>
|
|
|
|
<div
|
|
|
|
className={[
|
|
|
|
'border-0 border-t1 border-solid border-neutral-200',
|
|
|
|
String(item.sn) === String(currentConversation.sn) ? '__active text-primary bg-whatsapp-bg' : '',
|
|
|
|
String(item.sn) === String(tabSelectedConversation?.sn) ? ' bg-neutral-200' : '',
|
|
|
|
].join(' ')}>
|
|
|
|
<div className='pl-4 pt-1 text-xs text-right'>{/* {filterTags.map((tag) => <Tag color={tag.color} key={tag.value}>{tag.label}</Tag>)} */}</div>
|
|
|
|
<ChatItem
|
|
|
|
{...item}
|
|
|
|
key={item.sn}
|
|
|
|
id={item.sn}
|
|
|
|
letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).slice(0, 5) }}
|
|
|
|
alt={item.whatsapp_name}
|
|
|
|
title={item.whatsapp_name || item.whatsapp_phone_number}
|
|
|
|
// subtitle={item.coli_id}
|
|
|
|
subtitle={
|
|
|
|
<div>
|
|
|
|
{item.coli_id}
|
|
|
|
<div>
|
|
|
|
{[
|
|
|
|
{ label: '已付款', key: 'p1' },
|
|
|
|
{ label: '地接', key: 'p2' },
|
|
|
|
]?.map((tag) => (
|
|
|
|
<Tag key={tag.label} style={{ ...TagColorStyle(tag.label) }} className='text-xs px-0.5 me-0.5'>
|
|
|
|
{tag.label}
|
|
|
|
</Tag>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
date={item.last_received_time || item.last_send_time}
|
|
|
|
unread={item.unread_msg_count > 99 ? 0 : item.unread_msg_count}
|
|
|
|
// className={[
|
|
|
|
// String(item.sn) === String(currentConversation.sn) ? '__active text-primary bg-whatsapp-bg' : '',
|
|
|
|
// String(item.sn) === String(tabSelectedConversation?.sn) ? ' bg-neutral-200' : '',
|
|
|
|
// ].join(' ')}
|
|
|
|
statusText={<WhatsAppOutlined key={'channel'} className='text-whatsapp' />}
|
|
|
|
statusColor={'#fff'}
|
|
|
|
onClick={() => onSwitchConversation(item)}
|
|
|
|
customStatusComponents={[
|
|
|
|
...(item.unread_msg_count > 99 ? [() => <div className='w-4 h-4 bg-red-500 rounded-full' key={'unread'}></div>] : []),
|
|
|
|
// () => <span key={'tag'}>💎💴❤👑💼🤝💤💔💨✅🕳❓❔❇✳❎🚫❌🎈🎊🎁📜</span>,
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Dropdown>
|
|
|
|
))}
|
|
|
|
{dataSource.length === 0 && <Empty description={'无数据'} />}
|
|
|
|
</div>
|
|
|
|
<ConversationsNewItem
|
|
|
|
initialValues={{ is_current_order: false }}
|
|
|
|
open={newChatModalVisible}
|
|
|
|
onCreate={() => setNewChatModalVisible(false)}
|
|
|
|
onCancel={() => setNewChatModalVisible(false)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
export default Conversations;
|