reducer context 管理conversation state

dev/chat
Lei OT 2 years ago
parent fc556120ce
commit 4552d0ad58

@ -91,9 +91,10 @@ export const receivedMsgTypeMapped = {
}),
},
'error': {
// 发送消息的同步返回: 发送失败时
getMsg: (result) => result,
contentToRender: () => null,
contentToUpdate: (msgcontent) => ({ ...msgcontent, id: msgcontent.actionId, status: msgcontent?.status || 'failed', dateString: '发送失败 ❌' }),
contentToUpdate: (msgcontent) => ({ ...msgcontent, id: msgcontent.actionId, status: msgcontent?.status || 'failed', dateString: '发送失败 ❌', conversationid: msgcontent.actionId.split('.')[0], }),
},
};
export const whatsappMsgTypeMapped = {

@ -0,0 +1,129 @@
import { groupBy } from '@/utils/utils';
const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_callback';
const NAME_SPACE = 'CONVERSATION/';
export const initWebsocket = (socket) => ({
type: NAME_SPACE + 'INIT_WEBSOCKET',
payload: socket,
});
export const closeWebsocket0 = () => ({
type: NAME_SPACE + 'CLOSE_WEBSOCKET',
payload: null,
});
export const closeWebsocket = () => {};
export const receivedNewMessage = (targetId, message) => ({
type: NAME_SPACE + 'RECEIVED_NEW_MESSAGE',
payload: {
targetId,
message,
},
});
export const sentNewMessage = (message) => ({
type: NAME_SPACE + 'SENT_NEW_MESSAGE',
payload: {
targetId: message.conversationid,
message,
},
});
export const addError = (error) => {
return {
type: NAME_SPACE + 'ADD_ERROR',
payload: error,
};
};
export const receivedTemplates = (data) => ({
type: NAME_SPACE + 'SET_TEMPLATE_LIST',
payload: data,
});
export const receivedConversationList = (data) => {
return {
type: NAME_SPACE + 'SET_CONVERSATION_LIST',
payload: data,
};
};
export const addConversationList = (data) => {
return {
type: NAME_SPACE + 'ADD_CONVERSATIONS',
payload: data,
};
};
export const updateConversationListItemNew = (message) => ({
type: NAME_SPACE + 'UPDATE_CONVERSATION_LIST_ITEM_NEW',
payload: message,
});
export const receivedCustomerProfile = (data) => ({
type: NAME_SPACE + 'SET_CUSTOMER_ORDER_PROFILE',
payload: data,
});
export const setActiveConversations = (obj) => ({
type: NAME_SPACE + 'SET_ACTIVE_CONVERSATIONS',
payload: obj,
});
export const setCurrentConversation = (obj) => ({
type: NAME_SPACE + 'SET_CURRENT_CONVERSATION',
payload: obj,
});
export const updateMessageItem = (message) => ({
type: NAME_SPACE + 'UPDATE_SENT_MESSAGE_ITEM',
payload: message,
});
export const fetchTemplates = async () => {
const data = await fetchJSON(`${API_HOST}/listtemplates`);
const canUseTemplates = (data?.result?.items || [])
.filter((_t) => _t.status !== 'REJECTED')
.map((ele) => ({ ...ele, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) }));
return canUseTemplates;
};
export const fetchConversationsList = async (opisn) => {
const { result: data } = await fetchJSON(`${API_HOST}/getconversations`, { opisn });
const _data = [];
// const _data = testConversations;
const list = [..._data, ...data.map((ele) => ({ ...ele, customer_name: ele.whatsapp_name.trim() }))];
return list;
};
export const fetchCustomerProfile = async (colisn) => {
const { result } = await fetchJSON(`${API_HOST}/getorderinfo`, { colisn });
const data = result?.[0] || {};
return data;
};
export async function fetchJSON(url, data) {
let params = '';
let ifp = '';
if (data) {
params = new URLSearchParams(data).toString();
ifp = params ? '?' : ifp;
}
ifp = url.includes('?') ? '' : ifp;
const host = /^https?:\/\//i.test(url) ? '' : API_HOST;
const response = await fetch(`${host}${url}${ifp}${params}`);
return await response.json();
}
export async function postJSON(url, obj) {
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('fetch error:', error);
throw error;
}
}

