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/Conversations/Online/Components/ChatListItem.jsx

364 lines
15 KiB
JavaScript

import React, { useEffect, useState, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Dropdown, Input, Button, Tag, Popover, Form, Tooltip, Spin } from 'antd';
import { CloseCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
import { fetchConversationItemClose, fetchConversationsSearch, fetchConversationItemUnread, fetchConversationItemTop, postConversationTags, deleteConversationTags, fetchCleanUnreadMsgCount } from '@/actions/ConversationActions';
import { ChatItem } from 'react-chat-elements';
// import ConversationsNewItem from './ConversationsNewItem';
import { flush, isEmpty, isNotEmpty, stringToColour, TagColorStyle } from '@/utils/commons';
import useConversationStore from '@/stores/ConversationStore';
import useAuthStore from '@/stores/AuthStore';
import ChannelLogo from './ChannelLogo';
import RegionCodeEmoji from '@/components/RegionCodeEmoji'
import { ReadIcon } from '@/components/Icons';
import useStyleStore from '@/stores/StyleStore';
import { OrderLabelDefaultOptionsMapped, OrderStatusDefaultOptionsMapped } from '@/stores/OrderStore';
import { whatsappMsgTypeMapped } from '@/channel/bubbleMsgUtils';
const OrderSignEmoji = ({ item }) => (
<>
<Tooltip color={'cyan'} title={OrderLabelDefaultOptionsMapped[String(item.order_label_id)]?.label}>{OrderLabelDefaultOptionsMapped[String(item.order_label_id)]?.emoji}</Tooltip>
<Tooltip color={'orange'} title={OrderStatusDefaultOptionsMapped[String(item.order_state_id)]?.label}>{OrderStatusDefaultOptionsMapped[String(item.order_state_id)]?.emoji}</Tooltip>
<Tooltip color={'volcano'} title={'走团中'}>{item.intour === 1 && item.order_state_id === 5 ? '👣' : ''}</Tooltip>
</>
)
const NewTagForm = ({onSubmit,...props}) => {
const [form] = Form.useForm();
const [subLoding, setSubLoding] = useState(false);
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus()
return () => {}
}, [])
const onFinish = async (values) => {
// console.log('Received values of form[new_tag]: ', values);
setSubLoding(true);
if (typeof onSubmit === 'function') {
await onSubmit(values.tag_label);
}
form.resetFields();
setSubLoding(false);
}
return (
<Form
form={form}
name='new_tag_form'
layout='inline' size='small'
initialValues={{}}
onFinish={onFinish}>
<Form.Item name={'tag_label'} rules={[{ required: true, message: '请输入标签名' }]}>
<Input placeholder='新建并设置' ref={inputRef} />
</Form.Item>
<Form.Item>
<Button type='primary' htmlType='submit' loading={subLoding} >
确定
</Button>
</Form.Item>
</Form>
);
};
const ChatListItem = (({item, refreshConversationList,setListUpdateFlag,onSwitchConversation, setNewChatModalVisible,setEditingChat,...props}) => {
const [mobile] = useStyleStore((state) => [state.mobile]);
const routerReplace = mobile === false ? true : false; // : true;
const routePrefix = mobile === false ? `/order/chat` : `/m/chat`;
const { order_sn } = useParams();
const navigate = useNavigate();
const userId = useAuthStore((state) => state.loginUser.userId);
const [currentConversation, setCurrentConversation] = useConversationStore((state) => [state.currentConversation, state.setCurrentConversation]);
const delConversationitem = useConversationStore((state) => state.delConversationitem);
const setClosedConversationList = useConversationStore((state) => state.setClosedConversationList);
const [currentHandleChat, setCurrentHandleChat] = useState({});
const [handleLoading, setHandleLoading] = useState(false);
const itemTagsKeys = (item.tags || []).map(t => t.key);
const [tags, addTag] = useConversationStore(state => [state.tags, state.addTag]);
const handleConversationItemClose = async (item) => {
setHandleLoading(true);
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);
setCurrentHandleChat({});
setHandleLoading(false);
};
const handleConversationItemUnread = async (item) => {
setHandleLoading(true);
if (item.unread_msg_count < 999) {
await fetchConversationItemUnread({ conversationid: item.sn });
} else {
await fetchCleanUnreadMsgCount({ opisn: item.opi_sn, conversationid: item.sn });
}
await refreshConversationList(item.lasttime);
setListUpdateFlag(Math.random());
setCurrentHandleChat({});
setHandleLoading(false);
}
const handleConversationItemTop = async (item) => {
setHandleLoading(true);
await fetchConversationItemTop({ conversationid: item.sn, top_state: item.top_state === 0 ? 1 : 0 });
await refreshConversationList(item.lasttime);
setListUpdateFlag(Math.random());
setCurrentHandleChat({});
setHandleLoading(false);
}
const handleConversationItemMuted = async (item) => {
setHandleLoading(true);
await fetchConversationItemTop({ conversationid: item.sn, top_state: item.top_state === -1 ? 0 : -1 });
await refreshConversationList(item.lasttime);
setListUpdateFlag(Math.random());
setCurrentHandleChat({});
setHandleLoading(false);
}
const handleConversationItemTags = async (item, tagKey, tagLabel) => {
const _tags = (item.tags || []).map(t => t.key);
setHandleLoading(true);
if (isNotEmpty(tagKey) && _tags.includes(Number(tagKey))) {
await deleteConversationTags({ conversationid: item.sn, tag_id: tagKey, opisn: userId })
} else {
const rtag = await postConversationTags({
conversationid: item.sn,
tag_id: tagKey || '',
tag_label: tagLabel || '',
opisn: userId,
})
if (isEmpty(tagKey)) {
addTag({ label: rtag.tag_label, key: rtag.tag_key, value: rtag.tag_key })
}
}
await refreshConversationList(item.lasttime);
setListUpdateFlag(Math.random());
setContextMenuOpen(false);
setCurrentHandleChat({});
setHandleLoading(false);
}
const [contextMenuOpen, setContextMenuOpen] = useState(false);
const handleContextMenuOpenChange = (nextOpen, info, item) => {
if (info.source === 'trigger' || nextOpen) {
setContextMenuOpen(nextOpen);
setCurrentHandleChat(nextOpen ? item : {})
} else {
// setCurrentHandleChat({});
}
};
const [openTags, setOpenTags] = useState([]);
useEffect(() => {
if (contextMenuOpen === false) {
setOpenTags([]);
}
return () => {};
}, [contextMenuOpen])
const RenderLastMsg = (msg) => {
const readFromMsg = msg?.originText || msg?.text || '';
// const _text = isEmpty(msg) ? '' : msg.type === 'text' ? msg.text.body : `[${(msg?.type || '').toUpperCase()}]`;
const _text = isEmpty(msg?.type) ? '' : ((whatsappMsgTypeMapped?.[msg.type]?.renderForReply(msg) || {})?.message || readFromMsg)
return (
<>{_text}</>
);
};
return (
<>
<Dropdown
key={item.sn}
destroyPopupOnHide
trigger={['contextMenu']}
overlayClassName='z-[998]'
open={contextMenuOpen}
onOpenChange={(nextOpen, info) => handleContextMenuOpenChange(nextOpen, info, item)}
menu={{
items: [
{ label: item.top_state === 1 ? '取消置顶' : '置顶会话', key: 'top' },
{ label: item.unread_msg_count > 998 ? '标为已读' : '标记为未读', key: 'unread' },
// { label: item.top_state === -1 ? '取消静音' : '设为静音', key: 'mute' },
{
label: '设置标签',
key: 'tags',
children: [
...tags.map((t) => ({
...t,
key: `tag_${t.key}`,
style: { color: stringToColour(t.label) },
// icon: itemTagsKeys.includes(t.key) ? <CloseCircleOutlined className='text-red-500' /> : false,
extra: itemTagsKeys.includes(t.key) ? <MinusCircleOutlined className='text-red-500' /> : false,
})),
{
label: (
<>
<Popover
content={
<NewTagForm
onSubmit={(tagLabel) => {
handleConversationItemTags(item, '', tagLabel)
setContextMenuOpen(false)
}}
/>
}
placement='bottom'
trigger={['click']}>
<Button type='dashed' size='small' className='m-1'>
+新标签
</Button>
</Popover>
</>
),
key: 'new_tags',
},
],
onTitleClick: ({ key, domEvent }) => {
// console.log(']]]', key);
},
},
{ label: '编辑联系人', key: 'edit0' },
{ type: 'divider' },
{ label: '移到🗂隐藏列表', key: 'close', danger: true },
],
triggerSubMenuAction: 'click',
openKeys: openTags,
onOpenChange: (openKeys) => {
if (!isEmpty(openKeys) && contextMenuOpen) {
setOpenTags(openKeys)
}
},
onClick: ({ key, domEvent }) => {
domEvent.stopPropagation()
if (key.startsWith('tag_')) {
const tagKey = key.replace('tag_', '')
return handleConversationItemTags(item, tagKey)
}
switch (key) {
case 'top':
setContextMenuOpen(false);
return handleConversationItemTop(item);
case 'mute':
setContextMenuOpen(false);
return handleConversationItemMuted(item);
case 'unread':
setContextMenuOpen(false);
return handleConversationItemUnread(item);
case 'close':
setContextMenuOpen(false);
return handleConversationItemClose(item);
case 'edit0':
setOpenTags([])
setEditingChat({ ...item, is_new: false })
return setNewChatModalVisible(true)
case 'new_tags':
return
default:
// setContextMenuOpen(false);
console.log('unknown key', key)
return
}
},
}}>
<div
className={[
'border-0 border-solid border-neutral-200',
// item.top_state === 1 ? 'bg-stone-100' : '',
String(item.sn) === String(currentConversation.sn)
? '__active text-primary bg-whatsapp-bg'
: item.top_state === 1
? 'bg-stone-100'
: '',
'hover:bg-slate-50',
(item.sn) === (currentHandleChat?.sn) ? ' bg-slate-50 text-slate-500' : '',
// String(item.sn) === String(tabSelectedConversation?.sn) ? ' bg-neutral-200' : '',
].join(' ')}>
{/* <div className='pl-4 pt-1 text-xs text-right'>
{tags.map((tag) => <Tag color={tag.color} key={tag.value}>{tag.label}</Tag>)}
</div> */}
<Spin spinning={(item.sn) === (currentHandleChat?.sn) && (props.conversationsListLoading || handleLoading)} size='small'>
<ChatItem
{...item}
key={item.sn}
id={item.sn}
letterItem={ item.session_type === 1 ? void 0 : { id: item.show_default, letter: (item?.show_default || '').split("@")[0].slice(0, 5) }}
avatar={ item.session_type === 1 ? 'https://hiana-crm.oss-accelerate.aliyuncs.com/WAMedia/02ab0228-4c3c-4834-ac73-a6dfcdf81938.png' : void 0}
avatarSize={'small'}
alt={item.whatsapp_name}
title={
<span>
{/* 🔝 */}
{/* <RegionCodeEmoji regionCode={item?.last_message?.regionCode || ''} /> */}
{item.show_default}
</span>
// item.conversation_memo ||
}
// subtitle={item.coli_id}
subtitle={
<>
{/* <ReadIcon /> */}
{/* <DeliverIcon /> */}
{/* <SentIcon /> */}
{/* <span>{item.coli_id}</span> */}
{/* <span><ReadIcon />最后一条消息</span> */}
<span className='text-xs'>
<RenderLastMsg {...item?.last_message} />
</span>
<div className='text-sm'>
<OrderSignEmoji item={item} />
{(item?.tags || [])?.map((tag) => (
<Tag key={tag.label} style={{ ...TagColorStyle(tag.label, true) }} className='text-xs px-0.5 me-0.5'>
{tag.label}
</Tag>
))}
{/* <span title={'附加备注'}>附加备注</span> */}
</div>
</>
}
date={item.lasttime || item.last_received_time || item.last_send_time}
dateString='' // 为了覆盖: 其他客户端发送的失败消息, 推送到此处产生新会话, 但是dataString是长字符串
unread={item.unread_msg_count > 99 ? 0 : item.unread_msg_count}
muted={item.top_state === -1}
showMute={item.top_state === -1}
// 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' />}
statusText={
<ChannelLogo
channel={flush([
item?.channels?.phone_number ? 'waba' : null,
item?.channels?.email ? 'email' : null,
item?.channels?.whatsapp_phone_number ? 'waba' : null,
item?.last_message?.source || null, // wai, WABA, email
])}
/>
}
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'} className='self-end>💎💴❤👑💼🤝💤💔💨✅🕳❓❔❇✳❎🚫❌🎈🎊🎁📜</span>,
]}
/>
</Spin>
</div>
</Dropdown>
</>
)
});
export default ChatListItem;