IO-2208 added virtualization to conversation list
This commit is contained in:
@@ -7,6 +7,8 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
|
|||||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
import { List as VirtualizedList, AutoSizer } from "react-virtualized";
|
||||||
|
|
||||||
import "./chat-conversation-list.styles.scss";
|
import "./chat-conversation-list.styles.scss";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -18,73 +20,87 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setSelectedConversation(conversationId)),
|
dispatch(setSelectedConversation(conversationId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ChatConversationListComponent = React.forwardRef(
|
function ChatConversationListComponent({
|
||||||
function ChatConversationListComponent(
|
conversationList,
|
||||||
{
|
selectedConversation,
|
||||||
conversationList,
|
setSelectedConversation,
|
||||||
selectedConversation,
|
subscribeToMoreConversations,
|
||||||
setSelectedConversation,
|
loadMoreConversations,
|
||||||
subscribeToMoreConversations,
|
}) {
|
||||||
},
|
useEffect(
|
||||||
ref
|
() => subscribeToMoreConversations(),
|
||||||
) {
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(
|
[]
|
||||||
() => subscribeToMoreConversations(),
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[]
|
const rowRenderer = ({ index, key, style }) => {
|
||||||
);
|
const item = conversationList[index];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-list-container">
|
<List.Item
|
||||||
<List
|
key={key}
|
||||||
bordered
|
onClick={() => setSelectedConversation(item.id)}
|
||||||
dataSource={conversationList}
|
className={`chat-list-item ${
|
||||||
renderItem={(item) => (
|
item.id === selectedConversation
|
||||||
<List.Item
|
? "chat-list-selected-conversation"
|
||||||
key={item.id}
|
: null
|
||||||
onClick={() => setSelectedConversation(item.id)}
|
}`}
|
||||||
className={`chat-list-item ${
|
style={style}
|
||||||
item.id === selectedConversation
|
>
|
||||||
? "chat-list-selected-conversation"
|
<div sryle={{ display: "inline-block" }}>
|
||||||
: null
|
{item.label && <div className="chat-name">{item.label}</div>}
|
||||||
}`}
|
{item.job_conversations.length > 0 ? (
|
||||||
>
|
<div className="chat-name">
|
||||||
<div sryle={{ display: "inline-block" }}>
|
{item.job_conversations.map((j, idx) => (
|
||||||
{item.label && <div className="chat-name">{item.label}</div>}
|
<div key={idx}>
|
||||||
{item.job_conversations.length > 0 ? (
|
<OwnerNameDisplay ownerObject={j.job} />
|
||||||
<div className="chat-name">
|
|
||||||
{item.job_conversations.map((j, idx) => (
|
|
||||||
<div key={idx}>
|
|
||||||
<OwnerNameDisplay ownerObject={j.job} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div sryle={{ display: "inline-block" }}>
|
|
||||||
<div>
|
|
||||||
{item.job_conversations.length > 0
|
|
||||||
? item.job_conversations.map((j, idx) => (
|
|
||||||
<Tag key={idx} className="ro-number-tag">
|
|
||||||
{j.job.ro_number}
|
|
||||||
</Tag>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
) : (
|
||||||
</List.Item>
|
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||||
)}
|
)}
|
||||||
footer={<span ref={ref}></span>}
|
</div>
|
||||||
/>
|
<div sryle={{ display: "inline-block" }}>
|
||||||
</div>
|
<div>
|
||||||
|
{item.job_conversations.length > 0
|
||||||
|
? item.job_conversations.map((j, idx) => (
|
||||||
|
<Tag key={idx} className="ro-number-tag">
|
||||||
|
{j.job.ro_number}
|
||||||
|
</Tag>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||||
|
</div>
|
||||||
|
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||||
|
</List.Item>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps, null, {
|
return (
|
||||||
forwardRef: true,
|
<div className="chat-list-container">
|
||||||
})(ChatConversationListComponent);
|
<AutoSizer>
|
||||||
|
{({ height, width }) => (
|
||||||
|
<VirtualizedList
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
rowCount={conversationList.length}
|
||||||
|
rowHeight={60}
|
||||||
|
rowRenderer={rowRenderer}
|
||||||
|
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||||
|
if (scrollTop + clientHeight === scrollHeight) {
|
||||||
|
loadMoreConversations();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ChatConversationListComponent);
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
}
|
}
|
||||||
.chat-list-container {
|
.chat-list-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: 1px solid gainsboro;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list-item {
|
.chat-list-item {
|
||||||
@@ -21,4 +22,6 @@
|
|||||||
.ro-number-tag {
|
.ro-number-tag {
|
||||||
align-self: baseline;
|
align-self: baseline;
|
||||||
}
|
}
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-bottom: 1px solid gainsboro;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
ShrinkOutlined,
|
ShrinkOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useQuery, useSubscription } from "@apollo/client";
|
import { useLazyQuery, useQuery, useSubscription } from "@apollo/client";
|
||||||
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -25,7 +25,6 @@ import ChatConversationContainer from "../chat-conversation/chat-conversation.co
|
|||||||
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
|
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import "./chat-popup.styles.scss";
|
import "./chat-popup.styles.scss";
|
||||||
import { useInView } from "react-intersection-observer";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
selectedConversation: selectSelectedConversation,
|
selectedConversation: selectSelectedConversation,
|
||||||
@@ -42,7 +41,6 @@ export function ChatPopupComponent({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [pollInterval, setpollInterval] = useState(0);
|
const [pollInterval, setpollInterval] = useState(0);
|
||||||
const { ref, inView } = useInView({});
|
|
||||||
|
|
||||||
const { data: unreadData } = useSubscription(
|
const { data: unreadData } = useSubscription(
|
||||||
UNREAD_CONVERSATION_COUNT_SUBSCRIPTION
|
UNREAD_CONVERSATION_COUNT_SUBSCRIPTION
|
||||||
@@ -56,15 +54,14 @@ export function ChatPopupComponent({
|
|||||||
// }
|
// }
|
||||||
// );
|
// );
|
||||||
|
|
||||||
const { loading, data, called, refetch, fetchMore, subscribeToMore } =
|
const [
|
||||||
useQuery(CONVERSATION_LIST_QUERY, {
|
getConversations,
|
||||||
variables: {
|
{ loading, data, called, fetchMore, subscribeToMore },
|
||||||
offset: 0,
|
] = useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||||
},
|
fetchPolicy: "cache-and-network",
|
||||||
fetchPolicy: "cache-and-network",
|
nextFetchPolicy: "cache-first",
|
||||||
nextFetchPolicy: "cache-first",
|
skip: !chatVisible,
|
||||||
skip: !chatVisible,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const fcmToken = sessionStorage.getItem("fcmtoken");
|
const fcmToken = sessionStorage.getItem("fcmtoken");
|
||||||
|
|
||||||
@@ -77,18 +74,60 @@ export function ChatPopupComponent({
|
|||||||
}, [fcmToken]);
|
}, [fcmToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (called && chatVisible) refetch();
|
if (called && chatVisible)
|
||||||
}, [chatVisible, called, refetch]);
|
getConversations({
|
||||||
|
variables: {
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [chatVisible, called, getConversations]);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadMoreConversations = useCallback(() => {
|
||||||
if (inView && data && data.conversations) {
|
if (data)
|
||||||
fetchMore({
|
fetchMore({
|
||||||
variables: {
|
variables: {
|
||||||
offset: data.conversations.length,
|
offset: data.conversations.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}, [data, fetchMore]);
|
||||||
}, [inView, data, fetchMore]);
|
|
||||||
|
const subscribeToMoreConversations = useCallback(
|
||||||
|
() =>
|
||||||
|
subscribeToMore({
|
||||||
|
document: CONVERSATION_LIST_SUBSCRIPTION,
|
||||||
|
variables: { offset: 0 },
|
||||||
|
updateQuery: (prev, { subscriptionData }) => {
|
||||||
|
console.log("Hello");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.unshift(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, prev, {
|
||||||
|
conversations: conversations,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[subscribeToMore]
|
||||||
|
);
|
||||||
|
|
||||||
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
||||||
|
|
||||||
@@ -105,10 +144,10 @@ export function ChatPopupComponent({
|
|||||||
<Tooltip title={t("messaging.labels.recentonly")}>
|
<Tooltip title={t("messaging.labels.recentonly")}>
|
||||||
<InfoCircleOutlined />
|
<InfoCircleOutlined />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<SyncOutlined
|
{/* <SyncOutlined
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
/>
|
/> */}
|
||||||
{pollInterval > 0 && (
|
{pollInterval > 0 && (
|
||||||
<Tag color="yellow">{t("messaging.labels.nopush")}</Tag>
|
<Tag color="yellow">{t("messaging.labels.nopush")}</Tag>
|
||||||
)}
|
)}
|
||||||
@@ -125,7 +164,8 @@ export function ChatPopupComponent({
|
|||||||
) : (
|
) : (
|
||||||
<ChatConversationListComponent
|
<ChatConversationListComponent
|
||||||
conversationList={data ? data.conversations : []}
|
conversationList={data ? data.conversations : []}
|
||||||
subscribeToMoreConversations={() => {
|
loadMoreConversations={loadMoreConversations}
|
||||||
|
subscribeToMoreConversations={() =>
|
||||||
subscribeToMore({
|
subscribeToMore({
|
||||||
document: CONVERSATION_LIST_SUBSCRIPTION,
|
document: CONVERSATION_LIST_SUBSCRIPTION,
|
||||||
variables: { offset: 0 },
|
variables: { offset: 0 },
|
||||||
@@ -135,16 +175,33 @@ export function ChatPopupComponent({
|
|||||||
subscriptionData.data.conversations.length === 0
|
subscriptionData.data.conversations.length === 0
|
||||||
)
|
)
|
||||||
return prev;
|
return prev;
|
||||||
|
|
||||||
|
let conversations = [...prev.conversations];
|
||||||
const newConversations =
|
const newConversations =
|
||||||
subscriptionData.data.conversations;
|
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, {
|
return Object.assign({}, prev, {
|
||||||
conversations: [...newConversations],
|
conversations: conversations,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}}
|
}
|
||||||
ref={ref}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
Reference in New Issue
Block a user