@ -1,334 +1,12 @@
import { createContext, useContext, useState, useEffect, useRef } from 'react';
import { receivedMsgTypeMapped, sentMsgTypeMapped } from '@/lib/msgUtils';
import { groupBy, isEmpty, pick } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
import {createContext, useContext} from 'react';
export const ConversationContext = createContext();
export const useConversationContext = () => useContext(ConversationContext);
export async function fetchJSON(url, data) {
let params = '';
let ifp = '';
if (data) {
params = new URLSearchParams(data).toString();
ifp = params ? '?' : ifp;
}
ifp = url.includes('?') ? '' : ifp;
const host = /^https?:\/\//i.test(url) ? '' : ''; // HT_HOST;
const response = await fetch(`${host}${url}${ifp}${params}`);
return await response.json();
}
export async function postJSON(url, obj) {
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('fetch error:', error);
throw error;
}
}
// const API_HOST = 'http://202.103.68.144:8888';
const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_callback';
export const useConversations = ({loginUser, realtimeAPI}) => {
const { userId } = loginUser;
const [errors, setErrors] = useState([]);
const [messages, setMessages] = useState([]); // active conversation
const [activeConversations, setActiveConversations] = useState({}); // all active conversation
const [currentID, setCurrentID] = useState();
const [conversationsList, setConversationsList] = useState([]); // open conversations
const [currentConversation, setCurrentConversation] = useState({ sn: '', customer_name: '', coli_sn: '' });
const currentConversationRef = useRef(currentConversation);
useEffect(() => {
currentConversationRef.current = currentConversation;
}, [currentConversation]);
useEffect(() => {
console.log(errors, 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee');
return () => {};
}, [errors])
useEffect(() => {
realtimeAPI.onError(addError.bind(null, 'Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server'));
realtimeAPI.keepAlive(); // Ping Server
// Cleanup function to remove the event listeners when the component is unmounted
return () => {
realtimeAPI.disconnect();
};
}, []);
useEffect(() => {
getConversationsList(); // todo: 和刷新订单会话页面有冲突
getTemplates();
return () => {};
}, []);
// useEffect(() => {
// if (!currentConversation.id && conversationsList.length > 0) {
// switchConversation(conversationsList[0]);
// }
// return () => {};
// }, [conversationsList]);
const poseConversationItemClose = async (item) => {
const res = await postJSON(`${API_HOST}/closeconversation`, { opisn: userId, conversationid: item.sn });
}
const getConversationsList = async () => {
const { result: data } = await fetchJSON(`${API_HOST}/getconversations`, { opisn: userId });
// const _data = [];
const _data = testConversations;
const list = [..._data, ...data.map(ele => ({...ele, customer_name: ele.whatsapp_name.trim()}))];
const dataMapped = list.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
setConversationsList(list);
setActiveConversations({...dataMapped, ...activeConversations});
console.log(list, dataMapped);
if (list && list.length) {
switchConversation(list[0]);
}
};
const [templatesList, setTemplatesList] = useState([]);
const getTemplates = async () => {
const data = await fetchJSON(`${API_HOST}/listtemplates`);
const canUseTemplates = (data?.result?.items || []).filter((_t) => _t.status !== 'REJECTED').map((ele) => ({ ...ele, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) }));
setTemplatesList(canUseTemplates);
};
const [customerOrderProfile, setCustomerProfile] = useState({});
const getCustomerProfile = async (colisn) => {
const { result } = await fetchJSON(`${API_HOST}/getorderinfo`, { colisn });
const data = result?.[0] || {};
setCustomerProfile(data);
if (!isEmpty(data.conversation)) {
setConversationsList((pre) => [...data.conversations, ...pre]);
setCurrentConversation(data.conversation[0]);
const thisCMapped = data.conversation.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
setActiveConversations((pre) => ({ ...pre, ...thisCMapped }));
setMessages([]); // todo: 获取当前会话的历史消息
} else {
// reset chat window
setMessages([]);
if (isEmpty(data.contact?.[0]?.whatsapp_phone_number)) {
setCurrentConversation({ sn: '', customer_name: '', coli_sn: '' });
return false;
}
// 加入新会话
const newChat = {
'sn': uuid(),
// 'opi_sn': 354,
'coli_sn': colisn,
'whatsapp_phone_number': data.contact[0].whatsapp_phone_number,
"last_received_time": '',
"last_send_time": '',
'unread_msg_count': 0,
'whatsapp_name': data.contact[0].name,
'customer_name': data.contact[0].name,
};
setConversationsList((pre) => [...pre, newChat]);
setActiveConversations((pre) => ({ ...pre, [`${newChat.sn}`]: [] }));
setCurrentConversation(pick(newChat, ['sn', 'coli_sn', 'whatsapp_phone_number', 'whatsapp_name', 'customer_name']));
}
};
const switchConversation = (cc) => {
setCurrentID(`${cc.sn}`);
setCurrentConversation({...cc, id: cc.sn, customer_name: cc.whatsapp_name.trim()});
if (isEmpty(cc.coli_sn) || cc.coli_sn === '0') {
setCustomerProfile({});
}
};
// Get customer profile when switching conversation
useEffect(() => {
console.log('currentConversation', currentConversation);
setCurrentID(currentConversation.sn);
setMessages([...(activeConversations[currentConversation.sn] || [])]);
return () => {};
}, [currentConversation]);
/**
* *****************************************************************************************************
* websocket --------------------------------------------------------------------------------------------
* *****************************************************************************************************
*/
const addError = (reason) => {
setErrors((prevErrors) => [...prevErrors, { reason }]);
};
const addMessageToConversations = (targetId, message) => {
setActiveConversations((prevList) => ({
...prevList,
[targetId]: [...(prevList[targetId] || []), message],
}));
// console.log('activeConversations', activeConversations);
if (targetId !== currentID) {
setConversationsList((prevList) => {
return prevList.map((ele) => {
if (ele.sn === targetId) {
return { ...ele, new_msgs: ele.new_msgs + 1, last_received_time: message.date };
}
return ele;
});
});
}
};
useEffect(() => {
console.log(messages, 'messages');
return () => {
}
}, [messages])
const addMessage = (message) => {
addMessageToConversations(message.conversationid, message);
if (message.conversationid === currentID) {
setMessages((prevMessages) => [...prevMessages, message]);
}
};
const updateMessage = (message) => {
let targetMsgs;
setMessages((prevMessages) => {
targetMsgs = prevMessages.map(ele => {
if (ele.id === ele.actionId && ele.actionId === message.actionId) {
return {...ele, id: message.id, status: message.status, dateString: message.dateString};
}
else if (ele.id === message.id) {
return {...ele, id: message.id, status: message.status, dateString: message.dateString};
}
return ele;
});
return targetMsgs;
});
// 更新会话中的消息
const targetId = message.conversationid; // currentConversationRef.current.sn;
setActiveConversations((prevList) => ({
...prevList,
[targetId]: targetMsgs,
}));
// 更新列表的时间
setConversationsList((prevList) => {
return prevList.map((ele) => {
if (ele.sn === targetId) {
return { ...ele, new_msgs: ele.new_msgs + 1, last_received_time: message.date };
}
return ele;
});
});
};
const handleMessage = (data) => {
console.log('handleMessage------------------', );
/**
* ! event handlers in JavaScript capture the environment (including variables) at the time they are defined, not when they are executed.
*/
const { errcode, errmsg, result } = data;
if (!result) {
return false;
}
let resultType = result?.action || result.type;
if (errcode !== 0) {
// addError('Error Connecting to Server');
resultType = 'error';
}
console.log(resultType, 'result.type');
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
console.log('msgRender msgUpdate', msgRender, msgUpdate);
if ( ['whatsapp.message.updated', 'message', 'error'].includes(resultType)) {
updateMessage(msgUpdate);
// return false;
}
if ( ! isEmpty(msgRender)) {
addMessage(msgRender);
}
console.log('handleMessage*******************', );
};
const sendMessage = (msgObj) => {
const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj);
realtimeAPI.sendMessage({ ...contentToSend, opi_sn: userId });
const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj);
console.log(contentToRender, 'contentToRender sendMessage------------------');
addMessage(contentToRender);
};
return {
errors,
messages,
conversationsList,
currentConversation,
sendMessage,
getConversationsList,
switchConversation,
templates: templatesList, // setTemplates, getTemplates,
customerOrderProfile, getCustomerProfile,
poseConversationItemClose
};
};
export const useConversationContext = () => useContext(ConversationContext);
export const ConversationStateContext = createContext();
export const ConversationDispatchContext = createContext();
// test:
const testConversations = [
{
'sn': 3001,
'opi_sn': 354,
'coli_sn': 0,
'whatsapp_phone_number': '+8618777396951',
"last_received_time": new Date().toDateString(),
"last_send_time": new Date().toDateString(),
'unread_msg_count': Math.floor(Math.random() * (100 - 2 + 1) + 2),
'whatsapp_name': 'LiaoYijun',
'customer_name': 'LiaoYijun',
},
{
'sn': 3002,
'opi_sn': 354,
'coli_sn': 0,
'whatsapp_phone_number': '+8613317835586',
"last_received_time": new Date().toDateString(),
"last_send_time": new Date().toDateString(),
'unread_msg_count': Math.floor(Math.random() * (100 - 2 + 1) + 2),
'whatsapp_name': 'QinQianSheng',
'customer_name': 'QinQianSheng',
},
// {
// 'sn': 3003,
// 'opi_sn': 354,
// 'coli_sn': 240129003,
// 'whatsapp_phone_number': '+8618777396951',
// "last_received_time": new Date().toDateString(),
// "last_send_time": new Date().toDateString(),
// 'unread_msg_count': Math.floor(Math.random() * (100 - 2 + 1) + 2),
// 'whatsapp_name': 'LeiYuanTing',
// },
];
export const useConversationState = () => useContext(ConversationStateContext);
export const useConversationDispatch = () => useContext(ConversationDispatchContext);

