引用; 关闭会话

dev/mobile
Lei OT 2 years ago
parent 92225cb0c8
commit ddce009a57

@ -62,6 +62,12 @@ export const addConversationList = (data) => {
payload: data,
};
};
export const delConversationitem = (data) => {
return {
type: NAME_SPACE + 'DEL_CONVERSATIONS_ITEM',
payload: data,
};
};
// export const updateConversationListItemNew = (message) => ({
// type: NAME_SPACE + 'UPDATE_CONVERSATION_LIST_ITEM_NEW',
// payload: message,
@ -90,6 +96,12 @@ export const receivedMessageList = (targetId, data) => ({
type: NAME_SPACE + 'RECEIVED_MESSAGE_LIST',
payload: { targetId, data },
})
export const setReplyTo = (data) => {
return {
type: NAME_SPACE + 'SET_REFERENCE_MSG',
payload: data,
};
};
export const fetchTemplates = async () => {
const data = await fetchJSON(`${API_HOST}/listtemplates`);
@ -116,8 +128,8 @@ export const fetchCustomerProfile = async (colisn) => {
return data;
};
export const postConversationItemClose = async (body) => {
export const fetchConversationItemClose = async (body) => {
const getParams = body ? new URLSearchParams(body).toString() : '';
const { result } = await postJSON(`${API_HOST}/closeconversation?${getParams}`);
const { result } = await fetchJSON(`${API_HOST}/closeconversation?${getParams}`);
return result;
};

@ -25,14 +25,21 @@ export const replaceTemplateString = (str, replacements) => {
export const sentMsgTypeMapped = {
text: {
type: 'text',
contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'text', msgcontent: { body: msg.text } }),
contentToRender: (msg) => ({ ...msg, actionId: msg.id, conversationid: msg.id.split('.')[0], }),
contentToSend: (msg) => ({
action: 'message',
actionId: msg.id,
renderId: msg.id,
to: msg.to,
msgtype: 'text',
msgcontent: { body: msg.text, ...(msg.context ? { context: msg.context } : {}) },
}),
contentToRender: (msg) => ({ ...msg, actionId: msg.id, conversationid: msg.id.split('.')[0], ...(msg.context ? { reply: { message: msg.context.message_origin.text, title: msg.context.message_origin.senderName || 'Reference' } } : {}) }),
},
whatsappTemplate: {
contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }),
contentToRender: (msg) => {
console.log(msg);
const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({...r, [v.type]: v}), {}) : null;
const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : null;
// const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text);
// const fillTemplate = templateParam.length ? replaceTemplateString(msg.template_origin.components.body?.[0]?.text || '', templateParam) : (msg.template_origin.components.body?.[0]?.text || '');
// const footer = msg.template_origin.components?.footer?.[0]?.text || '';
@ -167,7 +174,7 @@ export const whatsappMsgTypeMapped = {
unsupported: { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) },
reaction: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || msg.reaction?.text?.body || 'Reaction', reply: { message: '{content}', title: 'React from' } }),
data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || msg.reaction?.text?.body || 'Reaction', reply: { message: '{content}', title: 'React from' } }), // todo:
},
document: {
type: 'file',
@ -198,6 +205,7 @@ export const parseRenderMessageItem = (msg) => {
...(typeof whatsappMsgTypeMapped[msg.type].type === 'function' ? whatsappMsgTypeMapped[msg.type].type(msg) : { type: whatsappMsgTypeMapped[msg.type].type || 'text' }),
// type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
sender: msg.from,
senderName: msg?.customerProfile?.name || msg.from,
// status: msg?.status || 'waiting',
// title: msg.customerProfile.name,
// replyButton: true,
@ -221,9 +229,11 @@ export const parseRenderMessageList = (messages, conversationid = null) => {
...(typeof whatsappMsgTypeMapped[msgType].type === 'function' ? whatsappMsgTypeMapped[msgType].type(msg) : { type: whatsappMsgTypeMapped[msgType].type || 'text' }),
date: msgContent?.sendTime || msg.msgtime || '',
sender: msgContent.from,
senderName: msgContent?.customerProfile?.name || msgContent.from,
...(msg.msg_direction === 'outbound'
? {
sender: 'me',
senderName: 'me',
status: msgStatusRenderMapped[msgContent?.status || 'failed'],
dateString: msgStatusRenderMapped[msgContent?.status || 'failed'] === 'failed' ? '发送失败 ❌' : '',
}

@ -13,5 +13,6 @@ const initialState = {
activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: [] }
currentConversation: {}, // 当前对话
referenceMsg: {},
};
export default initialState;

@ -27,6 +27,19 @@ const ConversationReducer = (state = initialState, action) => {
activeConversations: { ...activeConversations, ...newConversationsMapped },
};
}
case NAME_SPACE + 'DEL_CONVERSATIONS_ITEM': {
const { conversationsList, activeConversations, currentConversation, customerOrderProfile } = state;
const targetId = action.payload.sn;
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
conversationsList.splice(targetIndex, 1);
return {
...state,
conversationsList: [...conversationsList],
activeConversations: { ...activeConversations, [`${targetId}`]: [] },
currentConversation: {},
customerOrderProfile: {},
};
}
case NAME_SPACE + 'SET_TEMPLATE_LIST':
return { ...state, templates: action.payload };
@ -39,10 +52,10 @@ const ConversationReducer = (state = initialState, action) => {
const { conversationsList } = state;
const targetId = action.payload.sn;
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
conversationsList.splice(targetIndex, 1, {
targetIndex !== -1 ? conversationsList.splice(targetIndex, 1, {
...conversationsList[targetIndex],
unread_msg_count: 0,
});
}) : null;
return { ...state, currentConversation: action.payload, conversationsList: [...conversationsList] };
}
@ -126,6 +139,8 @@ const ConversationReducer = (state = initialState, action) => {
},
};
}
case NAME_SPACE + 'SET_REFERENCE_MSG':
return {...state, referenceMsg: action.payload};
case NAME_SPACE + 'ADD_ERROR': {
console.log('add error', state.errors, action.payload);
const prelist = state.errors || [];

@ -1,19 +1,55 @@
import { useRef, useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { List, Avatar, Flex } from 'antd';
import { List, Avatar, Flex, Popconfirm, Button, Dropdown } from 'antd';
import { SendOutlined, MessageOutlined, SmileOutlined, PictureOutlined, CommentOutlined, UploadOutlined, CloudUploadOutlined, FolderAddOutlined, FilePdfOutlined, CloseCircleOutlined, CloseCircleFilled, MoreOutlined } from '@ant-design/icons';
import { useAuthContext } from '@/stores/AuthContext';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
import {
fetchCustomerProfile,
receivedCustomerProfile,
setCurrentConversation,
addConversationList,
postConversationItemClose,
addConversationList, delConversationitem,
fetchConversationItemClose,
fetchMessages, receivedMessageList,
} from '@/actions/ConversationActions';
import { ChatList } from 'react-chat-elements';
import { ChatList, } from 'react-chat-elements';
import { isEmpty, pick } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
const CDropdown = () => {
const { loginUser } = useAuthContext();
const { userId } = loginUser;
const { currentConversation } = useConversationState();
const dispatch = useConversationDispatch();
const handleConversationItemClose = async (item) => {
await fetchConversationItemClose({ conversationid: item.sn, opisn: userId });
dispatch(delConversationitem(item));
};
return (
<Dropdown key={'more-action'}
trigger={'click'}
menu={{
items: [
{
key: 'close',
danger: true,
label: '关闭会话',
},
],
onClick: ({ key }) => {
switch (key) {
case 'close':
return handleConversationItemClose(currentConversation);
default:
return;
}
},
}}>
<Button key={'More'} type='text' title='More' className=' rounded-none text-gray-400' icon={<MoreOutlined />} size={'middle'} onClick={e => e.stopPropagation() } />
</Dropdown>
);
}
/**
* []
*/
@ -37,13 +73,14 @@ const Conversations = () => {
// subtitle: item.lastMessage,
date: item.last_received_time, // last_send_time // todo: GMT+8
unread: item.unread_msg_count,
showMute: true,
muted: false,
// showMute: true,
// muted: false,
// showVideoCall: true,
// statusColor: '#ccd5ae',
// statusColorType: 'badge',
// statusText: 'online'
className: String(item.sn) === String(currentConversation.sn) ? 'text-primary underline __active' : '',
// statusText: 'online',
className: String(item.sn) === String(currentConversation.sn) ? '__active text-primary underline bg-whatsapp-me ' : '',
customStatusComponents: [CDropdown],
}))
);
@ -91,6 +128,9 @@ const Conversations = () => {
}
};
const switchConversation = async (item) => {
if (String(item.sn) === String(currentConversation.sn)) {
return false;
}
dispatch(setCurrentConversation(item));
const messagesList = activeConversations[`${item.sn}`] || [];
if (isEmpty(messagesList)) {
@ -105,6 +145,8 @@ const Conversations = () => {
const onSwitchConversation = (item) => {
if (item.coli_sn) {
navigate(`/order/chat/${item.coli_sn}`, { replace: true });
} else {
navigate(`/order/chat`, { replace: true });
}
switchConversation(item);
};
@ -124,7 +166,7 @@ const Conversations = () => {
// console.log(item, i, e);
// return ( <p>ppp</p> )
// }}
onClickMute={handleConversationItemClose}
// onClickMute={handleConversationItemClose}
/>
</>
);

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { Input, Flex, Button } from 'antd';
import { Input, Flex, Button, } from 'antd';
// import { Input } from 'react-chat-elements';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
import { useAuthContext } from '@/stores/AuthContext';
import { sentNewMessage } from '@/actions/ConversationActions';
import { SendOutlined, MessageOutlined, SmileOutlined } from '@ant-design/icons';
import { sentNewMessage, setReplyTo } from '@/actions/ConversationActions';
import { SendOutlined, MessageOutlined, SmileOutlined, PictureOutlined, CommentOutlined, UploadOutlined, CloudUploadOutlined, FolderAddOutlined, FilePdfOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { isEmpty } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
import { sentMsgTypeMapped } from '@/lib/msgUtils';
@ -14,7 +14,7 @@ import dayjs from 'dayjs';
const InputBox = () => {
const { loginUser } = useAuthContext();
const { userId } = loginUser;
const { websocket, websocketOpened, currentConversation } = useConversationState();
const { websocket, websocketOpened, currentConversation, referenceMsg } = useConversationState();
const dispatch = useConversationDispatch();
const [textContent, setTextContent] = useState('');
@ -22,6 +22,8 @@ const InputBox = () => {
const gt24h = currentConversation.last_received_time ? dayjs().diff(dayjs(currentConversation.last_received_time), 'hour') > 24 : true;
const textabled = talkabled && !gt24h;
const invokeSendMessage = (msgObj) => {
console.log('sendMessage------------------', msgObj);
const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj);
@ -42,19 +44,27 @@ const InputBox = () => {
id: `${currentConversation.sn}.${uuid()}`, // Date.now().toString(16),
date: new Date(),
status: 'waiting',
...(referenceMsg.id ? { context: { message_id: referenceMsg.id, message_origin: referenceMsg } } : {}),
};
invokeSendMessage(msgObj);
setTextContent('');
dispatch(setReplyTo({}));
}
};
return (
<div>
{referenceMsg.id && (
<Flex justify='space-between' className='reply-to bg-gray-100 p-1 rounded-none text-slate-500'>
<div className=' border-l-3 border-y-0 border-r-0 border-slate-300 border-solid pl-2 pr-1 py-1'>{referenceMsg.text}</div>
<Button type='text' title='取消引用' className=' rounded-none text-slate-500' icon={<CloseCircleOutlined />} size={'middle'} onClick={() => dispatch(setReplyTo({}))} />
</Flex>
)}
<Input.TextArea
size='large'
placeholder={gt24h ? 'The session has expired, please send a template message to say hello' : 'Enter for Send, Shift+Enter for new line'}
placeholder={gt24h ? 'This session has expired. Please send a template message to activate the session' : 'Enter for Send, Shift+Enter for new line'}
rows={2}
disabled={!talkabled || gt24h}
disabled={!textabled}
value={textContent}
onChange={(e) => setTextContent(e.target.value)}
className='rounded-b-none'
@ -64,13 +74,19 @@ const InputBox = () => {
handleSendText();
}
}}
autoSize={{minRows: 2, maxRows: 6}}
autoSize={{ minRows: 2, maxRows: 6 }}
/>
<Flex justify={'space-between'} className=' bg-gray-200 p-1 rounded-b'>
<Flex gap={4} className='divide-y-0 divide-x divide-solid divide-gray-500 '>
<InputTemplate key='templates' disabled={!talkabled} invokeSendMessage={invokeSendMessage} />
{/* <Button type='text' className=' rounded-none text-primary' icon={<PictureOutlined />} size={'middle'} /> */}
{/* <Button type='text' className=' rounded-none text-primary' icon={<FolderAddOutlined />} size={'middle'} /> */}
{/* <Button type='text' className=' rounded-none text-primary' icon={<CloudUploadOutlined />} size={'middle'} /> */}
{/* <Button type='text' className=' rounded-none text-primary' icon={<FilePdfOutlined />} size={'middle'} /> */}
</Flex>
<Button key={'send-btn'} onClick={handleSendText} type='primary' size='middle' icon={<SendOutlined />} disabled={!talkabled || gt24h}>
<Button key={'send-btn'} onClick={handleSendText} type='primary' size='middle' icon={<SendOutlined />}
disabled={!textabled}
>
Send
</Button>
</Flex>

@ -1,10 +1,12 @@
import { useEffect, useState, useRef, useMemo } from 'react';
import { Image, Alert } from 'antd';
import { MessageBox } from 'react-chat-elements';
import { useConversationState } from '@/stores/ConversationContext';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
import { setReplyTo } from '@/actions/ConversationActions';
const Messages = () => {
const { activeConversations, currentConversation } = useConversationState();
const dispatch = useConversationDispatch();
const messagesList = useMemo(() => activeConversations[currentConversation.sn] || [], [activeConversations, currentConversation.sn]);
console.log('messagesList----------------------------------------------------', messagesList);
@ -30,7 +32,6 @@ const Messages = () => {
return (
<div>
{messagesList.map((message, index) => (
<MessageBox
// className={message.sender === 'me' ? 'whatsappme-container' : ''}
@ -45,9 +46,16 @@ const Messages = () => {
? {
style: { backgroundColor: '#ccd5ae' },
notchStyle: { fill: '#ccd5ae' },
// forwarded: false,
// todo: reaction: emoji
replyButton: message.type === 'text' && message.status !== 'failed' ? true : false,
onReplyClick: () => dispatch(setReplyTo(message)),
className: 'whatsappme-container whitespace-pre-wrap',
}
: {})}
: {
replyButton: message.type === 'text' ? true : false,
onReplyClick: () => dispatch(setReplyTo(message)),
})}
// notchStyle={{fill: '#ccd5ae'}}
// copiableDate={false}
/>

Loading…
Cancel
Save