You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Global-sales/src/views/ChatHistory.jsx

370 lines
13 KiB
React

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';
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
const data = [
{
2 years ago
title: 'Ann',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=0',
msgTime: '03-04 13:45:29',
content:
'Hi, this is Ann from China Highlights travel and it is my pleasure to help your Beijing trip. I have sent you an email with the general idea about you trip. Please check it and if any question or new idea just let me know. We are specailized in customizing tour and I would like to work with you to create a tour itinerary that you like. Look forward to your reply.',
},
{
2 years ago
title: 'David Azhari',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=2',
msgTime: '03-04 16:33:08',
content: 'Hi! I just replied to your email saying that I have a few questions and notes',
},
{
2 years ago
title: 'David Azhari',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=2',
msgTime: '03-04 16:33:34',
content: 'So first, is it possible for you guys to write a PU Invitation letter for visas or no?',
},
{
2 years ago
title: 'Ann',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=0',
msgTime: '03-04 16:33:41',
content: 'you prefer we discuss here or go on by mail ?',
2 years ago
},
{
title: 'David Azhari',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=2',
msgTime: '03-04 16:34:03',
content: 'I prefer by mail if its ok with you',
2 years ago
},
{
title: 'Ann',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=0',
msgTime: '03-04 16:34:28',
content: 'both is okay ,I am typing mail to you now lol and receive your message here.',
2 years ago
},
{
title: 'David Azhari',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=2',
msgTime: '03-05 02:56:37',
content: 'Ok thank you so much',
2 years ago
},
{
title: 'Ann',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=0',
msgTime: '03-04 16:44:03',
content: 'you are welcome I have sent the mail to you ,please check.it is my pleasure to assist you with your tour.',
},
];
// eslint-disable-next-line react/display-name
const SearchForm = memo(function ({ onSubmit }) {
const [form] = Form.useForm();
function handleSubmit(values) {
onSubmit?.(values);
}
return (
<Form
layout={'inline'}
form={form}
initialValues={{}}
onFinish={handleSubmit}
style={{
maxWidth: 'none',
}}>
<Form.Item label='顾问' name='travel' style={{ width: '200px' }}>
<Select
showSearch
placeholder='请选择'
optionFilterProp='children'
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
options={[
{
value: '354',
label: '宁艳',
},
{
value: '487',
label: '杨新玲',
},
{
value: '495',
label: '黄雪荣',
},
{
value: '骆梅玉',
label: '骆梅玉',
},
{
value: '586',
label: '秦宇尘',
},
{
value: '606',
label: '莫梦瑶',
},
{
value: '603',
label: '秦雯萱',
},
{
value: '601',
label: '王露加',
},
]}
/>
</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 handleSubmit = useCallback((values) => {
setFormValues({ ...values });
}, []);
const [loading, setLoading] = useState(false);
const [conversationsList, setConversationsList] = useState([]);
const [selectedConversation, setSelectedConversation] = 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) ? '' : 'text-base leading-5 emoji-text';
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={extraClass}>
{(objArr || []).map((part, index) => {
if (part.type === 'link') {
return (
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
{part.key}
</a>
);
} else {
// if (part.type === 'emoji')
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} />
<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 || ''} />}
dateString={message.dateString || message.localDate}
className={['whitespace-pre-wrap', 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>,
},
],
}
: {})}
/>
)}
/>
</div>
</Content>
</Layout>
</>
);
}
export default ChatHistory;