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..1c80d7c4c 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, {
+ const [
+ getConversations,
+ { loading, data, called, refetch, fetchMore, subscribeToMore },
+ ] = useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
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/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx
index ad56ebc61..65943905d 100644
--- a/client/src/components/job-payments/job-payments.component.jsx
+++ b/client/src/components/job-payments/job-payments.component.jsx
@@ -115,7 +115,7 @@ export function JobPayments({
render: (text, record) => (