From 4bc8ff26d2fd92bb516553809335197215385bd3 Mon Sep 17 00:00:00 2001 From: swtmply Date: Sat, 13 May 2023 00:32:51 +0800 Subject: [PATCH] IO-2208 Added pagination and subscription to chat --- client/package.json | 1 + .../chat-conversation-list.component.jsx | 127 ++++++++++-------- .../chat-popup/chat-popup.component.jsx | 73 +++++++--- client/src/graphql/conversations.queries.js | 78 ++++++----- client/src/utils/GraphQLClient.js | 15 ++- client/yarn.lock | 5 + 6 files changed, 189 insertions(+), 110 deletions(-) diff --git a/client/package.json b/client/package.json index 6f31ce130..24b997708 100644 --- a/client/package.json +++ b/client/package.json @@ -51,6 +51,7 @@ "react-i18next": "^12.2.0", "react-icons": "^4.7.1", "react-image-lightbox": "^5.1.4", + "react-intersection-observer": "^9.4.3", "react-number-format": "^5.1.3", "react-redux": "^8.0.5", "react-resizable": "^3.0.4", diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index 816772291..a9d48871e 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -1,5 +1,5 @@ import { Badge, List, Tag } from "antd"; -import React from "react"; +import React, { useEffect } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; @@ -18,60 +18,73 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setSelectedConversation(conversationId)), }); -export function ChatConversationListComponent({ - conversationList, - selectedConversation, - setSelectedConversation, -}) { - return ( -
- ( - setSelectedConversation(item.id)} - className={`chat-list-item ${ - item.id === selectedConversation - ? "chat-list-selected-conversation" - : null - }`} - > -
- {item.label &&
{item.label}
} - {item.job_conversations.length > 0 ? ( -
- {item.job_conversations.map((j, idx) => ( -
- -
- ))} -
- ) : ( - {item.phone_num} - )} -
-
-
- {item.job_conversations.length > 0 - ? item.job_conversations.map((j, idx) => ( - - {j.job.ro_number} - - )) - : null} +const ChatConversationListComponent = React.forwardRef( + function ChatConversationListComponent( + { + conversationList, + selectedConversation, + setSelectedConversation, + subscribeToMoreConversations, + }, + ref + ) { + useEffect( + () => subscribeToMoreConversations(), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return ( +
+ ( + setSelectedConversation(item.id)} + className={`chat-list-item ${ + item.id === selectedConversation + ? "chat-list-selected-conversation" + : null + }`} + > +
+ {item.label &&
{item.label}
} + {item.job_conversations.length > 0 ? ( +
+ {item.job_conversations.map((j, idx) => ( +
+ +
+ ))} +
+ ) : ( + {item.phone_num} + )}
- {item.updated_at} -
- - - )} - /> -
- ); -} -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatConversationListComponent); +
+
+ {item.job_conversations.length > 0 + ? item.job_conversations.map((j, idx) => ( + + {j.job.ro_number} + + )) + : null} +
+ {item.updated_at} +
+ + + )} + footer={} + /> +
+ ); + } +); + +export default connect(mapStateToProps, mapDispatchToProps, null, { + forwardRef: true, +})(ChatConversationListComponent); diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 9a4dc33eb..a3fda4843 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -4,7 +4,7 @@ import { ShrinkOutlined, SyncOutlined, } from "@ant-design/icons"; -import { useQuery } from "@apollo/client"; +import { useQuery, useSubscription } from "@apollo/client"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -12,7 +12,8 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { CONVERSATION_LIST_QUERY, - UNREAD_CONVERSATION_COUNT, + CONVERSATION_LIST_SUBSCRIPTION, + UNREAD_CONVERSATION_COUNT_SUBSCRIPTION, } from "../../graphql/conversations.queries"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { @@ -24,6 +25,7 @@ import ChatConversationContainer from "../chat-conversation/chat-conversation.co import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import "./chat-popup.styles.scss"; +import { useInView } from "react-intersection-observer"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation, @@ -40,19 +42,29 @@ export function ChatPopupComponent({ }) { const { t } = useTranslation(); const [pollInterval, setpollInterval] = useState(0); + const { ref, inView } = useInView({}); - const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - ...(pollInterval > 0 ? { pollInterval } : {}), - }); + const { data: unreadData } = useSubscription( + UNREAD_CONVERSATION_COUNT_SUBSCRIPTION + ); - const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !chatVisible, - ...(pollInterval > 0 ? { pollInterval } : {}), - }); + // const { data: unreadData, refetch: unreadRefetch } = useQuery( + // UNREAD_CONVERSATION_COUNT, + // { + // fetchPolicy: "network-only", + // nextFetchPolicy: "network-only", + // } + // ); + + const { loading, data, called, refetch, fetchMore, subscribeToMore } = + useQuery(CONVERSATION_LIST_QUERY, { + variables: { + offset: 0, + }, + fetchPolicy: "cache-and-network", + nextFetchPolicy: "cache-first", + skip: !chatVisible, + }); const fcmToken = sessionStorage.getItem("fcmtoken"); @@ -68,12 +80,15 @@ export function ChatPopupComponent({ if (called && chatVisible) refetch(); }, [chatVisible, called, refetch]); - // const unreadCount = data - // ? data.conversations.reduce( - // (acc, val) => val.messages_aggregate.aggregate.count + acc, - // 0 - // ) - // : 0; + useEffect(() => { + if (inView && data && data.conversations) { + fetchMore({ + variables: { + offset: data.conversations.length, + }, + }); + } + }, [inView, data, fetchMore]); const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; @@ -110,6 +125,26 @@ export function ChatPopupComponent({ ) : ( { + subscribeToMore({ + document: CONVERSATION_LIST_SUBSCRIPTION, + variables: { offset: 0 }, + updateQuery: (prev, { subscriptionData }) => { + if ( + !subscriptionData.data || + subscriptionData.data.conversations.length === 0 + ) + return prev; + const newConversations = + subscriptionData.data.conversations; + + return Object.assign({}, prev, { + conversations: [...newConversations], + }); + }, + }); + }} + ref={ref} /> )} diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index ef73f546b..53de38b62 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -1,35 +1,36 @@ import { gql } from "@apollo/client"; -// export const CONVERSATION_LIST_SUBSCRIPTION = gql` -// subscription CONVERSATION_LIST_SUBSCRIPTION { -// conversations( -// order_by: { updated_at: desc } -// limit: 50 -// where: { archived: { _eq: false } } -// ) { -// phone_num -// id -// updated_at -// unreadcnt -// messages_aggregate( -// where: { read: { _eq: false }, isoutbound: { _eq: false } } -// ) { -// aggregate { -// count -// } -// } -// job_conversations { -// job { -// id -// ro_number -// ownr_fn -// ownr_ln -// ownr_co_nm -// } -// } -// } -// } -// `; +export const CONVERSATION_LIST_SUBSCRIPTION = gql` + subscription CONVERSATION_LIST_SUBSCRIPTION($offset: Int!) { + conversations( + order_by: { updated_at: desc } + limit: 1 + offset: $offset + where: { archived: { _eq: false } } + ) { + phone_num + id + updated_at + unreadcnt + messages_aggregate( + where: { read: { _eq: false }, isoutbound: { _eq: false } } + ) { + aggregate { + count + } + } + job_conversations { + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + } + } + } +`; export const UNREAD_CONVERSATION_COUNT = gql` query UNREAD_CONVERSATION_COUNT { @@ -43,11 +44,24 @@ export const UNREAD_CONVERSATION_COUNT = gql` } `; +export const UNREAD_CONVERSATION_COUNT_SUBSCRIPTION = gql` + subscription UNREAD_CONVERSATION_COUNT_SUBSCRIPTION { + messages_aggregate( + where: { read: { _eq: false }, isoutbound: { _eq: false } } + ) { + aggregate { + count + } + } + } +`; + export const CONVERSATION_LIST_QUERY = gql` - query CONVERSATION_LIST_QUERY { + query CONVERSATION_LIST_QUERY($offset: Int!) { conversations( order_by: { updated_at: desc } - limit: 50 + limit: 20 + offset: $offset where: { archived: { _eq: false } } ) { phone_num diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js index 1861b8b7b..b6c2c0091 100644 --- a/client/src/utils/GraphQLClient.js +++ b/client/src/utils/GraphQLClient.js @@ -3,7 +3,10 @@ import { setContext } from "@apollo/client/link/context"; import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http"; import { RetryLink } from "@apollo/client/link/retry"; import { WebSocketLink } from "@apollo/client/link/ws"; -import { getMainDefinition } from "@apollo/client/utilities"; +import { + getMainDefinition, + offsetLimitPagination, +} from "@apollo/client/utilities"; //import { split } from "apollo-link"; import apolloLogger from "apollo-link-logger"; //import axios from "axios"; @@ -140,7 +143,15 @@ middlewares.push( ) ); -const cache = new InMemoryCache({}); +const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + conversations: offsetLimitPagination(), + }, + }, + }, +}); const client = new ApolloClient({ link: ApolloLink.from(middlewares), diff --git a/client/yarn.lock b/client/yarn.lock index 04749e444..d972ced66 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -11168,6 +11168,11 @@ react-image-lightbox@^5.1.4: prop-types "^15.7.2" react-modal "^3.11.1" +react-intersection-observer@^9.4.3: + version "9.4.3" + resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz#ec84ce0c25cad548075130ea045ac5c7adf908f3" + integrity sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ== + react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"