feature/IO-3000-messaging-sockets-migration2 - Handle some of the PR notes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -4,8 +4,17 @@ import React, { useContext, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default function ChatArchiveButton({ conversation, bodyshop }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
|
export function ChatArchiveButton({ conversation, bodyshop }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
|
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
|
||||||
@@ -36,3 +45,5 @@ export default function ChatArchiveButton({ conversation, bodyshop }) {
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatArchiveButton);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Can go back into virtuoso for additional fetch
|
// CAN DO: Can go back into virtuoso for additional fetch
|
||||||
// endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom
|
// endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,8 +6,17 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
|
export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
||||||
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
|
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
|
||||||
const { socket } = useContext(SocketContext);
|
const { socket } = useContext(SocketContext);
|
||||||
|
|
||||||
@@ -66,3 +75,5 @@ export default function ChatConversationTitleTags({ jobConversations, bodyshop }
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitleTags);
|
||||||
|
|||||||
@@ -6,19 +6,27 @@ import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conv
|
|||||||
import ChatLabelComponent from "../chat-label/chat-label.component";
|
import ChatLabelComponent from "../chat-label/chat-label.component";
|
||||||
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
||||||
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default function ChatConversationTitle({ conversation, bodyshop }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
|
export function ChatConversationTitle({ conversation }) {
|
||||||
return (
|
return (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
||||||
<ChatLabelComponent conversation={conversation} bodyshop={bodyshop} />
|
<ChatLabelComponent conversation={conversation} />
|
||||||
<ChatPrintButton conversation={conversation} />
|
<ChatPrintButton conversation={conversation} />
|
||||||
<ChatConversationTitleTags
|
<ChatConversationTitleTags jobConversations={(conversation && conversation.job_conversations) || []} />
|
||||||
jobConversations={(conversation && conversation.job_conversations) || []}
|
<ChatTagRoContainer conversation={conversation || []} />
|
||||||
bodyshop={bodyshop}
|
<ChatArchiveButton conversation={conversation} />
|
||||||
/>
|
|
||||||
<ChatTagRoContainer conversation={conversation || []} bodyshop={bodyshop} />
|
|
||||||
<ChatArchiveButton conversation={conversation} bodyshop={bodyshop} />
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitle);
|
||||||
|
|||||||
@@ -5,8 +5,17 @@ import ChatMessageListComponent from "../chat-messages-list/chat-message-list.co
|
|||||||
import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||||
import "./chat-conversation.styles.scss";
|
import "./chat-conversation.styles.scss";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default function ChatConversationComponent({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
|
export function ChatConversationComponent({
|
||||||
subState,
|
subState,
|
||||||
conversation,
|
conversation,
|
||||||
messages,
|
messages,
|
||||||
@@ -31,3 +40,5 @@ export default function ChatConversationComponent({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationComponent);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useApolloClient, useQuery } from "@apollo/client";
|
import { useApolloClient, useQuery } from "@apollo/client";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext";
|
import SocketContext from "../../contexts/SocketIO/socketContext";
|
||||||
@@ -14,8 +14,6 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(ChatConversationContainer);
|
|
||||||
|
|
||||||
export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { socket } = useContext(SocketContext);
|
const { socket } = useContext(SocketContext);
|
||||||
@@ -30,38 +28,40 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Utility to update Apollo cache
|
const updateCacheWithReadMessages = useCallback(
|
||||||
const updateCacheWithReadMessages = (conversationId, messageIds) => {
|
(conversationId, messageIds) => {
|
||||||
if (!conversationId || !messageIds || messageIds.length === 0) return;
|
if (!conversationId || !messageIds || messageIds.length === 0) return;
|
||||||
|
|
||||||
messageIds.forEach((messageId) => {
|
// Mark individual messages as read
|
||||||
|
messageIds.forEach((messageId) => {
|
||||||
|
client.cache.modify({
|
||||||
|
id: client.cache.identify({ __typename: "messages", id: messageId }),
|
||||||
|
fields: {
|
||||||
|
read() {
|
||||||
|
return true; // Mark message as read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update aggregate unread count for the conversation
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: client.cache.identify({ __typename: "messages", id: messageId }),
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
||||||
fields: {
|
fields: {
|
||||||
read() {
|
messages_aggregate(existingAggregate) {
|
||||||
return true; // Mark message as read
|
return {
|
||||||
|
...existingAggregate,
|
||||||
|
aggregate: {
|
||||||
|
...existingAggregate.aggregate,
|
||||||
|
count: 0 // No unread messages remaining
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
[client.cache]
|
||||||
// Optionally update aggregate unread count
|
);
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fields: {
|
|
||||||
messages_aggregate(existingAggregate) {
|
|
||||||
const updatedAggregate = {
|
|
||||||
...existingAggregate,
|
|
||||||
aggregate: {
|
|
||||||
...existingAggregate.aggregate,
|
|
||||||
count: 0 // No unread messages remaining
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return updatedAggregate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle WebSocket events
|
// Handle WebSocket events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -80,7 +80,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|||||||
return () => {
|
return () => {
|
||||||
socket.off("conversation-changed", handleConversationChange);
|
socket.off("conversation-changed", handleConversationChange);
|
||||||
};
|
};
|
||||||
}, [socket, client]);
|
}, [socket, client, updateCacheWithReadMessages]);
|
||||||
|
|
||||||
// Handle joining/leaving conversation
|
// Handle joining/leaving conversation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -143,7 +143,8 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|||||||
conversation={convoData ? convoData.conversations_by_pk : {}}
|
conversation={convoData ? convoData.conversations_by_pk : {}}
|
||||||
messages={convoData ? convoData.conversations_by_pk.messages : []}
|
messages={convoData ? convoData.conversations_by_pk.messages : []}
|
||||||
handleMarkConversationAsRead={handleMarkConversationAsRead}
|
handleMarkConversationAsRead={handleMarkConversationAsRead}
|
||||||
bodyshop={bodyshop}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(ChatConversationContainer);
|
||||||
|
|||||||
@@ -5,8 +5,17 @@ import React, { useContext, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
|
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default function ChatLabel({ conversation, bodyshop }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
|
export function ChatLabel({ conversation, bodyshop }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [value, setValue] = useState(conversation.label);
|
const [value, setValue] = useState(conversation.label);
|
||||||
@@ -67,3 +76,5 @@ export default function ChatLabel({ conversation, bodyshop }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatLabel);
|
||||||
|
|||||||
@@ -9,8 +9,17 @@ import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries
|
|||||||
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
||||||
import ChatTagRo from "./chat-tag-ro.component";
|
import ChatTagRo from "./chat-tag-ro.component";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default function ChatTagRoContainer({ conversation, bodyshop }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
|
export function ChatTagRoContainer({ conversation, bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { socket } = useContext(SocketContext);
|
const { socket } = useContext(SocketContext);
|
||||||
@@ -86,3 +95,5 @@ export default function ChatTagRoContainer({ conversation, bodyshop }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatTagRoContainer);
|
||||||
|
|||||||
Reference in New Issue
Block a user