|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
import { memo, useCallback, useEffect, useRef, useState, forwardRef } from 'react';
|
|
|
|
import { Row, Col, Divider, Table, Card, Button, Input, Flex, Layout, Space, Empty, Radio, Select, DatePicker, Form, List, Avatar, Spin, Image } from 'antd';
|
|
|
|
import { StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined } from '@ant-design/icons';
|
|
|
|
import { ChatList, ChatItem, MessageBox } from 'react-chat-elements';
|
|
|
|
import { fetchConversationsList, fetchMessages } from '@/actions/ConversationActions';
|
|
|
|
import { isEmpty } from '@/utils/utils';
|
|
|
|
import useFormStore from '@/stores/FormStore';
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
|
|
|
|
import { fetchSalesAgent } from '@/actions/CommonActions';
|
|
|
|
// import SearchInput from '@/components/SearchInput2';
|
|
|
|
import SearchInput from '@/components/SearchInput';
|
|
|
|
|
|
|
|
const { Sider, Content, Header, Footer } = Layout;
|
|
|
|
const { Search } = Input;
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
|
|
|
// https://media-xsp2-1.cdn.whatsapp.net/v/t61.24694-24/424735646_380563021285029_2962758854250800176_n.jpg?ccb=11-4&oh=01_AdTogiVdUE-ToI9uH-VQKTTLyDbP7bocXUQe1OETOeCgcg&oe=65F7C6AB&_nc_sid=e6ed6c&_nc_cat=104
|
|
|
|
|
|
|
|
// eslint-disable-next-line react/display-name
|
|
|
|
const SearchForm = memo(function ({ initialValues, onSubmit }) {
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
function handleSubmit(values) {
|
|
|
|
onSubmit?.({...values, travel: values?.agent?.value || -1});
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<Form
|
|
|
|
layout={'inline'}
|
|
|
|
form={form}
|
|
|
|
initialValues={initialValues}
|
|
|
|
onFinish={handleSubmit}
|
|
|
|
style={{
|
|
|
|
maxWidth: 'none',
|
|
|
|
}}>
|
|
|
|
<Form.Item label='顾问' name='agent' style={{ width: '200px' }}>
|
|
|
|
<SearchInput placeholder='搜索顾问' fetchOptions={fetchSalesAgent} />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item label='客人' name='orderLabel' style={{ width: '200px' }}>
|
|
|
|
<Select
|
|
|
|
showSearch
|
|
|
|
placeholder='请选择'
|
|
|
|
optionFilterProp='children'
|
|
|
|
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
|
|
options={[
|
|
|
|
{
|
|
|
|
value: 'Denise',
|
|
|
|
label: 'Denise',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Kennedy',
|
|
|
|
label: 'Kennedy',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Harsh',
|
|
|
|
label: 'Harsh',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'SMLiew',
|
|
|
|
label: 'SMLiew',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'See Kok Ching',
|
|
|
|
label: 'See Kok Ching',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Jonathan Michael Lilley',
|
|
|
|
label: 'Jonathan Michael Lilley',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Loan',
|
|
|
|
label: 'Loan',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Leah Belle',
|
|
|
|
label: 'Leah Belle',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Christelle Narvasa',
|
|
|
|
label: 'Christelle Narvasa',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'Mai Schaefer',
|
|
|
|
label: 'Mai Schaefer',
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item label='关键词' name='orderNumber'>
|
|
|
|
<Input placeholder='关键词' allowClear />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item label='日期' name='startDate'>
|
|
|
|
<RangePicker />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item>
|
|
|
|
<Button type='primary' htmlType='submit'>
|
|
|
|
搜索
|
|
|
|
</Button>
|
|
|
|
</Form.Item>
|
|
|
|
</Form>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
function ChatHistory() {
|
|
|
|
// const [formValues, setFormValues] = useState({});
|
|
|
|
const [formValues, setFormValues] = useFormStore(useShallow((state) => [state.chatHistoryForm, state.setChatHistoryForm]));
|
|
|
|
const [selectedConversation, setSelectedConversation] = useFormStore(useShallow((state) => [state.chatHistorySelectChat, state.setChatHistorySelectChat]));
|
|
|
|
|
|
|
|
const handleSubmit = useCallback((values) => {
|
|
|
|
setFormValues({ ...values });
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [conversationsList, setConversationsList] = useState([]);
|
|
|
|
const [chatItemMessages, setChatItemMessages] = useState([]);
|
|
|
|
const getConversationsList = async () => {
|
|
|
|
setLoading(true);
|
|
|
|
setChatItemMessages([]);
|
|
|
|
const data = await fetchConversationsList({ opisn: formValues.travel });
|
|
|
|
setLoading(false);
|
|
|
|
setConversationsList(data);
|
|
|
|
if (data.length === 1) {
|
|
|
|
setSelectedConversation(data[0]);
|
|
|
|
await getMessages(data[0]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const getMessages = async (chatItem) => {
|
|
|
|
setLoading(true);
|
|
|
|
setChatItemMessages([]);
|
|
|
|
const data = await fetchMessages({ opisn: chatItem.opi_sn, whatsappid: chatItem.whatsapp_phone_number });
|
|
|
|
setLoading(false);
|
|
|
|
setChatItemMessages(data);
|
|
|
|
};
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (selectedConversation.whatsapp_phone_number) {
|
|
|
|
getMessages(selectedConversation);
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => {};
|
|
|
|
}, [selectedConversation]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (formValues.travel) {
|
|
|
|
getConversationsList();
|
|
|
|
}
|
|
|
|
return () => {};
|
|
|
|
}, [formValues]);
|
|
|
|
|
|
|
|
const RenderText = memo(function renderText({ str }) {
|
|
|
|
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter((s) => s !== '');
|
|
|
|
const links = str.match(/https?:\/\/[\S]+/gi) || [];
|
|
|
|
const emojis = str.match(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g) || [];
|
|
|
|
const extraClass = isEmpty(emojis) ? '' : '';
|
|
|
|
const objArr = parts.reduce((prev, curr, index) => {
|
|
|
|
if (links.includes(curr)) {
|
|
|
|
prev.push({ type: 'link', key: curr });
|
|
|
|
} else if (emojis.includes(curr)) {
|
|
|
|
prev.push({ type: 'emoji', key: curr });
|
|
|
|
} else {
|
|
|
|
prev.push({ type: 'text', key: curr });
|
|
|
|
}
|
|
|
|
return prev;
|
|
|
|
}, []);
|
|
|
|
return (
|
|
|
|
<span className={`text-base leading-5 emoji-text ${extraClass}`}>
|
|
|
|
{(objArr || []).map((part, index) => {
|
|
|
|
if (part.type === 'link') {
|
|
|
|
return (
|
|
|
|
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-base'>
|
|
|
|
{part.key}
|
|
|
|
</a>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return part.key;
|
|
|
|
}
|
|
|
|
})}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
const handlePreview = (msg) => {
|
|
|
|
switch (msg.whatsapp_msg_type) {
|
|
|
|
case 'image':
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case 'document':
|
|
|
|
window.open(msg.data.link || msg.data.uri, '_blank', 'noopener,noreferrer');
|
|
|
|
return false;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const messagesEndRef = useRef(null);
|
|
|
|
const messageRefs = useRef([]);
|
|
|
|
const scrollToMessage = (id, index) => {
|
|
|
|
const _i = index || chatItemMessages.findIndex((msg) => msg.id === id);
|
|
|
|
if (_i >= 0) {
|
|
|
|
messageRefs.current[_i].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// eslint-disable-next-line react/display-name
|
|
|
|
const MessageBoxWithRef = forwardRef((props, ref) => (
|
|
|
|
<li ref={ref} >
|
|
|
|
<MessageBox {...props} />
|
|
|
|
</li>
|
|
|
|
));
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<SearchForm onSubmit={handleSubmit} initialValues={formValues} />
|
|
|
|
<Divider plain orientation='left'></Divider>
|
|
|
|
<Layout hasSider className='h-screen chathistory-wrapper chatwindow-wrapper' style={{ maxHeight: 'calc(100% - 279px)', height: 'calc(100% - 279px)' }}>
|
|
|
|
<Sider width={240} theme={'light'} className='h-full overflow-y-auto overflow-x-hidden' style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)' }}>
|
|
|
|
<Spin spinning={loading}>
|
|
|
|
{conversationsList.map((item) => (
|
|
|
|
<ChatItem
|
|
|
|
{...item}
|
|
|
|
key={item.sn}
|
|
|
|
id={item.sn}
|
|
|
|
letterItem={{ id: item.whatsapp_name.trim() || item.whatsapp_phone_number, letter: (item.whatsapp_name.trim() || item.whatsapp_phone_number).slice(0, 5) }}
|
|
|
|
alt={`${item.whatsapp_name.trim()}`}
|
|
|
|
title={item.whatsapp_name.trim() || item.whatsapp_phone_number}
|
|
|
|
subtitle={item.coli_id}
|
|
|
|
date={item.last_received_time}
|
|
|
|
className={String(item.sn) === String(selectedConversation.sn) ? '__active text-primary bg-neutral-100' : ''}
|
|
|
|
onClick={() => setSelectedConversation(item)}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</Spin>
|
|
|
|
</Sider>
|
|
|
|
<Content style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)', minWidth: '360px' }}>
|
|
|
|
<div className='h-full relative' ref={messagesEndRef}>
|
|
|
|
<List
|
|
|
|
loading={loading}
|
|
|
|
loadMore={() => {
|
|
|
|
console.log('load more');
|
|
|
|
}}
|
|
|
|
className='h-full overflow-y-auto px-2 relative'
|
|
|
|
itemLayout='vertical'
|
|
|
|
dataSource={chatItemMessages}
|
|
|
|
renderItem={(message, index) => (
|
|
|
|
// <List.Item>
|
|
|
|
// <List.Item.Meta avatar={<Avatar src={item.avatarUrl} />} title={item.title} description={item.msgTime} />
|
|
|
|
// <div>{item.content}</div>
|
|
|
|
// </List.Item>
|
|
|
|
<MessageBoxWithRef
|
|
|
|
ref={(el) => (messageRefs.current[index] = el)}
|
|
|
|
key={message.id}
|
|
|
|
{...message}
|
|
|
|
// position={message.sender === 'me' ? 'right' : 'left'}
|
|
|
|
position={'left'}
|
|
|
|
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
|
|
|
onOpen={() => handlePreview(message)}
|
|
|
|
onTitleClick={() => handlePreview(message)}
|
|
|
|
notch={false}
|
|
|
|
text={<RenderText str={message?.text || ''} />}
|
|
|
|
copiableDate={true}
|
|
|
|
dateString={message.dateString || message.localDate}
|
|
|
|
className={[
|
|
|
|
'whitespace-pre-wrap mb-2',
|
|
|
|
message.whatsapp_msg_type === 'sticker' ? 'bg-transparent' : '',
|
|
|
|
message.sender === 'me' ? 'whatsappme-container' : '',
|
|
|
|
].join(' ')}
|
|
|
|
style={{
|
|
|
|
backgroundColor: message.sender === 'me' ? '#ccd4ae' : '#fff',
|
|
|
|
}}
|
|
|
|
{...(message.type === 'meetingLink'
|
|
|
|
? {
|
|
|
|
actionButtons: [
|
|
|
|
{
|
|
|
|
onClickButton: () => {
|
|
|
|
navigator.clipboard.writeText(message.text);
|
|
|
|
},
|
|
|
|
Component: () => <div>复制</div>,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
: {})}
|
|
|
|
renderAddCmp={
|
|
|
|
<div className='border-dashed border-0 border-t border-slate-300 text-slate-600 space-x-2 emoji'>
|
|
|
|
<span>{message.senderName}</span>
|
|
|
|
<span>{message.dateString || message.localDate}</span>
|
|
|
|
<span>{message.statusCN}</span>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
// date={null}
|
|
|
|
// status={null}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Content>
|
|
|
|
</Layout>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default ChatHistory;
|