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..ffc087ae2 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";
@@ -7,6 +7,8 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
+import { List as VirtualizedList, AutoSizer } from "react-virtualized";
+
import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -18,59 +20,86 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setSelectedConversation(conversationId)),
});
-export function ChatConversationListComponent({
+function ChatConversationListComponent({
conversationList,
selectedConversation,
setSelectedConversation,
+ subscribeToMoreConversations,
+ loadMoreConversations,
}) {
+ useEffect(
+ () => subscribeToMoreConversations(),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
+ const rowRenderer = ({ index, key, style }) => {
+ const item = conversationList[index];
+
+ return (
+ setSelectedConversation(item.id)}
+ className={`chat-list-item ${
+ item.id === selectedConversation
+ ? "chat-list-selected-conversation"
+ : null
+ }`}
+ style={style}
+ >
+
+ {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}
+
+
{item.updated_at}
+
+
+
+ );
+ };
+
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}
-
-
{item.updated_at}
-
-
-
+
+ {({ height, width }) => (
+ {
+ if (scrollTop + clientHeight === scrollHeight) {
+ loadMoreConversations();
+ }
+ }}
+ />
)}
- />
+
);
}
+
export default connect(
mapStateToProps,
mapDispatchToProps
diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss
index bfc83d947..20cf8f4ef 100644
--- a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss
+++ b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss
@@ -3,8 +3,9 @@
}
.chat-list-container {
flex: 1;
- overflow: auto;
+ overflow: hidden;
height: 100%;
+ border: 1px solid gainsboro;
}
.chat-list-item {
@@ -21,4 +22,6 @@
.ro-number-tag {
align-self: baseline;
}
+ padding: 12px 24px;
+ border-bottom: 1px solid gainsboro;
}
diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx
index 9a4dc33eb..899fd3058 100644
--- a/client/src/components/chat-popup/chat-popup.component.jsx
+++ b/client/src/components/chat-popup/chat-popup.component.jsx
@@ -4,15 +4,16 @@ import {
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
-import { useQuery } from "@apollo/client";
+import { useLazyQuery, useSubscription } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
-import React, { useEffect, useState } from "react";
+import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
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 {
@@ -41,17 +42,17 @@ export function ChatPopupComponent({
const { t } = useTranslation();
const [pollInterval, setpollInterval] = useState(0);
- 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",
+ const [
+ getConversations,
+ { loading, data, called, refetch, fetchMore, subscribeToMore },
+ ] = useLazyQuery(CONVERSATION_LIST_QUERY, {
+ fetchPolicy: "cache-and-network",
+ nextFetchPolicy: "cache-first",
skip: !chatVisible,
- ...(pollInterval > 0 ? { pollInterval } : {}),
});
const fcmToken = sessionStorage.getItem("fcmtoken");
@@ -65,15 +66,22 @@ export function ChatPopupComponent({
}, [fcmToken]);
useEffect(() => {
- if (called && chatVisible) refetch();
- }, [chatVisible, called, refetch]);
+ if (called && chatVisible)
+ getConversations({
+ variables: {
+ offset: 0,
+ },
+ });
+ }, [chatVisible, called, getConversations]);
- // const unreadCount = data
- // ? data.conversations.reduce(
- // (acc, val) => val.messages_aggregate.aggregate.count + acc,
- // 0
- // )
- // : 0;
+ const loadMoreConversations = useCallback(() => {
+ if (data)
+ fetchMore({
+ variables: {
+ offset: data.conversations.length,
+ },
+ });
+ }, [data, fetchMore]);
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
@@ -110,6 +118,44 @@ export function ChatPopupComponent({
) : (
+ subscribeToMore({
+ document: CONVERSATION_LIST_SUBSCRIPTION,
+ variables: { offset: 0 },
+ updateQuery: (prev, { subscriptionData }) => {
+ if (
+ !subscriptionData.data ||
+ subscriptionData.data.conversations.length === 0
+ )
+ return prev;
+
+ let conversations = [...prev.conversations];
+ const newConversations =
+ subscriptionData.data.conversations;
+
+ for (const conversation of newConversations) {
+ const index = conversations.findIndex(
+ (prevConversation) =>
+ prevConversation.id === conversation.id
+ );
+
+ if (index !== -1) {
+ conversations.splice(index, 1);
+ conversations.unshift(conversation);
+
+ continue;
+ }
+
+ conversations.unshift(conversation);
+ }
+
+ return Object.assign({}, prev, {
+ conversations: conversations,
+ });
+ },
+ })
+ }
/>
)}
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"