feature/IO-3000-messaging-sockets-migrations2 -
- Conversation Labels Synced - Job Tagging Synced Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -97,70 +97,45 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
};
|
||||
|
||||
const handleConversationChanged = (data) => {
|
||||
const { type, conversationId, jobId, label } = data;
|
||||
const { conversationId, type, job_conversations, ...fields } = data;
|
||||
|
||||
switch (type) {
|
||||
case "conversation-marked-read":
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversationId
|
||||
}),
|
||||
fields: {
|
||||
messages_aggregate: () => ({ aggregate: { count: 0 } })
|
||||
}
|
||||
});
|
||||
// Identify the conversation in the Apollo cache
|
||||
const cacheId = client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversationId
|
||||
});
|
||||
|
||||
// Optionally, refetch queries if needed
|
||||
// client.refetchQueries({
|
||||
// include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS]
|
||||
// });
|
||||
break;
|
||||
case "tag-added":
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversationId
|
||||
}),
|
||||
fields: {
|
||||
job_conversations(existingJobConversations = []) {
|
||||
return [...existingJobConversations, { __ref: `jobs:${jobId}` }];
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "tag-removed":
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversationId
|
||||
}),
|
||||
fields: {
|
||||
job_conversations(existingJobConversations = []) {
|
||||
return existingJobConversations.filter((jobRef) => jobRef.__ref !== `jobs:${jobId}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "label-changed":
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversationId
|
||||
}),
|
||||
fields: {
|
||||
label() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Unhandled conversation change type: ${type}`);
|
||||
if (!cacheId) {
|
||||
console.error(`Could not find conversation with id: ${conversationId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
client.cache.modify({
|
||||
id: cacheId,
|
||||
fields: {
|
||||
...Object.fromEntries(
|
||||
Object.entries(fields).map(([key, value]) => [
|
||||
key,
|
||||
(cached) => (value !== undefined ? value : cached) // Update with new value or keep existing
|
||||
])
|
||||
),
|
||||
...(type === "conversation-marked-read" && {
|
||||
messages_aggregate: () => ({
|
||||
aggregate: { count: 0 } // Reset unread count
|
||||
})
|
||||
}),
|
||||
...(type === "tag-added" && {
|
||||
job_conversations: (existing = []) => {
|
||||
// Merge existing job_conversations with new ones
|
||||
return [...existing, ...job_conversations];
|
||||
}
|
||||
}),
|
||||
...(type === "tag-removed" && {
|
||||
job_conversations: (existing = [], { readField }) =>
|
||||
existing.filter((jobConversationRef) => readField("jobid", jobConversationRef) !== data.jobId)
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
socket.on("new-message-summary", handleNewMessageSummary);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Tag } from "antd";
|
||||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
export default function ChatConversationTitleTags({ jobConversations }) {
|
||||
export default function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
||||
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const handleRemoveTag = (jobId) => {
|
||||
const convId = jobConversations[0].conversationid;
|
||||
@@ -27,6 +29,16 @@ export default function ChatConversationTitleTags({ jobConversations }) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}).then(() => {
|
||||
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", {
|
||||
conversationId: convId,
|
||||
|
||||
@@ -7,14 +7,17 @@ import ChatLabelComponent from "../chat-label/chat-label.component";
|
||||
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
||||
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
||||
|
||||
export default function ChatConversationTitle({ conversation }) {
|
||||
export default function ChatConversationTitle({ conversation, bodyshop }) {
|
||||
return (
|
||||
<Space wrap>
|
||||
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
||||
<ChatLabelComponent conversation={conversation} />
|
||||
<ChatLabelComponent conversation={conversation} bodyshop={bodyshop} />
|
||||
<ChatPrintButton conversation={conversation} />
|
||||
<ChatConversationTitleTags jobConversations={(conversation && conversation.job_conversations) || []} />
|
||||
<ChatTagRoContainer conversation={conversation || []} />
|
||||
<ChatConversationTitleTags
|
||||
jobConversations={(conversation && conversation.job_conversations) || []}
|
||||
bodyshop={bodyshop}
|
||||
/>
|
||||
<ChatTagRoContainer conversation={conversation || []} bodyshop={bodyshop} />
|
||||
<ChatArchiveButton conversation={conversation} />
|
||||
</Space>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,13 @@ import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||
import "./chat-conversation.styles.scss";
|
||||
|
||||
export default function ChatConversationComponent({ subState, conversation, messages, handleMarkConversationAsRead }) {
|
||||
export default function ChatConversationComponent({
|
||||
subState,
|
||||
conversation,
|
||||
messages,
|
||||
handleMarkConversationAsRead,
|
||||
bodyshop
|
||||
}) {
|
||||
const [loading, error] = subState;
|
||||
|
||||
if (loading) return <LoadingSkeleton />;
|
||||
@@ -18,7 +24,7 @@ export default function ChatConversationComponent({ subState, conversation, mess
|
||||
onMouseDown={handleMarkConversationAsRead}
|
||||
onKeyDown={handleMarkConversationAsRead}
|
||||
>
|
||||
<ChatConversationTitle conversation={conversation} />
|
||||
<ChatConversationTitle conversation={conversation} bodyshop={bodyshop} />
|
||||
<ChatMessageListComponent messages={messages} />
|
||||
<ChatSendMessage conversation={conversation} />
|
||||
</div>
|
||||
|
||||
@@ -74,6 +74,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
conversation={convoData ? convoData.conversations_by_pk : {}}
|
||||
messages={convoData ? convoData.conversations_by_pk.messages : []}
|
||||
handleMarkConversationAsRead={handleMarkConversationAsRead}
|
||||
bodyshop={bodyshop}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Input, notification, Spin, Tag, Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
export default function ChatLabel({ conversation }) {
|
||||
export default function ChatLabel({ conversation, bodyshop }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [value, setValue] = useState(conversation.label);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [updateLabel] = useMutation(UPDATE_CONVERSATION_LABEL);
|
||||
@@ -26,6 +28,14 @@ export default function ChatLabel({ conversation }) {
|
||||
})
|
||||
});
|
||||
} else {
|
||||
if (socket) {
|
||||
socket.emit("conversation-modified", {
|
||||
type: "label-updated",
|
||||
conversationId: conversation.id,
|
||||
bodyshopId: bodyshop.id,
|
||||
label: value
|
||||
});
|
||||
}
|
||||
setEditing(false);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,16 +2,18 @@ import { PlusOutlined } from "@ant-design/icons";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Tag } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
||||
import ChatTagRo from "./chat-tag-ro.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
export default function ChatTagRoContainer({ conversation }) {
|
||||
export default function ChatTagRoContainer({ conversation, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const [loadRo, { loading, data }] = useLazyQuery(SEARCH_FOR_JOBS);
|
||||
|
||||
@@ -32,7 +34,31 @@ export default function ChatTagRoContainer({ conversation }) {
|
||||
|
||||
const handleInsertTag = (value, option) => {
|
||||
logImEXEvent("messaging_add_job_tag");
|
||||
insertTag({ variables: { jobId: option.key } });
|
||||
|
||||
insertTag({
|
||||
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]
|
||||
});
|
||||
}
|
||||
});
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -149,6 +149,23 @@ const cache = new InMemoryCache({
|
||||
fields: {
|
||||
conversations: offsetLimitPagination()
|
||||
}
|
||||
},
|
||||
conversations: {
|
||||
fields: {
|
||||
job_conversations: {
|
||||
keyArgs: false, // Indicates that all job_conversations share the same key
|
||||
merge(existing = [], incoming) {
|
||||
// Merge existing and incoming job_conversations
|
||||
const merged = [
|
||||
...existing,
|
||||
...incoming.filter(
|
||||
(incomingItem) => !existing.some((existingItem) => existingItem.__ref === incomingItem.__ref)
|
||||
)
|
||||
];
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -145,7 +145,6 @@ const redisSocketEvents = ({
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
socket.emit("error", { message: "Failed to join conversation" });
|
||||
}
|
||||
};
|
||||
const leaveConversationRoom = ({ bodyshopId, conversationId }) => {
|
||||
@@ -162,6 +161,24 @@ const redisSocketEvents = ({
|
||||
}
|
||||
};
|
||||
|
||||
const conversationModified = ({ bodyshopId, conversationId, ...fields }) => {
|
||||
try {
|
||||
// Retrieve the room name for the conversation
|
||||
const room = getBodyshopConversationRoom({ bodyshopId, conversationId });
|
||||
// Emit the updated data to all clients in the room
|
||||
io.to(room).emit("conversation-changed", {
|
||||
conversationId,
|
||||
...fields
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log("Failed to handle conversation modification", "error", "io-redis", null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
socket.on("conversation-modified", conversationModified);
|
||||
socket.on("join-bodyshop-conversation", joinConversationRoom);
|
||||
socket.on("leave-bodyshop-conversation", leaveConversationRoom);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user