@ -0,0 +1,334 @@
import { createContext, useContext, useState, useEffect, useRef } from 'react';
import { receivedMsgTypeMapped, sentMsgTypeMapped } from '@/lib/msgUtils';
import { groupBy, isEmpty, pick } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
export const ConversationContext = createContext();
export const useConversationContext = () => useContext(ConversationContext);
export async function fetchJSON(url, data) {
let params = '';
let ifp = '';
if (data) {
params = new URLSearchParams(data).toString();
ifp = params ? '?' : ifp;
}
ifp = url.includes('?') ? '' : ifp;
const host = /^https?:\/\//i.test(url) ? '' : ''; // HT_HOST;
const response = await fetch(`${host}${url}${ifp}${params}`);
return await response.json();
}
export async function postJSON(url, obj) {
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('fetch error:', error);
throw error;
}
}
// const API_HOST = 'http://202.103.68.144:8888';
const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_callback';
export const useConversations = ({loginUser, realtimeAPI}) => {
const { userId } = loginUser;
const [errors, setErrors] = useState([]);
const [messages, setMessages] = useState([]); // active conversation
const [activeConversations, setActiveConversations] = useState({}); // all active conversation
const [currentID, setCurrentID] = useState();
const [conversationsList, setConversationsList] = useState([]); // open conversations
const [currentConversation, setCurrentConversation] = useState({ sn: '', customer_name: '', coli_sn: '' });
const currentConversationRef = useRef(currentConversation);
useEffect(() => {
currentConversationRef.current = currentConversation;
}, [currentConversation]);
useEffect(() => {
console.log(errors, 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee');
return () => {};
}, [errors])
useEffect(() => {
realtimeAPI.onError(addError.bind(null, 'Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server'));
realtimeAPI.keepAlive(); // Ping Server
// Cleanup function to remove the event listeners when the component is unmounted
return () => {
realtimeAPI.disconnect();
};
}, []);
useEffect(() => {
getConversationsList(); // todo: 和刷新订单会话页面有冲突
getTemplates();
return () => {};
}, []);
// useEffect(() => {
// if (!currentConversation.id && conversationsList.length > 0) {
// switchConversation(conversationsList[0]);
// }
// return () => {};
// }, [conversationsList]);
const poseConversationItemClose = async (item) => {
const res = await postJSON(`${API_HOST}/closeconversation`, { opisn: userId, conversationid: item.sn });
}
const getConversationsList = async () => {
const { result: data } = await fetchJSON(`${API_HOST}/getconversations`, { opisn: userId });
// const _data = [];
const _data = testConversations;
const list = [..._data, ...data.map(ele => ({...ele, customer_name: ele.whatsapp_name.trim()}))];
const dataMapped = list.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
setConversationsList(list);
setActiveConversations({...dataMapped, ...activeConversations});
console.log(list, dataMapped);
if (list && list.length) {
switchConversation(list[0]);
}
};
const [templatesList, setTemplatesList] = useState([]);
const getTemplates = async () => {
const data = await fetchJSON(`${API_HOST}/listtemplates`);
const canUseTemplates = (data?.result?.items || []).filter((_t) => _t.status !== 'REJECTED').map((ele) => ({ ...ele, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) }));
setTemplatesList(canUseTemplates);
};
const [customerOrderProfile, setCustomerProfile] = useState({});
const getCustomerProfile = async (colisn) => {
const { result } = await fetchJSON(`${API_HOST}/getorderinfo`, { colisn });
const data = result?.[0] || {};
setCustomerProfile(data);
if (!isEmpty(data.conversation)) {
setConversationsList((pre) => [...data.conversations, ...pre]);
setCurrentConversation(data.conversation[0]);
const thisCMapped = data.conversation.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
setActiveConversations((pre) => ({ ...pre, ...thisCMapped }));
setMessages([]); // todo: 获取当前会话的历史消息
} else {
// reset chat window
setMessages([]);
if (isEmpty(data.contact?.[0]?.whatsapp_phone_number)) {
setCurrentConversation({ sn: '', customer_name: '', coli_sn: '' });
return false;
}
// 加入新会话
const newChat = {
'sn': uuid(),
// 'opi_sn': 354,
'coli_sn': colisn,
'whatsapp_phone_number': data.contact[0].whatsapp_phone_number,
"last_received_time": '',
"last_send_time": '',
'unread_msg_count': 0,
'whatsapp_name': data.contact[0].name,
'customer_name': data.contact[0].name,
};
setConversationsList((pre) => [...pre, newChat]);
setActiveConversations((pre) => ({ ...pre, [`${newChat.sn}`]: [] }));
setCurrentConversation(pick(newChat, ['sn', 'coli_sn', 'whatsapp_phone_number', 'whatsapp_name', 'customer_name']));
}
};
const switchConversation = (cc) => {
setCurrentID(`${cc.sn}`);
setCurrentConversation({...cc, id: cc.sn, customer_name: cc.whatsapp_name.trim()});
if (isEmpty(cc.coli_sn) || cc.coli_sn === '0') {
setCustomerProfile({});
}
};
// Get customer profile when switching conversation
useEffect(() => {
console.log('currentConversation', currentConversation);
setCurrentID(currentConversation.sn);
setMessages([...(activeConversations[currentConversation.sn] || [])]);
return () => {};
}, [currentConversation]);
/**
* *****************************************************************************************************
* websocket --------------------------------------------------------------------------------------------
* *****************************************************************************************************
*/
const addError = (reason) => {
setErrors((prevErrors) => [...prevErrors, { reason }]);
};
const addMessageToConversations = (targetId, message) => {
setActiveConversations((prevList) => ({
...prevList,
[targetId]: [...(prevList[targetId] || []), message],
}));
// console.log('activeConversations', activeConversations);
if (targetId !== currentID) {
setConversationsList((prevList) => {
return prevList.map((ele) => {
if (ele.sn === targetId) {
return { ...ele, new_msgs: ele.new_msgs + 1, last_received_time: message.date };
}
return ele;
});
});
}
};
useEffect(() => {
console.log(messages, 'messages');
return () => {
}
}, [messages])
const addMessage = (message) => {
addMessageToConversations(message.conversationid, message);
if (message.conversationid === currentID) {
setMessages((prevMessages) => [...prevMessages, message]);
}
};
const updateMessage = (message) => {
let targetMsgs;
setMessages((prevMessages) => {
targetMsgs = prevMessages.map(ele => {
if (ele.id === ele.actionId && ele.actionId === message.actionId) {
return {...ele, id: message.id, status: message.status, dateString: message.dateString};
}
else if (ele.id === message.id) {
return {...ele, id: message.id, status: message.status, dateString: message.dateString};
}
return ele;
});
return targetMsgs;
});
// 更新会话中的消息
const targetId = message.conversationid; // currentConversationRef.current.sn;
setActiveConversations((prevList) => ({
...prevList,
[targetId]: targetMsgs,
}));
// 更新列表的时间
setConversationsList((prevList) => {
return prevList.map((ele) => {
if (ele.sn === targetId) {
return { ...ele, new_msgs: ele.new_msgs + 1, last_received_time: message.date };
}
return ele;
});
});
};
const handleMessage = (data) => {
console.log('handleMessage------------------', );
/**
* ! event handlers in JavaScript capture the environment (including variables) at the time they are defined, not when they are executed.
*/
const { errcode, errmsg, result } = data;
if (!result) {
return false;
}
let resultType = result?.action || result.type;
if (errcode !== 0) {
// addError('Error Connecting to Server');
resultType = 'error';
}
console.log(resultType, 'result.type');
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
console.log('msgRender msgUpdate', msgRender, msgUpdate);
if ( ['whatsapp.message.updated', 'message', 'error'].includes(resultType)) {
updateMessage(msgUpdate);
// return false;
}
if ( ! isEmpty(msgRender)) {
addMessage(msgRender);
}
console.log('handleMessage*******************', );
};
const sendMessage = (msgObj) => {
const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj);
realtimeAPI.sendMessage({ ...contentToSend, opi_sn: userId });
const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj);
console.log(contentToRender, 'contentToRender sendMessage------------------');
addMessage(contentToRender);
};
return {
errors,
messages,
conversationsList,
currentConversation,
sendMessage,
getConversationsList,
switchConversation,
templates: templatesList, // setTemplates, getTemplates,
customerOrderProfile, getCustomerProfile,
poseConversationItemClose
};
};
// test:
const testConversations = [
{
'sn': 3001,
'opi_sn': 354,
'coli_sn': 0,
'whatsapp_phone_number': '+8618777396951',
"last_received_time": new Date().toDateString(),
"last_send_time": new Date().toDateString(),
'unread_msg_count': Math.floor(Math.random() * (100 - 2 + 1) + 2),
'whatsapp_name': 'LiaoYijun',
'customer_name': 'LiaoYijun',
},
{
'sn': 3002,
'opi_sn': 354,
'coli_sn': 0,
'whatsapp_phone_number': '+8613317835586',
"last_received_time": new Date().toDateString(),
"last_send_time": new Date().toDateString(),
'unread_msg_count': Math.floor(Math.random() * (100 - 2 + 1) + 2),
'whatsapp_name': 'QinQianSheng',
'customer_name': 'QinQianSheng',
},
// {
// 'sn': 3003,
// 'opi_sn': 354,
// 'coli_sn': 240129003,
// 'whatsapp_phone_number': '+8618777396951',
// "last_received_time": new Date().toDateString(),
// "last_send_time": new Date().toDateString(),
// 'unread_msg_count': Math.floor(Math.random() * (100 - 2 + 1) + 2),
// 'whatsapp_name': 'LeiYuanTing',
// },
];

