feature/IO-3000-messaging-sockets-migration2 -
-misc Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -2,39 +2,65 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql
|
|||||||
import { gql } from "@apollo/client";
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
const logLocal = (message, ...args) => {
|
const logLocal = (message, ...args) => {
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
|
||||||
return;
|
console.log(`==================== ${message} ====================`);
|
||||||
|
console.dir({ ...args });
|
||||||
}
|
}
|
||||||
console.log(`==================== ${message} ====================`);
|
|
||||||
console.dir({ ...args });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Utility function to enrich conversation data
|
||||||
|
const enrichConversation = (conversation, isOutbound) => ({
|
||||||
|
...conversation,
|
||||||
|
updated_at: conversation.updated_at || new Date().toISOString(),
|
||||||
|
unreadcnt: conversation.unreadcnt || 0,
|
||||||
|
archived: conversation.archived || false,
|
||||||
|
label: conversation.label || null,
|
||||||
|
job_conversations: conversation.job_conversations || [],
|
||||||
|
messages_aggregate: conversation.messages_aggregate || {
|
||||||
|
__typename: "messages_aggregate",
|
||||||
|
aggregate: {
|
||||||
|
__typename: "messages_aggregate_fields",
|
||||||
|
count: isOutbound ? 0 : 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
__typename: "conversations"
|
||||||
|
});
|
||||||
|
|
||||||
export const registerMessagingHandlers = ({ socket, client }) => {
|
export const registerMessagingHandlers = ({ socket, client }) => {
|
||||||
if (!(socket && client)) return;
|
if (!(socket && client)) return;
|
||||||
|
|
||||||
const handleNewMessageSummary = async (message) => {
|
const handleNewMessageSummary = async (message) => {
|
||||||
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
||||||
|
|
||||||
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
|
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
|
||||||
|
|
||||||
const queryVariables = { offset: 0 };
|
const queryVariables = { offset: 0 };
|
||||||
|
|
||||||
// Utility function to enrich conversation data
|
if (!existingConversation && conversationId) {
|
||||||
const enrichConversation = (conversation, isOutbound) => ({
|
// Attempt to read from the cache to determine if this is actually a new conversation
|
||||||
...conversation,
|
try {
|
||||||
updated_at: conversation.updated_at || new Date().toISOString(),
|
const cachedConversation = client.cache.readFragment({
|
||||||
unreadcnt: conversation.unreadcnt || 0,
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
||||||
archived: conversation.archived || false,
|
fragment: gql`
|
||||||
label: conversation.label || null,
|
fragment ExistingConversationCheck on conversations {
|
||||||
job_conversations: conversation.job_conversations || [],
|
id
|
||||||
messages_aggregate: conversation.messages_aggregate || {
|
}
|
||||||
__typename: "messages_aggregate",
|
`
|
||||||
aggregate: {
|
});
|
||||||
__typename: "messages_aggregate_fields",
|
|
||||||
count: isOutbound ? 0 : 1
|
if (cachedConversation) {
|
||||||
|
logLocal("handleNewMessageSummary - Existing Conversation inferred from cache", {
|
||||||
|
conversationId
|
||||||
|
});
|
||||||
|
return handleNewMessageSummary({
|
||||||
|
...message,
|
||||||
|
existingConversation: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
__typename: "conversations"
|
logLocal("handleNewMessageSummary - Cache miss", { conversationId });
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle new conversation
|
// Handle new conversation
|
||||||
if (!existingConversation && newConversation?.phone_num) {
|
if (!existingConversation && newConversation?.phone_num) {
|
||||||
@@ -49,7 +75,6 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
const existingConversations = queryResults?.conversations || [];
|
const existingConversations = queryResults?.conversations || [];
|
||||||
const enrichedConversation = enrichConversation(newConversation, isoutbound);
|
const enrichedConversation = enrichConversation(newConversation, isoutbound);
|
||||||
|
|
||||||
// Avoid adding duplicate conversations
|
|
||||||
if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) {
|
if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) {
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: "ROOT_QUERY",
|
id: "ROOT_QUERY",
|
||||||
@@ -68,81 +93,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
|
|
||||||
// Handle existing conversation
|
// Handle existing conversation
|
||||||
if (existingConversation) {
|
if (existingConversation) {
|
||||||
let conversationDetails;
|
|
||||||
|
|
||||||
// Attempt to read existing conversation details from cache
|
|
||||||
try {
|
try {
|
||||||
conversationDetails = client.cache.readFragment({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fragment: gql`
|
|
||||||
fragment ExistingConversation on conversations {
|
|
||||||
id
|
|
||||||
phone_num
|
|
||||||
updated_at
|
|
||||||
archived
|
|
||||||
label
|
|
||||||
unreadcnt
|
|
||||||
job_conversations {
|
|
||||||
jobid
|
|
||||||
conversationid
|
|
||||||
}
|
|
||||||
messages_aggregate {
|
|
||||||
aggregate {
|
|
||||||
count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__typename
|
|
||||||
}
|
|
||||||
`
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logLocal("handleNewMessageSummary - Cache miss for conversation, fetching from server", { conversationId });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch conversation details from server if not in cache
|
|
||||||
if (!conversationDetails) {
|
|
||||||
try {
|
|
||||||
const { data } = await client.query({
|
|
||||||
query: GET_CONVERSATION_DETAILS,
|
|
||||||
variables: { conversationId },
|
|
||||||
fetchPolicy: "network-only"
|
|
||||||
});
|
|
||||||
conversationDetails = data?.conversations_by_pk;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch conversation details from server:", error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that conversation details were retrieved
|
|
||||||
if (!conversationDetails) {
|
|
||||||
console.error("Unable to retrieve conversation details. Skipping cache update.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if the conversation is already in the cache
|
|
||||||
const queryResults = client.cache.readQuery({
|
|
||||||
query: CONVERSATION_LIST_QUERY,
|
|
||||||
variables: queryVariables
|
|
||||||
});
|
|
||||||
|
|
||||||
const isAlreadyInCache = queryResults?.conversations.some((conv) => conv.id === conversationId);
|
|
||||||
|
|
||||||
if (!isAlreadyInCache) {
|
|
||||||
const enrichedConversation = enrichConversation(conversationDetails, isoutbound);
|
|
||||||
|
|
||||||
client.cache.modify({
|
|
||||||
id: "ROOT_QUERY",
|
|
||||||
fields: {
|
|
||||||
conversations(existingConversations = []) {
|
|
||||||
return [enrichedConversation, ...existingConversations];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update fields for the existing conversation in the cache
|
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
||||||
fields: {
|
fields: {
|
||||||
@@ -166,7 +117,11 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating cache for existing conversation:", error);
|
console.error("Error updating cache for existing conversation:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logLocal("New Conversation Summary finished without work", { message });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewMessageDetailed = (message) => {
|
const handleNewMessageDetailed = (message) => {
|
||||||
|
|||||||
@@ -32,8 +32,13 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
return () => clearInterval(interval); // Cleanup on unmount
|
return () => clearInterval(interval); // Cleanup on unmount
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Memoize the sorted conversation list
|
||||||
|
const sortedConversationList = React.useMemo(() => {
|
||||||
|
return _.orderBy(conversationList, ["updated_at"], ["desc"]);
|
||||||
|
}, [conversationList]);
|
||||||
|
|
||||||
const renderConversation = (index) => {
|
const renderConversation = (index) => {
|
||||||
const item = conversationList[index];
|
const item = sortedConversationList[index];
|
||||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||||
const cardContentLeft =
|
const cardContentLeft =
|
||||||
item.job_conversations.length > 0
|
item.job_conversations.length > 0
|
||||||
@@ -76,13 +81,10 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// CAN DO: Can go back into virtuoso for additional fetch
|
|
||||||
// endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-list-container">
|
<div className="chat-list-container">
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
data={conversationList}
|
data={sortedConversationList}
|
||||||
itemContent={(index) => renderConversation(index)}
|
itemContent={(index) => renderConversation(index)}
|
||||||
style={{ height: "100%", width: "100%" }}
|
style={{ height: "100%", width: "100%" }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ 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);
|
||||||
|
|
||||||
const handleRemoveTag = (jobId) => {
|
const handleRemoveTag = async (jobId) => {
|
||||||
const convId = jobConversations[0].conversationid;
|
const convId = jobConversations[0].conversationid;
|
||||||
if (!!convId) {
|
if (!!convId) {
|
||||||
removeJobConversation({
|
await removeJobConversation({
|
||||||
variables: {
|
variables: {
|
||||||
conversationId: convId,
|
conversationId: convId,
|
||||||
jobId: jobId
|
jobId: jobId
|
||||||
@@ -38,17 +38,18 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then(() => {
|
|
||||||
if (socket) {
|
|
||||||
// Emit the `conversation-modified` event
|
|
||||||
socket.emit("conversation-modified", {
|
|
||||||
bodyshopId: bodyshop.id,
|
|
||||||
conversationId: convId,
|
|
||||||
type: "tag-removed",
|
|
||||||
jobId: jobId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
// Emit the `conversation-modified` event
|
||||||
|
socket.emit("conversation-modified", {
|
||||||
|
bodyshopId: bodyshop.id,
|
||||||
|
conversationId: convId,
|
||||||
|
type: "tag-removed",
|
||||||
|
jobId: jobId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
logImEXEvent("messaging_remove_job_tag", {
|
logImEXEvent("messaging_remove_job_tag", {
|
||||||
conversationId: convId,
|
conversationId: convId,
|
||||||
jobId: jobId
|
jobId: jobId
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function ChatTagRoContainer({ conversation, bodyshop }) {
|
|||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (v) => {
|
||||||
logImEXEvent("messaging_search_job_tag", { searchTerm: v });
|
logImEXEvent("messaging_search_job_tag", { searchTerm: v });
|
||||||
loadRo(v);
|
loadRo(v).catch((e) => console.error("Error in ChatTagRoContainer executeSearch:", e));
|
||||||
};
|
};
|
||||||
|
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||||
@@ -41,33 +41,34 @@ export function ChatTagRoContainer({ conversation, bodyshop }) {
|
|||||||
variables: { conversationId: conversation.id }
|
variables: { conversationId: conversation.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleInsertTag = (value, option) => {
|
const handleInsertTag = async (value, option) => {
|
||||||
logImEXEvent("messaging_add_job_tag");
|
logImEXEvent("messaging_add_job_tag");
|
||||||
|
|
||||||
insertTag({
|
await insertTag({
|
||||||
variables: { jobId: option.key }
|
variables: { jobId: option.key }
|
||||||
}).then(() => {
|
|
||||||
if (socket) {
|
|
||||||
// Find the job details from the search data
|
|
||||||
const selectedJob = data?.search_jobs.find((job) => job.id === option.key);
|
|
||||||
if (!selectedJob) return;
|
|
||||||
const newJobConversation = {
|
|
||||||
__typename: "job_conversations",
|
|
||||||
jobid: selectedJob.id,
|
|
||||||
conversationid: conversation.id,
|
|
||||||
job: {
|
|
||||||
__typename: "jobs",
|
|
||||||
...selectedJob
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.emit("conversation-modified", {
|
|
||||||
conversationId: conversation.id,
|
|
||||||
bodyshopId: bodyshop.id,
|
|
||||||
type: "tag-added",
|
|
||||||
job_conversations: [newJobConversation]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
// Find the job details from the search data
|
||||||
|
const selectedJob = data?.search_jobs.find((job) => job.id === option.key);
|
||||||
|
if (!selectedJob) return;
|
||||||
|
const newJobConversation = {
|
||||||
|
__typename: "job_conversations",
|
||||||
|
jobid: selectedJob.id,
|
||||||
|
conversationid: conversation.id,
|
||||||
|
job: {
|
||||||
|
__typename: "jobs",
|
||||||
|
...selectedJob
|
||||||
|
}
|
||||||
|
};
|
||||||
|
socket.emit("conversation-modified", {
|
||||||
|
conversationId: conversation.id,
|
||||||
|
bodyshopId: bodyshop.id,
|
||||||
|
type: "tag-added",
|
||||||
|
job_conversations: [newJobConversation]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user