BOD-14 Added virtualization for messages with known bug. Added messages geting marked as read.

This commit is contained in:
Patrick Fic
2020-04-30 17:37:34 -07:00
parent bf42655186
commit c98e0b33fd
17 changed files with 284 additions and 93 deletions

View File

@@ -1,15 +1,37 @@
import React from "react";
import { Tag } from "antd";
import { Link } from "react-router-dom";
import { useMutation } from "@apollo/react-hooks";
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
export default function ChatConversationTitleTags({ jobConversations }) {
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
const handleRemoveTag = (jobId) => {
const convId = jobConversations[0].conversationid;
if (!!convId) {
removeJobConversation({
variables: {
conversationId: convId,
jobId: jobId,
},
});
}
};
return (
<div>
{jobConversations.map((item) => (
<Link to={`/manage/jobs/${item.job.id}`}>
<Tag color='blue' style={{ cursor: "pointer" }}>
<Tag
key={item.job.id}
closable
color='blue'
style={{ cursor: "pointer" }}
onClose={() => handleRemoveTag(item.job.id)}>
<Link to={`/manage/jobs/${item.job.id}`}>
{item.job.ro_number || "?"}
</Tag>
</Link>
</Link>
</Tag>
))}
</div>
);

View File

@@ -6,7 +6,11 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"
import AlertComponent from "../alert/alert.component";
import ChatConversationTitle from "../chat-conversation-title/chat-conversation-title.component";
export default function ChatConversationComponent({ subState, conversation }) {
export default function ChatConversationComponent({
subState,
conversation,
handleMarkConversationAsRead,
}) {
const [loading, error] = subState;
if (loading) return <LoadingSkeleton />;
@@ -20,12 +24,13 @@ export default function ChatConversationComponent({ subState, conversation }) {
conversation.messages_aggregate.aggregate.count) ||
0;
const messages =
(conversation && conversation.messages) ||
[];
const messages = (conversation && conversation.messages) || [];
return (
<div className='chat-conversation'>
<div
className='chat-conversation'
onMouseDown={handleMarkConversationAsRead}
onKeyDown={handleMarkConversationAsRead}>
<Badge count={unreadCount}>
<Card size='small'>
<ChatConversationTitle conversation={conversation} />

View File

@@ -1,11 +1,11 @@
import { useSubscription } from "@apollo/react-hooks";
import { useMutation, useSubscription } from "@apollo/react-hooks";
import React from "react";
import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
import ChatConversationComponent from "./chat-conversation.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import ChatConversationComponent from "./chat-conversation.component";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
});
@@ -13,10 +13,6 @@ const mapStateToProps = createStructuredSelector({
export default connect(mapStateToProps, null)(ChatConversationContainer);
export function ChatConversationContainer({ selectedConversation }) {
console.log(
"ChatConversationContainer -> selectedConversation",
selectedConversation
);
const { loading, error, data } = useSubscription(
CONVERSATION_SUBSCRIPTION_BY_PK,
{
@@ -24,12 +20,33 @@ export function ChatConversationContainer({ selectedConversation }) {
}
);
const [markConversationRead] = useMutation(
MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{
variables: { conversationId: selectedConversation },
}
);
const unreadCount =
(data &&
data.conversations_by_pk &&
data.conversations_by_pk &&
data.conversations_by_pk.messages_aggregate &&
data.conversations_by_pk.messages_aggregate.aggregate &&
data.conversations_by_pk.messages_aggregate.aggregate.count) ||
0;
const handleMarkConversationAsRead = () => {
if (unreadCount > 0 && !!selectedConversation) {
markConversationRead();
}
};
return (
<ChatConversationComponent
subState={[loading, error]}
conversation={data ? data.conversations_by_pk : {}}
handleMarkConversationAsRead={handleMarkConversationAsRead}
/>
);
}

View File

@@ -1,44 +1,68 @@
import { CheckCircleOutlined, CheckOutlined } from "@ant-design/icons";
import React, { useEffect, useRef } from "react";
import "./chat-message-list.styles.scss";
import { List, CellMeasurer, CellMeasurerCache } from "react-virtualized";
export default function ChatMessageListComponent({ messages }) {
const messagesEndRef = useRef(null);
const virtualizedListRef = useRef(null);
const _cache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 20,
});
const scrollToBottom = () => {
!!messagesEndRef.current &&
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
console.log("SCrolling to", messages.length);
!!virtualizedListRef.current &&
virtualizedListRef.current.scrollToRow(messages.length - 1);
//TODO Outstanding isue on virtualization: https://github.com/bvaughn/react-virtualized/issues/1179
//Scrolling does not work on this version of React.
};
useEffect(scrollToBottom, [messages]);
const StatusRender = (status) => {
switch (status) {
case "sent":
return <CheckOutlined style={{ margin: "2px", float: "right" }} />;
case "delivered":
return (
<CheckCircleOutlined style={{ margin: "2px", float: "right" }} />
);
default:
return null;
}
const _rowRenderer = ({ index, key, parent, style }) => {
return (
<CellMeasurer cache={_cache} key={key} rowIndex={index} parent={parent}>
{({ measure, registerChild }) => (
<li
ref={registerChild}
style={style}
className={`${messages[index].isoutbound ? "replies" : "sent"}`}>
<p onLoad={measure}>
{messages[index].text}
{StatusRender(messages[index].status)}
</p>
</li>
)}
</CellMeasurer>
);
};
return (
<div className='messages'>
<ul>
{messages.map((item) => (
<li
key={item.id}
className={`${item.isoutbound ? "replies" : "sent"}`}>
<p>
{item.text}
{StatusRender(item.status)}
</p>
</li>
))}
<li ref={messagesEndRef} />
<List
ref={virtualizedListRef}
width={300}
height={300}
rowHeight={_cache.rowHeight}
rowRenderer={_rowRenderer}
rowCount={messages.length}
/>
</ul>
</div>
);
}
const StatusRender = (status) => {
switch (status) {
case "sent":
return <CheckOutlined style={{ margin: "2px", float: "right" }} />;
case "delivered":
return <CheckCircleOutlined style={{ margin: "2px", float: "right" }} />;
default:
return null;
}
};

View File

@@ -1,22 +1,22 @@
.messages {
height: 350px;
min-height: calc(100% - 10px);
max-height: calc(100% - 93px);
overflow-y: scroll;
overflow-x: hidden;
}
@media screen and (max-width: 735px) {
.messages {
max-height: calc(100% - 105px);
}
}
.messages::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.messages::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
}
// .messages {
// height: 300px;
// min-height: calc(100% - 10px);
// max-height: calc(100% - 93px);
// overflow-y: scroll;
// overflow-x: hidden;
// }
// @media screen and (max-width: 735px) {
// .messages {
// max-height: calc(100% - 105px);
// }
// }
// .messages::-webkit-scrollbar {
// width: 8px;
// background: transparent;
// }
// .messages::-webkit-scrollbar-thumb {
// background-color: rgba(0, 0, 0, 0.3);
// }
.messages ul li {
display: inline-block;
clear: both;

View File

@@ -1,13 +1,18 @@
import React from "react";
import { AutoComplete } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import { LoadingOutlined, CloseCircleOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
export default function ChatTagRoComponent({
searchQueryState,
roOptions,
loading,
executeSearch,
handleInsertTag,
setVisible,
}) {
const { t } = useTranslation();
const setSearchQuery = searchQueryState[1];
const handleSearchQuery = (value) => {
setSearchQuery(value);
@@ -20,19 +25,26 @@ export default function ChatTagRoComponent({
};
return (
<AutoComplete
suffixIcon={loading ? <LoadingOutlined /> : null}
style={{ width: 200 }}
onSearch={handleSearchQuery}
onSelect={handleInsertTag}
onKeyDown={handleKeyDown}>
{roOptions.map((item, idx) => (
<AutoComplete.Option key={item.id || idx}>
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
item.ownr_ln || ""
}`}
</AutoComplete.Option>
))}
</AutoComplete>
<span>
<AutoComplete
style={{ width: 100 }}
onSearch={handleSearchQuery}
onSelect={handleInsertTag}
placeholder={t("general.labels.search")}
onKeyDown={handleKeyDown}>
{roOptions.map((item, idx) => (
<AutoComplete.Option key={item.id || idx}>
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
item.ownr_ln || ""
}`}
</AutoComplete.Option>
))}
</AutoComplete>
{loading ? (
<LoadingOutlined />
) : (
<CloseCircleOutlined onClick={() => setVisible(false)} />
)}
</span>
);
}

View File

@@ -3,9 +3,14 @@ import ChatTagRo from "./chat-tag-ro.component";
import { useLazyQuery, useMutation } from "@apollo/react-hooks";
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import { Tag } from "antd";
import { useTranslation } from "react-i18next";
import { PlusOutlined } from "@ant-design/icons";
export default function ChatTagRoContainer({ conversation }) {
console.log("ChatTagRoContainer -> conversation", conversation);
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const searchQueryState = useState("");
const searchText = searchQueryState[0];
@@ -28,19 +33,33 @@ export default function ChatTagRoContainer({ conversation }) {
});
const handleInsertTag = (value, option) => {
console.log("value, option", value, option);
insertTag({ variables: { jobId: option.key } });
setVisible(false);
};
const existingJobTags = conversation.job_conversations.map((i) => i.jobid);
const roOptions = data
? data.jobs.filter((job) => !existingJobTags.includes(job.id))
: [];
return (
<div>
<ChatTagRo
loading={loading}
searchQueryState={searchQueryState}
roOptions={data ? data.jobs : []}
executeSearch={executeSearch}
handleInsertTag={handleInsertTag}
/>
{visible ? (
<ChatTagRo
loading={loading}
searchQueryState={searchQueryState}
roOptions={roOptions}
executeSearch={executeSearch}
handleInsertTag={handleInsertTag}
setVisible={setVisible}
/>
) : (
<Tag onClick={() => setVisible(true)}>
<PlusOutlined />
{t("messaging.actions.link")}
</Tag>
)}
</div>
);
}