@ -0,0 +1,99 @@
import initialState from '@/stores/Conversations/initialState';
const NAME_SPACE = 'CONVERSATION/';
const ConversationReducer = (state = initialState, action) => {
switch (action.type) {
case NAME_SPACE + 'INIT_WEBSOCKET':
return { ...state, websocket: action.payload };
case NAME_SPACE + 'SET_CONVERSATION_LIST':
return { ...state, conversationsList: action.payload };
case NAME_SPACE + 'ADD_CONVERSATIONS':
return { ...state, conversationsList: [...action.payload, ...state.conversationsList] };
case NAME_SPACE + 'SET_TEMPLATE_LIST':
return { ...state, templates: action.payload };
case NAME_SPACE + 'SET_CUSTOMER_ORDER_PROFILE':
return { ...state, customerOrderProfile: action.payload };
case NAME_SPACE + 'SET_CURRENT_CONVERSATION': {
// 清空未读
const { conversationsList } = state;
const targetId = action.payload.sn;
const newConversationList = conversationsList.map((ele) => {
if (ele.sn === targetId) {
return { ...ele, unread_msg_count: 0 };
}
return ele;
});
return { ...state, currentConversation: action.payload, conversationsList: newConversationList };
}
case NAME_SPACE + 'SET_ACTIVE_CONVERSATIONS': {
const { activeConversations } = state;
return { ...state, activeConversations: { ...activeConversations, ...action.payload } };
}
case NAME_SPACE + 'UPDATE_SENT_MESSAGE_ITEM': {
console.log('UPDATE_SENT_MESSAGE_ITEM-----------------------------------------------------------------');
// 更新会话中的消息
const { activeConversations, conversationsList } = state;
const message = action.payload;
const targetId = message.conversationid;
const targetMsgs = (activeConversations[targetId] || []).map((ele) => {
if (ele.id === ele.actionId && ele.actionId === message.actionId) {
return { ...ele, id: message.id, status: message.status, dateString: message.dateString };
} else if (ele.id === message.id) {
return { ...ele, id: message.id, status: message.status, dateString: message.dateString };
}
return ele;
});
// 更新列表的时间
const newConversationList = conversationsList.map((ele) => {
if (ele.sn === targetId && message.type !== 'error') {
return { ...ele, last_received_time: message.date };
}
return ele;
});
return {
...state,
activeConversations: { ...state.activeConversations, [targetId]: targetMsgs },
conversationsList: newConversationList,
};
}
case NAME_SPACE + 'SENT_NEW_MESSAGE':
case NAME_SPACE + 'RECEIVED_NEW_MESSAGE': {
const { activeConversations, conversationsList, currentConversation } = state;
const { targetId, message } = action.payload;
const targetMsgs = activeConversations[targetId] || [];
console.log(activeConversations, targetId, 'sent sent sent');
const newConversationList = conversationsList.map((ele) => {
if (ele.sn === targetId) {
return {
...ele,
last_received_time: message.date,
unread_msg_count: ele.sn === currentConversation.sn ? ele.unread_msg_count + 1 : ele.unread_msg_count,
};
}
return ele;
});
return {
...state,
activeConversations: { ...activeConversations, [targetId]: [...targetMsgs, message] },
conversationsList: newConversationList,
};
}
case NAME_SPACE + 'ADD_ERROR': {
console.log('add error', state.errors, action.payload);
const prelist = state.errors || [];
return { ...state, errors: [...prelist, action.payload] };
}
default:
// throw new Error(`Unknown action: ${action.type}`);
return state;
}
};
export default ConversationReducer;

