From 11d9b4269b1c9f2f1f1caf656b142f82806b6b24 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 1 Feb 2024 09:33:27 +0800 Subject: [PATCH] =?UTF-8?q?reducer=20context=20=E7=AE=A1=E7=90=86conversat?= =?UTF-8?q?ion=20state;=20=E6=8B=86=E5=88=86reducer,=20=E5=87=8F=E5=B0=91r?= =?UTF-8?q?e-render;=20=E6=95=B4=E7=90=86=E7=9B=AE=E5=BD=95;=20=E6=96=B0?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=B4=AF=E5=8A=A0=E6=9C=AA=E8=AF=BB=E6=95=B0?= =?UTF-8?q?=E9=87=8F=0Btest:=20=E5=85=B3=E9=97=AD=E4=BC=9A=E8=AF=9D;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/ConversationActions.js | 103 ++++++++++++++++++ src/lib/msgUtils.js | 3 +- src/records/ConversationState.js | 13 +++ src/reducers/ConversationReducer.js | 99 +++++++++++++++++ src/stores/ConversationContext.js | 12 ++ ...nContext.js => ConversationContext_bak.js} | 0 src/utils/request.js | 10 +- src/views/Conversations/ChatWindow.jsx | 47 ++------ .../Components/ConversationsList.jsx | 79 ++++++++++++-- .../Components/CustomerProfile.jsx | 9 +- .../Conversations/Components/InputBox.jsx | 30 +++-- .../Components/LocalTimeClock.jsx | 4 +- .../Conversations/Components/Messages.jsx | 8 +- .../Components/MessagesHeader.jsx | 27 +++++ .../Components/QuotesHistory.jsx | 4 +- .../Conversations/ConversationProvider.jsx | 97 ++++++++++++++--- 16 files changed, 459 insertions(+), 86 deletions(-) create mode 100644 src/actions/ConversationActions.js create mode 100644 src/records/ConversationState.js create mode 100644 src/reducers/ConversationReducer.js create mode 100644 src/stores/ConversationContext.js rename src/stores/Conversations/{ConversationContext.js => ConversationContext_bak.js} (100%) create mode 100644 src/views/Conversations/Components/MessagesHeader.jsx diff --git a/src/actions/ConversationActions.js b/src/actions/ConversationActions.js new file mode 100644 index 0000000..1439b3d --- /dev/null +++ b/src/actions/ConversationActions.js @@ -0,0 +1,103 @@ + +import { groupBy } from '@/utils/utils'; +import { fetchJSON, postJSON } from '@/utils/request' + +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 list = 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 const postConversationItemClose = async (body) => { + const getParams = body ? new URLSearchParams(body).toString() : ''; + const { result } = await postJSON(`${API_HOST}/closeconversation?${getParams}`); + return result; +}; diff --git a/src/lib/msgUtils.js b/src/lib/msgUtils.js index 48d66b1..a4a6629 100644 --- a/src/lib/msgUtils.js +++ b/src/lib/msgUtils.js @@ -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 = { diff --git a/src/records/ConversationState.js b/src/records/ConversationState.js new file mode 100644 index 0000000..fc3e06a --- /dev/null +++ b/src/records/ConversationState.js @@ -0,0 +1,13 @@ +const initialState = { + websocket: null, + errors: [], // 错误信息 + + conversationsList: [], // 对话列表 + templates: [], + customerOrderProfile: {}, + + activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: [] } + currentConversation: {}, // 当前对话 + +}; +export default initialState; diff --git a/src/reducers/ConversationReducer.js b/src/reducers/ConversationReducer.js new file mode 100644 index 0000000..641b210 --- /dev/null +++ b/src/reducers/ConversationReducer.js @@ -0,0 +1,99 @@ +import initialState from '@/records/ConversationState'; + +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 (String(ele.sn) === String(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[String(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 (String(ele.sn) === String(targetId) && message.type !== 'error') { + return { ...ele, last_received_time: message.date }; + } + return ele; + }); + + return { + ...state, + activeConversations: { ...state.activeConversations, [String(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[String(targetId)] || []; + console.log(activeConversations, String(targetId), 'sent sent sent'); + const newConversationList = conversationsList.map((ele) => { + if (String(ele.sn) === String(targetId)) { + return { + ...ele, + last_received_time: message.date, + unread_msg_count: String(ele.sn) !== String(currentConversation.sn) ? ele.unread_msg_count + 1 : ele.unread_msg_count, + }; + } + return ele; + }); + return { + ...state, + activeConversations: { ...activeConversations, [String(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; diff --git a/src/stores/ConversationContext.js b/src/stores/ConversationContext.js new file mode 100644 index 0000000..dca607c --- /dev/null +++ b/src/stores/ConversationContext.js @@ -0,0 +1,12 @@ +import {createContext, useContext} from 'react'; + +export const ConversationContext = createContext(); + +export const useConversationContext = () => useContext(ConversationContext); + + +export const ConversationStateContext = createContext(); +export const ConversationDispatchContext = createContext(); + +export const useConversationState = () => useContext(ConversationStateContext); +export const useConversationDispatch = () => useContext(ConversationDispatchContext); diff --git a/src/stores/Conversations/ConversationContext.js b/src/stores/Conversations/ConversationContext_bak.js similarity index 100% rename from src/stores/Conversations/ConversationContext.js rename to src/stores/Conversations/ConversationContext_bak.js diff --git a/src/utils/request.js b/src/utils/request.js index ffea578..7de1c15 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -20,13 +20,15 @@ export function fetchText(url) { }) } -export function fetchJSON(url) { - return fetch(url) +export function fetchJSON(url, data) { + const params = data ? new URLSearchParams(data).toString() : ''; + const ifp = url.includes('?') ? '&' : '?'; + return fetch(`${url}${ifp}${params}`) .then(checkStatus) .then(response => response.json()) .catch(error => { - throw error - }) + throw error; + }); } export function postForm(url, data) { diff --git a/src/views/Conversations/ChatWindow.jsx b/src/views/Conversations/ChatWindow.jsx index 1ddbe4a..749d26b 100644 --- a/src/views/Conversations/ChatWindow.jsx +++ b/src/views/Conversations/ChatWindow.jsx @@ -1,40 +1,29 @@ -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 { Layout, Spin } from 'antd'; +import MessagesHeader from './Components/MessagesHeader'; import Messages from './Components/Messages'; import InputBox from './Components/InputBox'; import ConversationsList from './Components/ConversationsList'; import CustomerProfile from './Components/CustomerProfile'; -import LocalTimeClock from './Components/LocalTimeClock'; - -import { useConversationContext } from '@/stores/Conversations/ConversationContext'; import './Conversations.css'; -import { useAuthContext } from '@/stores/AuthContext.js'; 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 = () => { + console.log('chat window;;;;;;;;;;;;;;;;;;;;;;;;'); - // console.log(order_sn, currentUser); useEffect(() => { - if (order_sn) { - getCustomerProfile(order_sn); - } + console.log('chat window 222;;;;;;;;;;;;;;;;;;;;;;;;'); return () => {}; - }, [order_sn]); - + }, []); return ( - + @@ -43,21 +32,7 @@ const ChatWindow = (() => {
- - {currentConversation.customer_name && } - - - {currentConversation.customer_name} - {currentConversation.whatsapp_phone_number} - - - - {/* {order?.location} */} - - {/* {customerDateTime} */} - - - +
@@ -65,7 +40,7 @@ const ChatWindow = (() => {
- sendMessage(v)} /> +
@@ -76,6 +51,6 @@ const ChatWindow = (() => {
); -}); +}; export default ChatWindow; diff --git a/src/views/Conversations/Components/ConversationsList.jsx b/src/views/Conversations/Components/ConversationsList.jsx index 1095dc3..33c29e4 100644 --- a/src/views/Conversations/Components/ConversationsList.jsx +++ b/src/views/Conversations/Components/ConversationsList.jsx @@ -1,20 +1,26 @@ 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 { useAuthContext } from '@/stores/AuthContext'; +import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext'; +import { fetchCustomerProfile, receivedCustomerProfile, setCurrentConversation, addConversationList, setActiveConversations, postConversationItemClose } from '@/actions/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 { loginUser } = useAuthContext(); + const { userId } = loginUser; + const {conversationsList} = useConversationState(); + const dispatch = useConversationDispatch(); 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 +28,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 +42,56 @@ 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)) { + return false; + } + 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 +99,11 @@ const Conversations = (() => { } } + const handleConversationItemClose = async (item) => { + console.log('invoke close', item); + const data = await postConversationItemClose({ conversationid: item.sn, opisn: userId }); + } + return ( <> onSwitchConversation(item)} @@ -50,7 +111,7 @@ const Conversations = (() => { // console.log(item, i, e); // return (

ppp

) // }} - onClickMute={poseConversationItemClose} + onClickMute={handleConversationItemClose} /> ); diff --git a/src/views/Conversations/Components/CustomerProfile.jsx b/src/views/Conversations/Components/CustomerProfile.jsx index 2ce3089..f24ff70 100644 --- a/src/views/Conversations/Components/CustomerProfile.jsx +++ b/src/views/Conversations/Components/CustomerProfile.jsx @@ -1,6 +1,6 @@ import { Card, Flex, Avatar, Typography, Radio, Button, Table } from 'antd'; import { useAuthContext } from '@/stores/AuthContext.js'; -import { useConversationContext } from '@/stores/Conversations/ConversationContext'; +import { useConversationState } from '@/stores/ConversationContext'; import { HomeOutlined, LoadingOutlined, SettingFilled, SmileOutlined, SyncOutlined, PhoneOutlined, MailOutlined, WhatsAppOutlined, SmileTwoTone } from '@ant-design/icons'; import CreatePayment from './CreatePayment'; @@ -21,8 +21,8 @@ const orderStatus = [ const { Meta } = Card; -const CustomerProfile = (({ customer }) => { - const { errors, customerOrderProfile: orderInfo } = useConversationContext(); +const CustomerProfile = (() => { + const { customerOrderProfile: orderInfo } = useConversationState(); const { loginUser: currentUser } = useAuthContext(); const { quotes, contact, last_contact, ...order } = orderInfo; @@ -59,7 +59,8 @@ const CustomerProfile = (({ customer }) => { -

+      {/* 
 */}
+      

{/* 沟通记录 diff --git a/src/views/Conversations/Components/InputBox.jsx b/src/views/Conversations/Components/InputBox.jsx index cd5e8ca..efc9acd 100644 --- a/src/views/Conversations/Components/InputBox.jsx +++ b/src/views/Conversations/Components/InputBox.jsx @@ -1,20 +1,34 @@ import React, { useEffect, useState } from 'react'; import { Input, Button, Tabs, List, Space, Popover, Flex } from 'antd'; // import { Input } from 'react-chat-elements'; -import { useConversationContext } from '@/stores/Conversations/ConversationContext'; +import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext'; +import { sentNewMessage } from '@/actions/ConversationActions'; +import { useAuthContext } from '@/stores/AuthContext'; import { LikeOutlined, MessageOutlined, StarOutlined, SendOutlined, PlusOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { cloneDeep, getNestedValue, isEmpty } from '@/utils/utils'; import { v4 as uuid } from 'uuid'; -import { whatsappTemplatesParamMapped } from '@/lib/msgUtils'; +import { whatsappTemplatesParamMapped, sentMsgTypeMapped } from '@/lib/msgUtils'; -const InputBox = ({ onSend }) => { - const { currentConversation, templates } = useConversationContext(); +const InputBox = () => { + const { loginUser } = useAuthContext(); + const { userId } = loginUser; + const { websocket, currentConversation, templates } = useConversationState(); + const dispatch = useConversationDispatch(); const [textContent, setTextContent] = useState(''); const talkabled = !isEmpty(currentConversation.sn); + const invokeSendMessage = (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)); + }; + const handleSendText = () => { - if (typeof onSend === 'function' && textContent.trim() !== '') { + if (textContent.trim() !== '') { const msgObj = { type: 'text', text: textContent, @@ -24,14 +38,13 @@ const InputBox = ({ onSend }) => { date: new Date(), status: 'waiting', }; - onSend(msgObj); + invokeSendMessage(msgObj); setTextContent(''); } }; const handleSendTemplate = (fromTemplate) => { console.log(fromTemplate, 'fromTemplate'); - if (typeof onSend === 'function') { const _conversation = { ...cloneDeep(currentConversation) }; const msgObj = { type: 'whatsappTemplate', @@ -67,9 +80,8 @@ const InputBox = ({ onSend }) => { }, template_origin: fromTemplate, }; - onSend(msgObj); + invokeSendMessage(msgObj); setOpenTemplates(false); - } }; const [openTemplates, setOpenTemplates] = useState(false); diff --git a/src/views/Conversations/Components/LocalTimeClock.jsx b/src/views/Conversations/Components/LocalTimeClock.jsx index 0228803..3eb17e6 100644 --- a/src/views/Conversations/Components/LocalTimeClock.jsx +++ b/src/views/Conversations/Components/LocalTimeClock.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Typography } from 'antd'; -import { useConversationContext } from '@/stores/Conversations/ConversationContext'; +import { useConversationState } from '@/stores/ConversationContext'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; @@ -9,7 +9,7 @@ dayjs.extend(utc); dayjs.extend(timezone); const LocalTimeClock = ((props) => { - const { customerOrderProfile: orderInfo } = useConversationContext(); + const { customerOrderProfile: orderInfo } = useConversationState(); const [customerDateTime, setCustomerDateTime] = useState(); diff --git a/src/views/Conversations/Components/Messages.jsx b/src/views/Conversations/Components/Messages.jsx index 349063c..17309aa 100644 --- a/src/views/Conversations/Components/Messages.jsx +++ b/src/views/Conversations/Components/Messages.jsx @@ -1,12 +1,12 @@ import { useEffect, useState, useRef } from 'react'; import { List, Avatar, Timeline, Image } from 'antd'; import { MessageBox } from 'react-chat-elements'; -import { useConversationContext } from '@/stores/Conversations/ConversationContext'; +import { useConversationState } from '@/stores/ConversationContext'; const Messages = (() => { - const { messages: messagesList } = useConversationContext(); - // const messagesList = parseMessage(messages); - // console.log(messagesList); + const { activeConversations, currentConversation } = useConversationState(); + const messagesList = activeConversations[currentConversation.sn] || []; + console.log('messagesList----------------------------------------------------', messagesList); const messagesEndRef = useRef(null); useEffect(() => { diff --git a/src/views/Conversations/Components/MessagesHeader.jsx b/src/views/Conversations/Components/MessagesHeader.jsx new file mode 100644 index 0000000..4eeccf7 --- /dev/null +++ b/src/views/Conversations/Components/MessagesHeader.jsx @@ -0,0 +1,27 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import { useConversationState } from '@/stores/ConversationContext'; + +import { Flex, Typography, Avatar } from 'antd'; +import LocalTimeClock from './LocalTimeClock'; + +const MessagesHeader = () => { + const { currentConversation } = useConversationState(); + return ( + <> + + {currentConversation.customer_name && } + + + {currentConversation.customer_name} + {currentConversation.whatsapp_phone_number} + + + {/* {order?.location} */} + {/* {customerDateTime} */} + + + + + ); +}; +export default MessagesHeader; diff --git a/src/views/Conversations/Components/QuotesHistory.jsx b/src/views/Conversations/Components/QuotesHistory.jsx index 2a1f029..8b547ae 100644 --- a/src/views/Conversations/Components/QuotesHistory.jsx +++ b/src/views/Conversations/Components/QuotesHistory.jsx @@ -1,9 +1,9 @@ import { createContext, useContext, useEffect, useState } from 'react'; import { Popover, Flex, Button, List, Popconfirm } from 'antd'; -import { useConversationContext } from '@/stores/Conversations/ConversationContext'; +import { useConversationState } from '@/stores/ConversationContext'; const QuotesHistory = ((props) => { - const { customerOrderProfile: orderInfo } = useConversationContext(); + const { customerOrderProfile: orderInfo } = useConversationState(); const { quotes, ...order } = orderInfo; const [open, setOpen] = useState(false); diff --git a/src/views/Conversations/ConversationProvider.jsx b/src/views/Conversations/ConversationProvider.jsx index 9e8dc61..a6d6bd7 100644 --- a/src/views/Conversations/ConversationProvider.jsx +++ b/src/views/Conversations/ConversationProvider.jsx @@ -1,30 +1,97 @@ -import { useContext } from 'react'; -import { ConversationContext, useConversations, } from '@/stores/Conversations/ConversationContext'; +import { useContext, useReducer, useEffect } from 'react'; +import { ConversationStateContext, ConversationDispatchContext } from '@/stores/ConversationContext'; +import ConversationReducer from '@/reducers/ConversationReducer'; +import { + initWebsocket, + addError, + fetchConversationsList, + fetchTemplates, + receivedConversationList, + receivedTemplates, + setActiveConversations, + updateMessageItem, + receivedNewMessage, +} from '@/actions/ConversationActions'; +import initialState from '@/records/ConversationState'; + 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 {children}; -}; + 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; + 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 {children}; return ( - - {children} - + + {children} + ); }; -export default AuthAndConversationProvider; +export default ConversationProvider;