Merged in feature/IO-2208-chat-affix (pull request #797)

Feature/IO-2208 chat affix

Approved-by: Patrick Fic
This commit is contained in:
John Allen Delos Reyes
2023-05-23 23:28:31 +00:00
committed by Patrick Fic
7 changed files with 208 additions and 99 deletions

View File

@@ -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 (
<List.Item
key={key}
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
style={style}
>
<div sryle={{ display: "inline-block" }}>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<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>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
);
};
return (
<div className="chat-list-container">
<List
bordered
dataSource={conversationList}
renderItem={(item) => (
<List.Item
key={item.id}
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
>
<div sryle={{ display: "inline-block" }}>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<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>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
<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

View File

@@ -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;
}

View File

@@ -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({
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
loadMoreConversations={loadMoreConversations}
subscribeToMoreConversations={() =>
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,
});
},
})
}
/>
)}
</Col>