|
|
|
import { create } from 'zustand';
|
|
|
|
import { RealTimeAPI } from '@/channel/realTimeAPI';
|
|
|
|
import { olog, isEmpty, groupBy, sortArrayByOrder, logWebsocket, pick, sortKeys, omit, sortObjectsByKeysMap, clean7DaysWebsocketLog } from '@/utils/commons';
|
|
|
|
import { receivedMsgTypeMapped, handleNotification } from '@/channel/bubbleMsgUtils';
|
|
|
|
import { fetchConversationsList, fetchTemplates, fetchConversationsSearch, UNREAD_MARK, fetchTags } from '@/actions/ConversationActions';
|
|
|
|
import { devtools } from 'zustand/middleware';
|
|
|
|
import { WS_URL, DATETIME_FORMAT } from '@/config';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
|
const replaceObjectsByKey = (arr1, arr2, key) => {
|
|
|
|
const map2 = new Map(arr2.map(ele => [ele[key], ele]));
|
|
|
|
return arr1.map(item => map2.has(item[key]) ? map2.get(item[key]) : item);
|
|
|
|
}
|
|
|
|
|
|
|
|
const sortConversationList = (list) => {
|
|
|
|
const mergedListMapped = groupBy(list, 'top_state');
|
|
|
|
const topValOrder = Object.keys(mergedListMapped).filter(ss => ss !== '1').sort((a, b) => b - a);
|
|
|
|
const pagelist = topValOrder.reduce((r, topVal) => r.concat(mergedListMapped[String(topVal)]), []);
|
|
|
|
return {
|
|
|
|
topList: mergedListMapped['1'] || [],
|
|
|
|
pageList: pagelist,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// const WS_URL = 'ws://202.103.68.144:8888/whatever/';
|
|
|
|
// const WS_URL = 'ws://120.79.9.217:10022/whatever/';
|
|
|
|
const conversationRow = {
|
|
|
|
sn: '',
|
|
|
|
opi_sn: '',
|
|
|
|
coli_sn: '',
|
|
|
|
coli_id: '',
|
|
|
|
last_received_time: '',
|
|
|
|
last_send_time: '',
|
|
|
|
unread_msg_count: '',
|
|
|
|
whatsapp_name: '',
|
|
|
|
customer_name: '',
|
|
|
|
whatsapp_phone_number: '',
|
|
|
|
top_state: 0,
|
|
|
|
session_type: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
const initialConversationState = {
|
|
|
|
// websocket: null,
|
|
|
|
// websocketOpened: null,
|
|
|
|
// websocketRetrying: null,
|
|
|
|
// websocketRetrytimes: null,
|
|
|
|
|
|
|
|
errors: [], // 错误信息
|
|
|
|
initialState: false,
|
|
|
|
|
|
|
|
// templates: [],
|
|
|
|
|
|
|
|
closedConversationsList: [], // 已关闭的对话列表
|
|
|
|
conversationsList: [], // 对话列表
|
|
|
|
topList: [],
|
|
|
|
pageList: [],
|
|
|
|
currentConversation: {}, // 当前对话
|
|
|
|
|
|
|
|
activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: <messageItem>[] }
|
|
|
|
|
|
|
|
referenceMsg: {},
|
|
|
|
complexMsg: {},
|
|
|
|
|
|
|
|
totalNotify: 0,
|
|
|
|
msgListLoading: false,
|
|
|
|
|
|
|
|
detailPopupOpen: false,
|
|
|
|
|
|
|
|
wai: {},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const globalNotifySlice = (set) => ({
|
|
|
|
globalNotify: [],
|
|
|
|
setGlobalNotify: (notify) => set(() => ({ globalNotify: notify })),
|
|
|
|
addGlobalNotify: (notify) => set((state) => ({ globalNotify: [notify, ...state.globalNotify] })),
|
|
|
|
removeGlobalNotify: (id) => set((state) => ({ globalNotify: state.globalNotify.filter(item => item.id !== id) })),
|
|
|
|
clearGlobalNotify: () => set(() => ({ globalNotify: [] })),
|
|
|
|
})
|
|
|
|
|
|
|
|
const waiSlice = (set) => ({
|
|
|
|
wai: {},
|
|
|
|
setWai: (wai) => set({ wai }),
|
|
|
|
});
|
|
|
|
|
|
|
|
// 顾问的自定义标签
|
|
|
|
const tagsSlice = (set) => ({
|
|
|
|
tags: [],
|
|
|
|
setTags: (tags) => set({ tags }),
|
|
|
|
addTag: (tag) => set((state) => ({ tags: [...state.tags, tag] })),
|
|
|
|
removeTag: (tag) => set((state) => ({ tags: state.tags.filter((t) => t.key !== tag.key) })),
|
|
|
|
updateTag: (tag) => set((state) => ({ tags: state.tags.map((t) => (t.key === tag.key ? tag : t)) })),
|
|
|
|
resetTags: () => set({ tags: [] }),
|
|
|
|
});
|
|
|
|
|
|
|
|
// 会话筛选
|
|
|
|
const filterObj = {
|
|
|
|
pagesize: '',
|
|
|
|
search: '',
|
|
|
|
otype: '',
|
|
|
|
tags: [],
|
|
|
|
loadNextPage: true,
|
|
|
|
lastpagetime: '',
|
|
|
|
lastactivetime: '', // dayjs().subtract(30, "days").format('YYYY-MM-DD 00:00'), // 30 days
|
|
|
|
};
|
|
|
|
const filterSlice = (set) => ({
|
|
|
|
filter: structuredClone(filterObj),
|
|
|
|
setFilter: (filter) => set(state => ({ filter: { ...state.filter, ...filter } })),
|
|
|
|
setFilterSearch: (search) => set((state) => ({ filter: { ...state.filter, search } })),
|
|
|
|
setFilterOtype: (otype) => set((state) => ({ filter: {...state.filter, otype } })),
|
|
|
|
setFilterTags: (tags) => set((state) => ({ filter: {...state.filter, tags } })),
|
|
|
|
setFilterLoadNextPage: (loadNextPage) => set((state) => ({ filter: {...state.filter, loadNextPage } })),
|
|
|
|
resetFilter: () => set({ filter: structuredClone(filterObj) }),
|
|
|
|
})
|
|
|
|
// WABA 模板
|
|
|
|
const templatesSlice = (set) => ({
|
|
|
|
templates: [],
|
|
|
|
setTemplates: (templates) => set({ templates }),
|
|
|
|
});
|
|
|
|
|
|
|
|
const websocketSlice = (set, get) => ({
|
|
|
|
websocket: null,
|
|
|
|
websocketOpened: null,
|
|
|
|
websocketRetrying: null,
|
|
|
|
websocketRetrytimes: null,
|
|
|
|
|
|
|
|
setWebsocket: (websocket) => set({ websocket }),
|
|
|
|
setWebsocketOpened: (opened) => set({ websocketOpened: opened }),
|
|
|
|
setWebsocketRetrying: (retrying) => set({ websocketRetrying: retrying }),
|
|
|
|
setWebsocketRetrytimes: (retrytimes) => set({ websocketRetrytimes: retrytimes, websocketRetrying: retrytimes > 0 }),
|
|
|
|
|
|
|
|
connectWebsocket: (userId) => {
|
|
|
|
const { setWebsocket, setWebsocketOpened, setWebsocketRetrytimes, addError, handleMessage, activeConversations } = get();
|
|
|
|
|
|
|
|
const realtimeAPI = new RealTimeAPI(
|
|
|
|
{
|
|
|
|
url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`,
|
|
|
|
protocol: 'WhatsApp',
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
setWebsocketOpened(true);
|
|
|
|
setWebsocketRetrytimes(0);
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
setWebsocketOpened(false);
|
|
|
|
const newMsgList = Object.keys(activeConversations).reduce((acc, key) => {
|
|
|
|
const newMsgList = activeConversations[key].slice(-10);
|
|
|
|
acc[key] = newMsgList;
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
set({ activeConversations: newMsgList, currentConversation: {} });
|
|
|
|
},
|
|
|
|
(n) => setWebsocketRetrytimes(n)
|
|
|
|
);
|
|
|
|
|
|
|
|
realtimeAPI.onError(() => addError('Error'));
|
|
|
|
realtimeAPI.onMessage(handleMessage);
|
|
|
|
realtimeAPI.onCompletion(() => addError('Connection broken'));
|
|
|
|
|
|
|
|
olog('Connecting to websocket...');
|
|
|
|
setWebsocket(realtimeAPI);
|
|
|
|
},
|
|
|
|
disconnectWebsocket: () => {
|
|
|
|
const { websocket } = get();
|
|
|
|
if (websocket) websocket.disconnect();
|
|
|
|
return set({ websocket: null });
|
|
|
|
},
|
|
|
|
reconnectWebsocket: (userId) => {
|
|
|
|
const { disconnectWebsocket, connectWebsocket } = get();
|
|
|
|
disconnectWebsocket();
|
|
|
|
setTimeout(() => {
|
|
|
|
connectWebsocket(userId);
|
|
|
|
}, 500);
|
|
|
|
},
|
|
|
|
handleMessage: (data) => {
|
|
|
|
// olog('websocket Message IN ⬇', JSON.stringify(data, null, 2));
|
|
|
|
logWebsocket(data, 'I');
|
|
|
|
// olog('websocket Messages ----', data);
|
|
|
|
// console.log(data);
|
|
|
|
const { updateMessageItem, sentOrReceivedNewMessage, addGlobalNotify, setWai, addToConversationList } = get();
|
|
|
|
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';
|
|
|
|
}
|
|
|
|
|
|
|
|
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',
|
|
|
|
'email.updated', 'wai.message.updated',
|
|
|
|
].includes(resultType) && !isEmpty(msgUpdate)) {
|
|
|
|
updateMessageItem(msgUpdate);
|
|
|
|
}
|
|
|
|
if (!isEmpty(msgRender)) {
|
|
|
|
sentOrReceivedNewMessage(msgRender.conversationid, msgRender);
|
|
|
|
handleNotification(msgRender.senderName, {
|
|
|
|
body: msgRender?.text || `[ ${msgRender.type} ]`,
|
|
|
|
...(msgRender.type === 'photo' ? { image: msgRender.data.uri } : {}),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// 其他通知, 不是消息
|
|
|
|
if ([
|
|
|
|
'email.action.received',
|
|
|
|
].includes(resultType)) {
|
|
|
|
const msgNotify = receivedMsgTypeMapped[resultType].contentToNotify(msgObj);
|
|
|
|
addGlobalNotify(msgNotify);
|
|
|
|
}
|
|
|
|
// WhatsApp creds update
|
|
|
|
if ([
|
|
|
|
'wai.creds.update'
|
|
|
|
].includes(resultType)) {
|
|
|
|
const _data = receivedMsgTypeMapped[resultType].getMsg(result);
|
|
|
|
setWai(_data)
|
|
|
|
if (['offline', 'close'].includes(_data.status)) {
|
|
|
|
const msgNotify = receivedMsgTypeMapped[resultType].contentToNotify(msgObj);
|
|
|
|
addGlobalNotify(msgNotify);
|
|
|
|
}
|
|
|
|
// setTimeout(() => {
|
|
|
|
// setWai({}); // 60s 后清空
|
|
|
|
// }, 60_000);
|
|
|
|
}
|
|
|
|
// 会话表 更新
|
|
|
|
if (['session.new', 'session.updated'].includes(resultType)) {
|
|
|
|
const sessionList = receivedMsgTypeMapped[resultType].getMsg(result);
|
|
|
|
addToConversationList(sessionList, 'top');
|
|
|
|
}
|
|
|
|
// console.log('handleMessage*******************');
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const referenceMsgSlice = (set) => ({
|
|
|
|
referenceMsg: {},
|
|
|
|
setReferenceMsg: (referenceMsg) => set({ referenceMsg }),
|
|
|
|
});
|
|
|
|
|
|
|
|
const complexMsgSlice = (set) => ({
|
|
|
|
complexMsg: {},
|
|
|
|
setComplexMsg: (complexMsg) => set({ complexMsg }),
|
|
|
|
});
|
|
|
|
|
|
|
|
const conversationSlice = (set, get) => ({
|
|
|
|
conversationsListLoading: false,
|
|
|
|
conversationsList: [],
|
|
|
|
currentConversation: {},
|
|
|
|
closedConversationsList: [],
|
|
|
|
|
|
|
|
topList: [],
|
|
|
|
pageList: [],
|
|
|
|
|
|
|
|
setConversationsListLoading: (conversationsListLoading) => set({ conversationsListLoading }),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 首次加载
|
|
|
|
* 搜索结果
|
|
|
|
*/
|
|
|
|
setConversationsList: (conversationsList) => {
|
|
|
|
const { activeConversations, currentConversation } = get();
|
|
|
|
// 让当前会话显示在页面上
|
|
|
|
let _tmpCurrentMsgs = [];
|
|
|
|
if (currentConversation.sn) {
|
|
|
|
_tmpCurrentMsgs = activeConversations[currentConversation.sn];
|
|
|
|
}
|
|
|
|
const conversationsMapped = conversationsList.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
|
|
|
|
if (currentConversation.sn) {
|
|
|
|
const hasCurrent = Object.keys(conversationsMapped).findIndex(sn => Number(sn) === Number(currentConversation.sn)) !== -1;
|
|
|
|
conversationsMapped[currentConversation.sn] = _tmpCurrentMsgs;
|
|
|
|
const _len = hasCurrent ? 0 : conversationsList.unshift(currentConversation);
|
|
|
|
}
|
|
|
|
|
|
|
|
const { topList, pageList } = sortConversationList(conversationsList);
|
|
|
|
|
|
|
|
return set({
|
|
|
|
topList,
|
|
|
|
// conversationsList: conversationsTopStateMapped[0],
|
|
|
|
pageList,
|
|
|
|
conversationsList,
|
|
|
|
activeConversations: { ...conversationsMapped, ...activeConversations }
|
|
|
|
})
|
|
|
|
},
|
|
|
|
setClosedConversationList: (closedConversationsList) => {
|
|
|
|
const { activeConversations, } = get();
|
|
|
|
const listMapped = closedConversationsList.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
|
|
|
|
return set({ closedConversationsList, activeConversations: { ...activeConversations, ...listMapped } });
|
|
|
|
},
|
|
|
|
addToConversationList: (newList, position='top') => {
|
|
|
|
const { activeConversations, conversationsList, currentConversation } = get();
|
|
|
|
// const conversationsIds = Object.keys(activeConversations);
|
|
|
|
const conversationsIds = conversationsList.map((chatItem) => `${chatItem.sn}`);
|
|
|
|
const newConversations = newList.filter((conversation) => !conversationsIds.includes(`${conversation.sn}`));
|
|
|
|
const newConversationsMapped = newConversations.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
|
|
|
|
|
|
|
|
const newListIds = newList.map((chatItem) => `${chatItem.sn}`);
|
|
|
|
const withoutNew = conversationsList.filter((item) => !newListIds.includes(`${item.sn}`));
|
|
|
|
|
|
|
|
const updateList = replaceObjectsByKey(conversationsList, newList, 'sn');
|
|
|
|
|
|
|
|
const mergedList = position==='top' ? [...newList, ...withoutNew] : [...updateList, ...newConversations];
|
|
|
|
const mergedListMsgs = { ...newConversationsMapped, ...activeConversations, };
|
|
|
|
|
|
|
|
const needUpdateCurrent = -1 !== newList.findIndex(row => Number(row.sn) === Number(currentConversation.sn));
|
|
|
|
const updateCurrent = needUpdateCurrent ? { currentConversation: newList.find(row => Number(row.sn) === Number(currentConversation.sn)) } : {};
|
|
|
|
// 让当前会话显示在页面上
|
|
|
|
if (currentConversation.sn) {
|
|
|
|
const hasCurrent = -1 !== Object.keys(mergedListMsgs).findIndex(sn => Number(sn) === Number(currentConversation.sn));
|
|
|
|
hasCurrent ? 0 : mergedList.unshift(currentConversation);
|
|
|
|
}
|
|
|
|
const refreshTotalNotify = mergedList.reduce((r, c) => r+(c.unread_msg_count === UNREAD_MARK ? 0 : c.unread_msg_count), 0);
|
|
|
|
|
|
|
|
const { topList, pageList } = sortConversationList(mergedList)
|
|
|
|
|
|
|
|
return set((state) => ({
|
|
|
|
...updateCurrent,
|
|
|
|
topList,
|
|
|
|
pageList,
|
|
|
|
conversationsList: mergedList,
|
|
|
|
activeConversations: mergedListMsgs,
|
|
|
|
totalNotify: refreshTotalNotify,
|
|
|
|
// totalNotify: state.totalNotify + newConversations.map((ele) => ele.unread_msg_count).reduce((acc, cur) => acc + (cur || 0), 0),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
delConversationitem: (conversation) => {
|
|
|
|
const { conversationsList, activeConversations } = get();
|
|
|
|
const targetId = conversation.sn;
|
|
|
|
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
|
|
|
|
conversationsList.splice(targetIndex, 1);
|
|
|
|
const { topList, pageList } = sortConversationList(conversationsList)
|
|
|
|
|
|
|
|
return set({
|
|
|
|
topList,
|
|
|
|
pageList,
|
|
|
|
conversationsList: [...conversationsList],
|
|
|
|
activeConversations: { ...activeConversations, [`${targetId}`]: [] },
|
|
|
|
currentConversation: {},
|
|
|
|
});
|
|
|
|
},
|
|
|
|
setCurrentConversation: (conversation) => {
|
|
|
|
// 清空未读
|
|
|
|
const { conversationsList } = get();
|
|
|
|
const targetId = conversation.sn;
|
|
|
|
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
|
|
|
|
const targetItemFromList = conversationsList.find((ele) => String(ele.sn) === String(targetId));
|
|
|
|
targetIndex !== -1
|
|
|
|
? conversationsList.splice(targetIndex, 1, {
|
|
|
|
...conversationsList[targetIndex],
|
|
|
|
unread_msg_count: 0,
|
|
|
|
})
|
|
|
|
: null;
|
|
|
|
const mergedListMapped = groupBy(conversationsList, 'top_state');
|
|
|
|
|
|
|
|
return set((state) => ({
|
|
|
|
totalNotify: state.totalNotify - (conversation.unread_msg_count || 0),
|
|
|
|
currentConversation: Object.assign({}, conversation, targetItemFromList),
|
|
|
|
referenceMsg: {},
|
|
|
|
// topList: mergedListMapped['1'] || [],
|
|
|
|
// pageList: mergedListMapped['0'] || [],
|
|
|
|
// conversationsList: [...conversationsList],
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
updateCurrentConversation: (conversation) => {
|
|
|
|
const { updateConversationItem, currentConversation } = get();
|
|
|
|
updateConversationItem({...currentConversation, ...conversation})
|
|
|
|
|
|
|
|
return set((state) => ({ currentConversation: { ...state.currentConversation, ...conversation } }))
|
|
|
|
},
|
|
|
|
updateConversationItem: (conversation) => {
|
|
|
|
const { conversationsList } = get();
|
|
|
|
const targetId = conversation.sn;
|
|
|
|
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
|
|
|
|
targetIndex !== -1
|
|
|
|
? conversationsList.splice(targetIndex, 1, {
|
|
|
|
...conversationsList[targetIndex],
|
|
|
|
...conversation,
|
|
|
|
})
|
|
|
|
: null;
|
|
|
|
const { topList, pageList } = sortConversationList(conversationsList)
|
|
|
|
|
|
|
|
return set({
|
|
|
|
topList,
|
|
|
|
pageList,
|
|
|
|
conversationsList: [...conversationsList]
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const messageSlice = (set, get) => ({
|
|
|
|
totalNotify: 0,
|
|
|
|
msgListLoading: false,
|
|
|
|
activeConversations: {},
|
|
|
|
refreshTotalNotify: () => set((state) => ({ totalNotify: state.conversationsList.reduce((r, c) => r+(c.unread_msg_count === UNREAD_MARK ? 0 : c.unread_msg_count), 0) })),
|
|
|
|
setMsgLoading: (msgListLoading) => set({ msgListLoading }),
|
|
|
|
receivedMessageList: (conversationid, msgList) =>
|
|
|
|
set((state) => ({
|
|
|
|
// msgListLoading: false,
|
|
|
|
activeConversations: { ...state.activeConversations, [String(conversationid)]: msgList },
|
|
|
|
})),
|
|
|
|
updateMessageItem: (message) => {
|
|
|
|
// msgUpdate
|
|
|
|
// console.log('UPDATE_SENT_MESSAGE_ITEM-----------------------------------------------------------------', message);
|
|
|
|
// 更新会话中的消息
|
|
|
|
const { activeConversations, conversationsList, currentConversation, setFilter } = get();
|
|
|
|
const targetId = message.conversationid;
|
|
|
|
const targetMsgs = (activeConversations[String(targetId)] || []).map((ele) => {
|
|
|
|
// 更新状态
|
|
|
|
// * 已读的不再更新状态, 有时候投递结果在已读之后返回
|
|
|
|
// if (ele.id === ele.actionId && ele.actionId === message.actionId) {
|
|
|
|
if (ele.actionId === message.actionId && !isEmpty(ele.actionId) && !isEmpty(message.actionId)) {
|
|
|
|
// console.log('actionID', message.actionId, ele.actionId)
|
|
|
|
// WABA: 同步返回, 根据actionId 更新消息的id;
|
|
|
|
const toUpdateFields = pick(message, ['msgOrigin', 'id', 'status', 'dateString', 'replyButton', 'coli_id', 'coli_sn']);
|
|
|
|
return { ...ele, ...toUpdateFields, status: ele.status === 'read' ? ele.status : message.status, };
|
|
|
|
} else if (String(ele.id) === String(message.id)) {
|
|
|
|
// console.log('id', message.id, ele.id)
|
|
|
|
// WABA: 异步的后续状态更新, id已更新为wamid
|
|
|
|
// console.log('coming msg', message.type, message);
|
|
|
|
// console.log('old msg ele', ele.type, ele);
|
|
|
|
const renderStatus = message?.data?.status ? { status: { ...ele.data.status, loading: 0, download: true } } : {};
|
|
|
|
const keepReply = ele.reply ? { reply: ele.reply } : {};
|
|
|
|
const keepTemplate = ele.template ? { template: ele.template, template_origin: ele.template_origin, text: ele.text } : {};
|
|
|
|
return { ...ele, ...message, id: message.id, status: ele.status === 'read' ? ele.status : message.status, dateString: message.dateString, data: { ...ele.data, ...renderStatus }, ...keepReply, ...keepTemplate };
|
|
|
|
}
|
|
|
|
return ele;
|
|
|
|
});
|
|
|
|
// 显示会话中其他客户端发送的消息
|
|
|
|
const targetMsgsIds = targetMsgs.map((ele) => ele.id);
|
|
|
|
if (!targetMsgsIds.includes(message.id)) {
|
|
|
|
targetMsgs.push(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
// const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
|
|
|
|
// let newConversations = [];
|
|
|
|
// if (targetIndex !== -1) { // 'delivered'
|
|
|
|
// // 更新列表的时间
|
|
|
|
// conversationsList.splice(targetIndex, 1, {
|
|
|
|
// ...conversationsList[targetIndex],
|
|
|
|
// last_received_time: message.status === 'received' ? dayjs(message.deliverTime).add(8, 'hours').format(DATETIME_FORMAT) : conversationsList[targetIndex].last_received_time,
|
|
|
|
// conversation_expiretime: message?.conversation?.expireTime || conversationsList[targetIndex].conversation_expiretime || '', // 保留使用UTC时间
|
|
|
|
// last_message: { ...message, source: message.msg_source },
|
|
|
|
// })
|
|
|
|
// } else if (targetIndex === -1) {
|
|
|
|
// // 当前客户端不存在的会话
|
|
|
|
// // todo: 设置为当前(在WhatsApp返回号码不一致时)
|
|
|
|
// const newContact = message.msg_direction === 'outbound' ? message.to : message.from;
|
|
|
|
// newConversations = [{
|
|
|
|
// ...conversationRow,
|
|
|
|
// ...message,
|
|
|
|
// sn: targetId,
|
|
|
|
// opi_sn: currentConversation.opi_sn, // todo: coli sn
|
|
|
|
// last_received_time: message.date,
|
|
|
|
// unread_msg_count: 0,
|
|
|
|
// whatsapp_name: newContact, //message?.senderName || message?.sender || '',
|
|
|
|
// customer_name: newContact, // message?.senderName || message?.sender || '',
|
|
|
|
// conversation_expiretime: message?.conversation?.expireTime || '', // 保留使用UTC时间
|
|
|
|
// whatsapp_phone_number: message.type === 'email' ? null : newContact,
|
|
|
|
// show_default: message?.conversation?.name || newContact || '',
|
|
|
|
// session_type: message?.conversation?.type === 'group' ? 1 : 0,
|
|
|
|
// last_message: { ...message, source: message.msg_source },
|
|
|
|
// channels: {
|
|
|
|
// "email": message.type === 'email' ? newContact : null,
|
|
|
|
// "phone_number": message.type === 'email' ? null : newContact,
|
|
|
|
// "whatsapp_phone_number": message.type === 'email' ? null : newContact,
|
|
|
|
// },
|
|
|
|
// }];
|
|
|
|
// }
|
|
|
|
// const mergedList = [...newConversations, ...conversationsList]
|
|
|
|
setFilter({ loadNextPage: true });
|
|
|
|
// const { topList, pageList } = sortConversationList(mergedList);
|
|
|
|
|
|
|
|
return set({
|
|
|
|
// topList,
|
|
|
|
// pageList,
|
|
|
|
// conversationsList: mergedList,
|
|
|
|
activeConversations: { ...activeConversations, [String(targetId)]: targetMsgs },
|
|
|
|
});
|
|
|
|
},
|
|
|
|
sentOrReceivedNewMessage: (targetId, message) => {
|
|
|
|
// msgRender:
|
|
|
|
// console.log('sentOrReceivedNewMessage', targetId, message)
|
|
|
|
const { activeConversations, conversationsList, currentConversation, totalNotify, setFilter } = get();
|
|
|
|
const targetMsgs = activeConversations[String(targetId)] || [];
|
|
|
|
// const targetIndex = conversationsList.findIndex((ele) => Number(ele.sn) === Number(targetId));
|
|
|
|
// const lastReceivedTime = (message.type !== 'system' && message.sender !== 'me') ? dayjs(message.date).add(8, 'hours').format(DATETIME_FORMAT) : null;
|
|
|
|
// const newContact = message.msg_direction === 'outbound' ? message.to : message.from;
|
|
|
|
// const newConversation =
|
|
|
|
// targetIndex !== -1
|
|
|
|
// ? {
|
|
|
|
// ...conversationsList[targetIndex],
|
|
|
|
// last_received_time: lastReceivedTime || conversationsList[targetIndex].last_received_time,
|
|
|
|
// unread_msg_count:
|
|
|
|
// Number(targetId) !== Number(currentConversation.sn) && message.sender !== 'me'
|
|
|
|
// ? conversationsList[targetIndex].unread_msg_count + 1
|
|
|
|
// : conversationsList[targetIndex].unread_msg_count,
|
|
|
|
// last_message: { ...message, source: message.msg_source },
|
|
|
|
// }
|
|
|
|
// : {
|
|
|
|
// ...conversationRow,
|
|
|
|
// ...message,
|
|
|
|
// sn: Number(targetId),
|
|
|
|
// opi_sn: message.opi_sn || currentConversation.opi_sn, // todo: coli sn
|
|
|
|
// last_received_time: dayjs(message.date).add(8, 'hours').format(DATETIME_FORMAT),
|
|
|
|
// unread_msg_count: message.sender === 'me' ? 0 : 1,
|
|
|
|
// whatsapp_name: message?.senderName || message?.sender || '',
|
|
|
|
// customer_name: message?.senderName || message?.sender || '',
|
|
|
|
// whatsapp_phone_number: message.type === 'email' ? null : newContact,
|
|
|
|
// show_default: message?.conversation?.name || message?.senderName || message?.sender || newContact || '',
|
|
|
|
// session_type: message?.conversation?.type === 'group' ? 1 : 0,
|
|
|
|
// last_message: { ...message, source: message.msg_source },
|
|
|
|
// channels: {
|
|
|
|
// "email": message.type === 'email' ? newContact : null,
|
|
|
|
// "phone_number": message.type === 'email' ? null : newContact,
|
|
|
|
// "whatsapp_phone_number": message.type === 'email' ? null : newContact,
|
|
|
|
// },
|
|
|
|
// };
|
|
|
|
// if (targetIndex === -1) {
|
|
|
|
// conversationsList.unshift(newConversation);
|
|
|
|
// } else {
|
|
|
|
// // if (String(targetId)!== '0') {
|
|
|
|
// conversationsList.splice(targetIndex, 1);
|
|
|
|
// conversationsList.unshift(newConversation);
|
|
|
|
// }
|
|
|
|
// console.log('find in list, i:', targetIndex);
|
|
|
|
// console.log('find in list, chat updated and Top: \n', JSON.stringify(newConversation, null, 2));
|
|
|
|
// console.log('list updated : \n', JSON.stringify(conversationsList, null, 2));
|
|
|
|
setFilter({ loadNextPage: true });
|
|
|
|
|
|
|
|
const isCurrent = Number(targetId) === Number(currentConversation.sn);
|
|
|
|
const updatedCurrent = isCurrent
|
|
|
|
? {
|
|
|
|
...currentConversation,
|
|
|
|
last_received_time: dayjs(message.date).add(8, 'hours').format(DATETIME_FORMAT),
|
|
|
|
conversation_expiretime: dayjs(message.date).add(24, 'hours').format(DATETIME_FORMAT),
|
|
|
|
last_message: { ...message, source: message.msg_source },
|
|
|
|
}
|
|
|
|
: {...currentConversation, last_message: message,};
|
|
|
|
|
|
|
|
// const { topList, pageList } = sortConversationList(conversationsList);
|
|
|
|
return set({
|
|
|
|
// currentConversation: updatedCurrent,
|
|
|
|
// topList,
|
|
|
|
// pageList,
|
|
|
|
// conversationsList: [...conversationsList],
|
|
|
|
// totalNotify: totalNotify + (message.sender === 'me' ? 0 : 1),
|
|
|
|
activeConversations: { ...activeConversations, [String(targetId)]: [...targetMsgs, message] },
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Email
|
|
|
|
*/
|
|
|
|
const emailSlice = (set, get) => ({
|
|
|
|
emailMsg: { id: -1, conversationid: '', actionId: '', order_opi: '', coli_sn: '', msgOrigin: {} },
|
|
|
|
setEmailMsg: (emailMsg) => {
|
|
|
|
const { editorOpen } = get();
|
|
|
|
return editorOpen ? false : set({ emailMsg }); // 已经打开的不更新
|
|
|
|
},
|
|
|
|
detailPopupOpen: false,
|
|
|
|
setDetailOpen: (v) => set({ detailPopupOpen: v }),
|
|
|
|
openDetail: () => set(() => ({ detailPopupOpen: true })),
|
|
|
|
closeDetail: () => set(() => ({ detailPopupOpen: false })),
|
|
|
|
editorOpen: false,
|
|
|
|
setEditorOpen: (v) => set({ editorOpen: v }),
|
|
|
|
openEditor: () => set(() => ({ editorOpen: true })),
|
|
|
|
closeEditor: () => set(() => ({ editorOpen: false })),
|
|
|
|
|
|
|
|
// EmailEditorPopup 组件的 props
|
|
|
|
// @property {string} fromEmail - 发件人邮箱
|
|
|
|
// @property {string} fromUser - 发件人用户
|
|
|
|
// @property {string} fromOrder - 发件订单
|
|
|
|
// @property {string} toEmail - 收件人邮箱
|
|
|
|
// @property {string} conversationid - 会话ID
|
|
|
|
// @property {string} quoteid - 引用邮件ID
|
|
|
|
// @property {object} draft - 草稿
|
|
|
|
// @property {string} action - reply / forward / new / edit
|
|
|
|
// @property {string} oid - coli_sn
|
|
|
|
// @property {object} mailData - 邮件内容
|
|
|
|
// @property {string} receiverName - 收件人称呼
|
|
|
|
emailEdiorProps: new Map(),
|
|
|
|
setEditorProps: (v) => {
|
|
|
|
const { emailEdiorProps } = get();
|
|
|
|
const uniqueKey = v.quoteid || Date.now().toString(32);
|
|
|
|
const currentEditValue = {...v, key: `${v.action}-${uniqueKey}`};
|
|
|
|
const news = new Map(emailEdiorProps).set(currentEditValue.key, currentEditValue);
|
|
|
|
for (const [key, value] of news.entries()) {
|
|
|
|
console.log(value);
|
|
|
|
}
|
|
|
|
return set((state) => ({ emailEdiorProps: news, currentEditKey: currentEditValue.key, currentEditValue }))
|
|
|
|
// return set((state) => ({ emailEdiorProps: { ...state.emailEdiorProps, ...v } }))
|
|
|
|
},
|
|
|
|
closeEditor1: (key) => {
|
|
|
|
const { emailEdiorProps } = get();
|
|
|
|
const newProps = new Map(emailEdiorProps);
|
|
|
|
newProps.delete(key);
|
|
|
|
return set(() => ({ emailEdiorProps: newProps }))
|
|
|
|
},
|
|
|
|
clearEditor: () => {
|
|
|
|
return set(() => ({ emailEdiorProps: new Map() }))
|
|
|
|
},
|
|
|
|
currentEditKey: '',
|
|
|
|
setCurrentEditKey: (key) => {
|
|
|
|
const { emailEdiorProps, setCurrentEditValue } = get();
|
|
|
|
const value = emailEdiorProps.get(key);
|
|
|
|
setCurrentEditValue(value);
|
|
|
|
return set(() => ({ currentEditKey: key }))
|
|
|
|
},
|
|
|
|
currentEditValue: {},
|
|
|
|
setCurrentEditValue: (v) => {
|
|
|
|
return set(() => ({ currentEditValue: v }))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
export const useConversationStore = create(
|
|
|
|
devtools((set, get) => ({
|
|
|
|
...initialConversationState,
|
|
|
|
...websocketSlice(set, get),
|
|
|
|
...conversationSlice(set, get),
|
|
|
|
...templatesSlice(set, get),
|
|
|
|
...messageSlice(set, get),
|
|
|
|
...referenceMsgSlice(set, get),
|
|
|
|
...complexMsgSlice(set, get),
|
|
|
|
...tagsSlice(set, get),
|
|
|
|
...filterSlice(set, get),
|
|
|
|
...globalNotifySlice(set, get),
|
|
|
|
...emailSlice(set, get),
|
|
|
|
...waiSlice(set, get),
|
|
|
|
|
|
|
|
// state actions
|
|
|
|
addError: (error) => set((state) => ({ errors: [...state.errors, error] })),
|
|
|
|
setInitial: (v) => set({ initialState: v }),
|
|
|
|
|
|
|
|
// side effects
|
|
|
|
fetchInitialData: async ({userId, whatsAppBusiness, ...loginUser}) => {
|
|
|
|
const { addToConversationList, setTemplates, setInitial, setTags } = get();
|
|
|
|
|
|
|
|
const conversationsList = await fetchConversationsList({ opisn: userId });
|
|
|
|
addToConversationList(conversationsList);
|
|
|
|
|
|
|
|
const templates = await fetchTemplates({ waba: whatsAppBusiness });
|
|
|
|
setTemplates(templates);
|
|
|
|
|
|
|
|
const myTags = await fetchTags({ opisn: userId});
|
|
|
|
setTags(myTags);
|
|
|
|
|
|
|
|
setInitial(true);
|
|
|
|
// 登录后, 执行一下清除日志缓存
|
|
|
|
clean7DaysWebsocketLog();
|
|
|
|
},
|
|
|
|
|
|
|
|
reset: () => set(initialConversationState),
|
|
|
|
}), { name: 'cStore' })
|
|
|
|
);
|
|
|
|
|
|
|
|
export default useConversationStore;
|