@ -0,0 +1,13 @@
const initialState = {
websocket: null,
errors: [], // 错误信息
conversationsList: [], // 对话列表
templates: [],
customerOrderProfile: {},
activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: [] }
currentConversation: {}, // 当前对话
};
export default initialState;

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { useParams, useNavigate } from "react-router-dom";
import { Layout, List, Avatar, Flex, Typography, Spin } from 'antd';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Layout, Avatar, Flex, Typography, Spin } from 'antd';
import Messages from './Components/Messages';
import InputBox from './Components/InputBox';
import ConversationsList from './Components/ConversationsList';
@ -8,6 +8,8 @@ import CustomerProfile from './Components/CustomerProfile';
import LocalTimeClock from './Components/LocalTimeClock';
import { useConversationContext } from '@/stores/Conversations/ConversationContext';
import { sentNewMessage } from '@/stores/Conversations/ConversationActions';
import { sentMsgTypeMapped } from '@/lib/msgUtils';
import './Conversations.css';
import { useAuthContext } from '@/stores/AuthContext.js';
@ -17,24 +19,29 @@ const { Sider, Content, Header, Footer } = Layout;
/**
*
*/
const ChatWindow = (() => {
const { order_sn } = useParams();
const { loginUser: currentUser } = useAuthContext();
const { errors, sendMessage, currentConversation, customerOrderProfile: orderInfo, getCustomerProfile } = useConversationContext();
const { quotes, contact, last_contact, ...order } = orderInfo;
const ChatWindow = () => {
const { loginUser } = useAuthContext();
const { userId } = loginUser;
const { dispatch, websocket, errors, currentConversation } = useConversationContext();
console.log(errors, 'errors;;;;;;;;;;;;;;;;;;;;;;;;');
// console.log(order_sn, currentUser);
useEffect(() => {
if (order_sn) {
getCustomerProfile(order_sn);
}
console.log(errors, 'errors 222;;;;;;;;;;;;;;;;;;;;;;;;');
return () => {};
}, [order_sn]);
}, []);
const sendMessage = (msgObj) => {
console.log('sendMessage------------------', msgObj);
const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj);
websocket.sendMessage({ ...contentToSend, opi_sn: userId });
const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj);
console.log(contentToRender, 'contentToRender sendMessage------------------');
dispatch(sentNewMessage(contentToRender));
};
return (
<Spin spinning={false} tip={'正在连接...'} >
<Spin spinning={false} tip={'正在连接...'}>
<Layout className='h-screen chatwindow-wrapper' style={{ maxHeight: 'calc(100% - 198px)', height: 'calc(100% - 198px)' }}>
<Sider width={240} theme={'light'} className='h-full overflow-y-auto' style={{ maxHeight: 'calc(100vh - 198px)', height: 'calc(100vh - 198px)' }}>
<ConversationsList />
@ -51,9 +58,7 @@ const ChatWindow = (() => {
<Typography.Text>{currentConversation.whatsapp_phone_number}</Typography.Text>
</Flex>
<Flex vertical={true} justify='space-between'>
<Typography.Text>
{/* <LocalTimeClock /> <Typography.Text strong>{order?.location} </Typography.Text> */}
</Typography.Text>
<Typography.Text>{/* <LocalTimeClock /> <Typography.Text strong>{order?.location} </Typography.Text> */}</Typography.Text>
{/* <Typography.Text>{customerDateTime}</Typography.Text> */}
</Flex>
</Flex>
@ -76,6 +81,6 @@ const ChatWindow = (() => {
</Layout>
</Spin>
);
});
};
export default ChatWindow;

@ -1,20 +1,22 @@
import { useRef, useEffect, useState } from 'react';
import { useNavigate } from "react-router-dom";
import { useParams, useNavigate } from "react-router-dom";
import { List, Avatar, Flex } from 'antd';
import { useConversationContext } from '@/stores/Conversations/ConversationContext';
import { ChatItem, ChatList } from 'react-chat-elements';
import { useGetJson } from '@/hooks/userFetch';
import { fetchCustomerProfile, receivedCustomerProfile, setCurrentConversation, addConversationList, setActiveConversations } from '@/stores/Conversations/ConversationActions'
import { ChatList } from 'react-chat-elements';
import { isEmpty, pick } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
/**
* []
*/
const Conversations = (() => {
const { order_sn } = useParams();
const navigate = useNavigate();
const { switchConversation, conversationsList, poseConversationItemClose } = useConversationContext();
// console.log(conversationsList);
const { dispatch, conversationsList, } = useConversationContext();
const [chatlist, setChatlist] = useState([]);
useEffect(() => {
setChatlist(
(conversationsList || []).map((item) => ({
(conversationsList).map((item) => ({
...item,
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${item.whatsapp_name.trim() || item.whatsapp_phone_number}`,
id: item.sn,
@ -22,7 +24,7 @@ const Conversations = (() => {
title: item.whatsapp_name.trim() || item.whatsapp_phone_number,
// subtitle: item.whatsapp_phone_number,
// subtitle: item.lastMessage,
date: item.last_received_time, // last_send_time
date: item.last_received_time, // last_send_time // todo: GMT+8
unread: item.unread_msg_count,
showMute: true,
muted: false,
@ -36,6 +38,53 @@ const Conversations = (() => {
return () => {};
}, [conversationsList]);
useEffect(() => {
if (order_sn) {
getCustomerProfile(order_sn);
}
return () => {};
}, [order_sn]);
const getCustomerProfile = async (colisn) => {
const data = await fetchCustomerProfile(colisn);
dispatch(receivedCustomerProfile(data));
if (!isEmpty(data.conversation)) {
dispatch(addConversationList(data.conversations));
dispatch(setCurrentConversation(data.conversation[0]));
const thisCMapped = data.conversation.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
dispatch(setActiveConversations(thisCMapped));
} else {
// reset chat window
if (isEmpty(data.contact?.[0]?.whatsapp_phone_number)) {
dispatch(setCurrentConversation({ sn: '', customer_name: '', coli_sn: '' }));
return false;
}
//
const newChat = {
'sn': uuid(),
// 'opi_sn': 354,
'coli_sn': colisn,
'whatsapp_phone_number': data.contact[0].whatsapp_phone_number,
"last_received_time": '',
"last_send_time": '',
'unread_msg_count': 0,
'whatsapp_name': data.contact[0].name,
'customer_name': data.contact[0].name,
};
dispatch(addConversationList([newChat]));
dispatch(setActiveConversations({ [`${newChat.sn}`]: []}));
const newCurrent = pick(newChat, ['sn', 'coli_sn', 'whatsapp_phone_number', 'whatsapp_name', 'customer_name']);
dispatch(setCurrentConversation(newCurrent));
}
}
const switchConversation = (item) => {
dispatch(setCurrentConversation(item));
if (isEmpty(item.coli_sn) || item.coli_sn === '0') {
dispatch(receivedCustomerProfile({}));
}
};
const onSwitchConversation = (item) => {
switchConversation(item);
if (item.coli_sn) {
@ -43,6 +92,8 @@ const Conversations = (() => {
}
}
const onCloseConversationItem = (item) => { }
return (
<>
<ChatList className='' dataSource={chatlist} onClick={(item) => onSwitchConversation(item)}
@ -50,7 +101,7 @@ const Conversations = (() => {
// console.log(item, i, e);
// return ( <p>ppp</p> )
// }}
onClickMute={poseConversationItemClose}
onClickMute={onCloseConversationItem}
/>
</>
);

@ -21,7 +21,7 @@ const orderStatus = [
const { Meta } = Card;
const CustomerProfile = (({ customer }) => {
const CustomerProfile = (() => {
const { errors, customerOrderProfile: orderInfo } = useConversationContext();
const { loginUser: currentUser } = useAuthContext();
const { quotes, contact, last_contact, ...order } = orderInfo;

@ -8,7 +8,7 @@ import { v4 as uuid } from 'uuid';
import { whatsappTemplatesParamMapped } from '@/lib/msgUtils';
const InputBox = ({ onSend }) => {
const { currentConversation, templates } = useConversationContext();
const { dispatch, currentConversation, templates } = useConversationContext();
const [textContent, setTextContent] = useState('');
const talkabled = !isEmpty(currentConversation.sn);

@ -4,9 +4,9 @@ import { MessageBox } from 'react-chat-elements';
import { useConversationContext } from '@/stores/Conversations/ConversationContext';
const Messages = (() => {
const { messages: messagesList } = useConversationContext();
// const messagesList = parseMessage(messages);
// console.log(messagesList);
const { activeConversations, currentConversation } = useConversationContext();
const messagesList = activeConversations[currentConversation.sn] || [];
console.log('messagesList----------------------------------------------------', messagesList);
const messagesEndRef = useRef(null);
useEffect(() => {

@ -1,30 +1,97 @@
import { useContext } from 'react';
import { ConversationContext, useConversations, } from '@/stores/Conversations/ConversationContext';
import { useContext, useReducer, useEffect } from 'react';
import { ConversationContext, ConversationStateContext, ConversationDispatchContext } from '@/stores/Conversations/ConversationContext';
import ConversationReducer from '@/stores/Conversations/ConversationReducer';
import {
initWebsocket,
addError,
fetchConversationsList,
fetchTemplates,
receivedConversationList,
receivedTemplates,
setActiveConversations,
updateMessageItem,
receivedNewMessage,
} from '@/stores/Conversations/ConversationActions';
import initialState from '@/stores/Conversations/initialState';
import { AuthContext } from '@/stores/AuthContext';
import { RealTimeAPI } from '@/lib/realTimeAPI';
import { receivedMsgTypeMapped } from '@/lib/msgUtils';
import { isEmpty } from '@/utils/utils';
// const WS_URL = 'ws://202.103.68.144:8888/whatever/';
// const WS_URL = 'ws://120.79.9.217:10022/whatever/';
const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_callback'; // prod:
export const ConversationProvider = ({ children, loginUser, realtimeAPI }) => {
const ConversationProvider = ({ children }) => {
const { loginUser } = useContext(AuthContext);
const { userId } = loginUser;
console.log('====', loginUser);
const conversations = useConversations({loginUser, realtimeAPI});
return <ConversationContext.Provider value={conversations}>{children}</ConversationContext.Provider>;
};
const realtimeAPI = new RealTimeAPI({ url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, protocol: 'WhatsApp' });
const [state, dispatch] = useReducer(ConversationReducer, { ...initialState, websocket: null });
// export default ConversationProvider;
console.log('ConversationProvider', state, dispatch);
const AuthAndConversationProvider = ({ children }) => {
const { loginUser } = useContext(AuthContext);
const {userId} = loginUser;
const realtimeAPI = new RealTimeAPI({ url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, protocol: 'WhatsApp' });
useEffect(() => {
console.log('invoke provider');
realtimeAPI.onError(() => dispatch(addError('Error')));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(() => dispatch(addError('Connection broken')));
dispatch(initWebsocket(realtimeAPI));
return () => {
realtimeAPI.disconnect();
};
}, []);
useEffect(() => {
fetchConversationsList(userId).then((data) => {
console.log(data, 'llllllllllllllllllllllll');
const dataMapped = data.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
dispatch(receivedConversationList(data));
dispatch(setActiveConversations(dataMapped));
}); // todo:
fetchTemplates().then((data) => dispatch(receivedTemplates(data)));
return () => {};
}, []);
const handleMessage = (data) => {
console.log('handleMessage------------------');
console.log(data);
const { errcode, errmsg, result } = data;
return (
<ConversationProvider loginUser={loginUser} realtimeAPI={realtimeAPI} >
{children}
</ConversationProvider>
);
if (!result) {
return false;
}
let resultType = result?.action || result.type;
if (errcode !== 0) {
// addError('Error Connecting to Server');
resultType = 'error';
}
console.log(resultType, 'result.type');
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
console.log('msgRender msgUpdate', msgRender, msgUpdate);
if (['whatsapp.message.updated', 'message', 'error'].includes(resultType)) {
dispatch(updateMessageItem(msgUpdate));
// return false;
}
if (!isEmpty(msgRender)) {
dispatch(receivedNewMessage(msgRender.conversationid, msgRender));
}
console.log('handleMessage*******************');
};
return <ConversationContext.Provider value={{ ...state, dispatch }}>{children}</ConversationContext.Provider>;
// return (
// <ConversationDispatchContext.Provider value={dispatch}>
// <ConversationStateContext.Provider value={{...state}}>{children}</ConversationStateContext.Provider>
// </ConversationDispatchContext.Provider>
// );
};
export default AuthAndConversationProvider;
export default ConversationProvider;

Loading…
Cancel
Save