|
|
|
@ -1,11 +1,15 @@
|
|
|
|
|
import { createContext, useEffect, useState } from 'react';
|
|
|
|
|
import { Button, Tag, Radio, Popover, Form, Dropdown, Tabs, List, Image, Empty, Avatar, Card } from 'antd';
|
|
|
|
|
import { FileSearchOutlined, FilterOutlined, FilterTwoTone } from '@ant-design/icons';
|
|
|
|
|
import { FilterIcon, InboxIcon, MailSendIcon, SendPlaneFillIcon, SendPlaneLineIcon } from '@/components/Icons';
|
|
|
|
|
import { groupBy, isEmpty, objectMapper, stringToColour } from '@/utils/commons';
|
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
|
import { App, Button, Popover, Tabs, List, Image, Avatar, Card } from 'antd';
|
|
|
|
|
import { FileSearchOutlined, LoadingOutlined } from '@ant-design/icons';
|
|
|
|
|
import { InboxIcon, SendPlaneFillIcon } from '@/components/Icons';
|
|
|
|
|
import { groupBy, stringToColour } from '@/utils/commons';
|
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
|
import EmailDetail from './EmailDetail';
|
|
|
|
|
import { MESSAGE_PAGE_SIZE, fetchMessagesHistory } from '@/actions/ConversationActions';
|
|
|
|
|
import DnDModal from '@/components/DnDModal';
|
|
|
|
|
|
|
|
|
|
const BIG_PAGE_SIZE = MESSAGE_PAGE_SIZE * 10;
|
|
|
|
|
|
|
|
|
|
const CalColorStyle = (tag, outerStyle = true) => {
|
|
|
|
|
const color = stringToColour(tag);
|
|
|
|
@ -13,72 +17,143 @@ const CalColorStyle = (tag, outerStyle = true) => {
|
|
|
|
|
return { color: `${color}`, ...outerStyleObj };
|
|
|
|
|
};
|
|
|
|
|
const getVideoName = (vUrl) => {
|
|
|
|
|
if ( ! vUrl) return '';
|
|
|
|
|
const url = new URL(vUrl);
|
|
|
|
|
return url.pathname.split('/').pop();
|
|
|
|
|
};
|
|
|
|
|
/**
|
|
|
|
|
* 消息记录筛选----------------------------------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
const MessageListFilter = ({ ...props }) => {
|
|
|
|
|
const activeMessages = useConversationStore(
|
|
|
|
|
useShallow((state) => (state.currentConversation.sn && state.activeConversations[state.currentConversation.sn] ? state.activeConversations[state.currentConversation.sn] : []))
|
|
|
|
|
);
|
|
|
|
|
const currentConversation = useConversationStore((state) => state.currentConversation);
|
|
|
|
|
const { opi_sn: opisn, whatsapp_phone_number: whatsappid } = currentConversation;
|
|
|
|
|
|
|
|
|
|
const { message: appMessage } = App.useApp();
|
|
|
|
|
|
|
|
|
|
const LongList = () => {
|
|
|
|
|
const LongList = () => {
|
|
|
|
|
return <></>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
const [paramsForMsgList, setParamsForMsgList] = useState({});
|
|
|
|
|
const [historyMessages, setHistoryMessages] = useState([]);
|
|
|
|
|
const getMessagesPre = async (param) => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
const chatItem = { opisn, whatsappid };
|
|
|
|
|
const data = await fetchMessagesHistory({ ...chatItem, lasttime: param.pretime, pagedir: 'pre', pagesize: BIG_PAGE_SIZE });
|
|
|
|
|
setLoading(false);
|
|
|
|
|
setHistoryMessages((prevValue) => [].concat(data, prevValue));
|
|
|
|
|
const loadPrePage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
|
|
|
|
// if (data.length > 0) {
|
|
|
|
|
// setParamsForMsgList({ loadPrePage, pretime: data[0].orgmsgtime });
|
|
|
|
|
// }
|
|
|
|
|
setParamsForMsgList((preVal) => ({ ...preVal, loadPrePage, pretime: data.length > 0 ? data[0].orgmsgtime : preVal.pretime }));
|
|
|
|
|
};
|
|
|
|
|
const onLoadMore = async () => {
|
|
|
|
|
await getMessagesPre(paramsForMsgList);
|
|
|
|
|
};
|
|
|
|
|
const loadMore = paramsForMsgList.loadPrePage ? (
|
|
|
|
|
<div className='text-center h-8 leading-8'>
|
|
|
|
|
{!loading ? (
|
|
|
|
|
<Button onClick={onLoadMore} size='small'>
|
|
|
|
|
loading more
|
|
|
|
|
</Button>
|
|
|
|
|
) : (
|
|
|
|
|
<LoadingOutlined className='text-primary' />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
) : null;
|
|
|
|
|
|
|
|
|
|
const handleCopyClick = (url) => {
|
|
|
|
|
try {
|
|
|
|
|
navigator.clipboard.writeText(url)
|
|
|
|
|
appMessage.success('复制成功😀');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
appMessage.warning('不支持自动复制, 请手动复制');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (activeMessages.length > 0) {
|
|
|
|
|
setHistoryMessages(activeMessages);
|
|
|
|
|
}
|
|
|
|
|
const { opi_sn: opisn, whatsapp_phone_number: whatsappid } = currentConversation;
|
|
|
|
|
setParamsForMsgList({ loadPrePage: true, pretime: activeMessages.length > 0 ? activeMessages[0].orgmsgtime : '', opisn, whatsappid });
|
|
|
|
|
|
|
|
|
|
return () => {};
|
|
|
|
|
}, [activeMessages, currentConversation.sn]);
|
|
|
|
|
|
|
|
|
|
const Album = () => {
|
|
|
|
|
const data = (activeMessages || []).filter((item) => item.type === 'photo').reverse();
|
|
|
|
|
const data = historyMessages.filter((item) => item.type === 'photo').reverse();
|
|
|
|
|
const byDate = groupBy(data, (item) => item.localDate.slice(0, 10));
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{data.length === 0 && <Empty description={false} />}
|
|
|
|
|
<div className='max-h-96 overflow-y-auto'>
|
|
|
|
|
<Image.PreviewGroup className='my-4'>
|
|
|
|
|
{Object.keys(byDate).map((date, index) => (
|
|
|
|
|
<Card size='small' title={date} key={date} className='mb-2'>
|
|
|
|
|
<div className='grid grid-cols-5 gap-2 '>
|
|
|
|
|
{byDate[date].map((img) => (
|
|
|
|
|
<Image className='border object-cover' key={img.data.id} width={100} height={100} src={img.data.uri} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
|
|
|
|
</Image.PreviewGroup>
|
|
|
|
|
</div>
|
|
|
|
|
<Image.PreviewGroup className='my-4'>
|
|
|
|
|
<List
|
|
|
|
|
className='max-h-96 overflow-y-auto'
|
|
|
|
|
itemLayout='vertical'
|
|
|
|
|
dataSource={Object.keys(byDate)}
|
|
|
|
|
loadMore={loadMore}
|
|
|
|
|
loading={loading}
|
|
|
|
|
renderItem={(date) => (
|
|
|
|
|
<List.Item>
|
|
|
|
|
<Card size='small' title={date} key={date} className='mb-2'>
|
|
|
|
|
<div className='grid grid-cols-5 gap-2 '>
|
|
|
|
|
{byDate[date].map((img) => (
|
|
|
|
|
<Image className='border object-cover' key={img.data.id} width={100} height={100} src={img.data.uri} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
</List.Item>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
</Image.PreviewGroup>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Videos = () => {
|
|
|
|
|
// todo: 打开视频播放
|
|
|
|
|
const data = (activeMessages || []).filter((item) => item.type === 'video').reverse();
|
|
|
|
|
const data = historyMessages.filter((item) => item.type === 'video').reverse();
|
|
|
|
|
|
|
|
|
|
const [videoUrl, setVideoUrl] = useState('');
|
|
|
|
|
const [openVideoPlay, setOpenVideoPlay] = useState(false);
|
|
|
|
|
const handleOpenVideoPlay = (vurl) => {
|
|
|
|
|
setVideoUrl(vurl);
|
|
|
|
|
setOpenVideoPlay(true);
|
|
|
|
|
setOpenPopup(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* {data.length === 0 && <Empty description={false} />}
|
|
|
|
|
<div className='grid grid-cols-3 gap-2 max-h-96 overflow-y-auto'>
|
|
|
|
|
{data.map((item) => (
|
|
|
|
|
<video controls controlsList='nodownload nofullscreen noremoteplayback' preload='metadata' key={item.data.id} className='max-h-24'>
|
|
|
|
|
<source src={`${item?.data.videoURL}#t=0.1`} type='video/mp4' />
|
|
|
|
|
Your browser does not support HTML video.
|
|
|
|
|
</video>
|
|
|
|
|
))}
|
|
|
|
|
</div> */}
|
|
|
|
|
<List
|
|
|
|
|
className='max-h-96 overflow-y-auto'
|
|
|
|
|
itemLayout='horizontal'
|
|
|
|
|
// itemLayout='horizontal'
|
|
|
|
|
dataSource={data}
|
|
|
|
|
renderItem={(item, index) => (
|
|
|
|
|
<List.Item actions={[item.localDate]}>
|
|
|
|
|
loadMore={loadMore}
|
|
|
|
|
loading={loading}
|
|
|
|
|
renderItem={(item) => (
|
|
|
|
|
<List.Item
|
|
|
|
|
actions={[
|
|
|
|
|
<Button key='copyv' onClick={() => handleCopyClick(item.data.videoURL)} type='link' size='small'>
|
|
|
|
|
复制🔗
|
|
|
|
|
</Button>,
|
|
|
|
|
item.localDate,
|
|
|
|
|
]}
|
|
|
|
|
className='items-center'>
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
// avatar={<Avatar src={`https://api.dicebear.com/7.x/miniavs/svg?seed=${index}`} />}
|
|
|
|
|
avatar={
|
|
|
|
|
<Avatar size='small' style={CalColorStyle(item.sender)}>
|
|
|
|
|
{item.senderName}
|
|
|
|
|
</Avatar>
|
|
|
|
|
}
|
|
|
|
|
title={
|
|
|
|
|
<a href={item.data.uri} target='_blank' rel='noreferrer'>
|
|
|
|
|
<span onClick={() => handleOpenVideoPlay(item?.data.videoURL)}>
|
|
|
|
|
{getVideoName(item?.data.videoURL)}
|
|
|
|
|
</a>
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
description={item.text}
|
|
|
|
|
/>
|
|
|
|
@ -86,18 +161,26 @@ const MessageListFilter = ({ ...props }) => {
|
|
|
|
|
</List.Item>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
<DnDModal open={openVideoPlay} setOpen={setOpenVideoPlay} title={getVideoName(videoUrl)} key='video-player'>
|
|
|
|
|
<video controls preload='metadata' width={660}>
|
|
|
|
|
<source src={videoUrl} type='video/mp4' />
|
|
|
|
|
Your browser does not support HTML video.
|
|
|
|
|
</video>
|
|
|
|
|
</DnDModal>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
const Audios = () => {
|
|
|
|
|
const data = (activeMessages || []).filter((item) => item.type === 'audio').reverse();
|
|
|
|
|
const data = historyMessages.filter((item) => item.type === 'audio').reverse();
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<List
|
|
|
|
|
className='max-h-96 overflow-y-auto'
|
|
|
|
|
itemLayout='horizontal'
|
|
|
|
|
// itemLayout='horizontal'
|
|
|
|
|
dataSource={data}
|
|
|
|
|
renderItem={(item, index) => (
|
|
|
|
|
loadMore={loadMore}
|
|
|
|
|
loading={loading}
|
|
|
|
|
renderItem={(item) => (
|
|
|
|
|
<List.Item actions={[item.localDate]}>
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
avatar={
|
|
|
|
@ -115,16 +198,21 @@ const MessageListFilter = ({ ...props }) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const FileList = () => {
|
|
|
|
|
const data = (activeMessages || []).filter((item) => item.type === 'file').reverse();
|
|
|
|
|
const data = historyMessages.filter((item) => item.type === 'file').reverse();
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* {data.length === 0 && <Empty />} */}
|
|
|
|
|
<List
|
|
|
|
|
className='max-h-96 overflow-y-auto'
|
|
|
|
|
itemLayout='horizontal'
|
|
|
|
|
// itemLayout='horizontal'
|
|
|
|
|
dataSource={data}
|
|
|
|
|
renderItem={(item, index) => (
|
|
|
|
|
<List.Item actions={[item.localDate]}>
|
|
|
|
|
loadMore={loadMore}
|
|
|
|
|
loading={loading}
|
|
|
|
|
renderItem={(item) => (
|
|
|
|
|
<List.Item actions={[
|
|
|
|
|
<Button key='copyv' onClick={() => handleCopyClick(item.data.uri)} type='link' size='small'>
|
|
|
|
|
复制🔗
|
|
|
|
|
</Button>,item.localDate]}>
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
// avatar={<Avatar src={`https://api.dicebear.com/7.x/miniavs/svg?seed=${index}`} />}
|
|
|
|
|
avatar={
|
|
|
|
@ -148,8 +236,7 @@ const MessageListFilter = ({ ...props }) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const EmailList = () => {
|
|
|
|
|
// todo: 打开邮件
|
|
|
|
|
const data = (activeMessages || []).filter((item) => item.type === 'email').reverse();
|
|
|
|
|
const data = historyMessages.filter((item) => item.type === 'email').reverse();
|
|
|
|
|
|
|
|
|
|
const [openEmailDetail, setOpenEmailDetail] = useState(false);
|
|
|
|
|
const [emailDetail, setEmailDetail] = useState({});
|
|
|
|
@ -163,42 +250,39 @@ const MessageListFilter = ({ ...props }) => {
|
|
|
|
|
{/* {data.length === 0 && <Empty />} */}
|
|
|
|
|
<List
|
|
|
|
|
className='max-h-96 overflow-y-auto'
|
|
|
|
|
itemLayout='horizontal'
|
|
|
|
|
// itemLayout='horizontal'
|
|
|
|
|
dataSource={data}
|
|
|
|
|
renderItem={({ emailOrigin, ...item }, index) => (
|
|
|
|
|
<List.Item actions={[item.localDate]}>
|
|
|
|
|
loadMore={loadMore}
|
|
|
|
|
loading={loading}
|
|
|
|
|
renderItem={({ emailOrigin, ...item }) => (
|
|
|
|
|
<List.Item
|
|
|
|
|
actions={[item.localDate]}
|
|
|
|
|
className='cursor-pointer'
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onOpenEmail({ emailOrigin, ...item });
|
|
|
|
|
setOpenPopup(false);
|
|
|
|
|
}}>
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
avatar={
|
|
|
|
|
item.sender === 'me' ? <SendPlaneFillIcon /> : <InboxIcon className='text-indigo-500' />
|
|
|
|
|
item.sender === 'me' ? <SendPlaneFillIcon className='text-primary' /> : <InboxIcon className='text-indigo-500' />
|
|
|
|
|
// <Avatar size='small' style={CalColorStyle(item.senderName)}>
|
|
|
|
|
// {item.senderName.substring(0, 3)}
|
|
|
|
|
// </Avatar>
|
|
|
|
|
}
|
|
|
|
|
title={
|
|
|
|
|
<Button
|
|
|
|
|
type='link'
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onOpenEmail({ emailOrigin, ...item });
|
|
|
|
|
setOpenPopup(false);
|
|
|
|
|
}}
|
|
|
|
|
size='small'
|
|
|
|
|
className='px-0'>
|
|
|
|
|
{emailOrigin.subject}
|
|
|
|
|
</Button>
|
|
|
|
|
}
|
|
|
|
|
title={emailOrigin.subject}
|
|
|
|
|
description={`To: ${emailOrigin.toEmail}`}
|
|
|
|
|
/>
|
|
|
|
|
{emailOrigin.abstract}
|
|
|
|
|
</List.Item>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
<EmailDetail open={openEmailDetail} setOpen={setOpenEmailDetail} emailDetail={emailDetail} key={`email-detail-${emailDetail.id}`} />
|
|
|
|
|
<EmailDetail open={openEmailDetail} setOpen={setOpenEmailDetail} emailDetail={emailDetail} key={`email-detail-1-${emailDetail.id}`} />
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const [openPopup, setOpenPopup] = useState(false);
|
|
|
|
|
// todo: 实时请求列表
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Popover
|
|
|
|
|