feature/IO-3000-messaging-sockets-migrations2 -
- Checkpoint, Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -142,14 +142,47 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleConversationChanged = (data) => {
|
||||
const handleConversationChanged = async (data) => {
|
||||
if (!data) return;
|
||||
|
||||
const { conversationId, type, job_conversations, ...fields } = data;
|
||||
|
||||
logLocal("handleConversationChanged", data);
|
||||
|
||||
// Identify the conversation in the Apollo cache
|
||||
const updatedAt = new Date().toISOString();
|
||||
|
||||
const updateConversationList = (newConversation) => {
|
||||
try {
|
||||
const existingList = client.cache.readQuery({
|
||||
query: CONVERSATION_LIST_QUERY,
|
||||
variables: { offset: 0 }
|
||||
});
|
||||
|
||||
const updatedList = existingList?.conversations
|
||||
? [
|
||||
newConversation,
|
||||
...existingList.conversations.filter(
|
||||
(conv) => conv.id !== newConversation.id // Prevent duplicates
|
||||
)
|
||||
]
|
||||
: [newConversation];
|
||||
|
||||
client.cache.writeQuery({
|
||||
query: CONVERSATION_LIST_QUERY,
|
||||
variables: { offset: 0 },
|
||||
data: {
|
||||
conversations: updatedList
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating conversation list in the cache:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (type === "conversation-created") {
|
||||
updateConversationList({ ...fields, job_conversations, updated_at: updatedAt });
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheId = client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversationId
|
||||
@@ -161,20 +194,20 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
|
||||
if (type === "conversation-archived") {
|
||||
// Remove all messages associated with this conversation
|
||||
const messageRefs = client.cache.readFragment({
|
||||
id: cacheId,
|
||||
fragment: gql`
|
||||
fragment ConversationMessages on conversations {
|
||||
messages {
|
||||
id
|
||||
try {
|
||||
// Evict messages associated with the conversation
|
||||
const messageRefs = client.cache.readFragment({
|
||||
id: cacheId,
|
||||
fragment: gql`
|
||||
fragment ConversationMessages on conversations {
|
||||
messages {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
});
|
||||
`
|
||||
});
|
||||
|
||||
if (messageRefs?.messages) {
|
||||
messageRefs.messages.forEach((message) => {
|
||||
messageRefs?.messages?.forEach((message) => {
|
||||
const messageCacheId = client.cache.identify({
|
||||
__typename: "messages",
|
||||
id: message.id
|
||||
@@ -183,39 +216,84 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
client.cache.evict({ id: messageCacheId });
|
||||
}
|
||||
});
|
||||
|
||||
// Evict the conversation itself
|
||||
client.cache.evict({ id: cacheId });
|
||||
client.cache.gc(); // Trigger garbage collection
|
||||
} catch (error) {
|
||||
console.error("Error archiving conversation:", error);
|
||||
}
|
||||
|
||||
// Evict the conversation itself
|
||||
client.cache.evict({ id: cacheId });
|
||||
client.cache.gc(); // Trigger garbage collection to clean up unused entries
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "conversation-unarchived") {
|
||||
try {
|
||||
// Fetch the conversation from the database if not already in the cache
|
||||
const existingConversation = client.cache.readQuery({
|
||||
query: GET_CONVERSATION_DETAILS,
|
||||
variables: { conversationId }
|
||||
});
|
||||
|
||||
if (!existingConversation) {
|
||||
const { data: fetchedData } = await client.query({
|
||||
query: GET_CONVERSATION_DETAILS,
|
||||
variables: { conversationId },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
if (fetchedData?.conversations_by_pk) {
|
||||
const conversationData = fetchedData.conversations_by_pk;
|
||||
|
||||
// Enrich conversation data
|
||||
const enrichedConversation = {
|
||||
...conversationData,
|
||||
messages_aggregate: {
|
||||
__typename: "messages_aggregate",
|
||||
aggregate: {
|
||||
__typename: "messages_aggregate_fields",
|
||||
count: conversationData.messages.filter((message) => !message.read && !message.isoutbound).length
|
||||
}
|
||||
},
|
||||
updated_at: updatedAt
|
||||
};
|
||||
|
||||
updateConversationList(enrichedConversation);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the conversation as unarchived in the cache
|
||||
client.cache.modify({
|
||||
id: cacheId,
|
||||
fields: {
|
||||
archived: () => false,
|
||||
updated_at: () => updatedAt
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error unarchiving conversation:", error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle other types of updates (e.g., marked read, tags added/removed)
|
||||
client.cache.modify({
|
||||
id: cacheId,
|
||||
fields: {
|
||||
// This is a catch-all for just sending it fields off conversation
|
||||
...Object.fromEntries(
|
||||
Object.entries(fields).map(([key, value]) => [
|
||||
key,
|
||||
(cached) => (value !== undefined ? value : cached) // Update with new value or keep existing
|
||||
])
|
||||
Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)])
|
||||
),
|
||||
...(type === "conversation-marked-read" && {
|
||||
messages_aggregate: () => ({
|
||||
aggregate: { count: 0 } // Reset unread count
|
||||
__typename: "messages_aggregate",
|
||||
aggregate: { __typename: "messages_aggregate_fields", count: 0 }
|
||||
})
|
||||
}),
|
||||
...(type === "tag-added" && {
|
||||
job_conversations: (existing = []) => {
|
||||
// Merge existing job_conversations with new ones
|
||||
return [...existing, ...job_conversations];
|
||||
}
|
||||
job_conversations: (existing = []) => [...existing, ...job_conversations]
|
||||
}),
|
||||
...(type === "tag-removed" && {
|
||||
job_conversations: (existing = [], { readField }) =>
|
||||
existing.filter((jobConversationRef) => readField("jobid", jobConversationRef) !== data.jobId)
|
||||
existing.filter((jobRef) => readField("jobid", jobRef) !== data.jobId)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { PlusCircleFilled } from "@ant-design/icons";
|
||||
import { Button, Form, Popover } from "antd";
|
||||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
||||
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -17,8 +18,10 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
export function ChatNewConversation({ openChatByPhone }) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const handleFinish = (values) => {
|
||||
openChatByPhone({ phone_num: values.phoneNumber });
|
||||
openChatByPhone({ phone_num: values.phoneNumber, socket });
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { notification } from "antd";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
||||
@@ -9,6 +9,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -21,6 +22,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobid, openChatByPhone }) {
|
||||
const { t } = useTranslation();
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
if (!phone) return <></>;
|
||||
|
||||
if (!bodyshop.messagingservicesid) return <PhoneNumberFormatter>{phone}</PhoneNumberFormatter>;
|
||||
@@ -33,7 +36,7 @@ export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobi
|
||||
const p = parsePhoneNumber(phone, "CA");
|
||||
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
|
||||
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid, socket });
|
||||
} else {
|
||||
notification["error"]({ message: t("messaging.error.invalidphone") });
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select,
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
@@ -24,6 +24,7 @@ import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -49,6 +50,8 @@ export function ScheduleEventComponent({
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const [title, setTitle] = useState(event.title);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const blockContent = (
|
||||
<Space direction="vertical" wrap>
|
||||
<Input
|
||||
@@ -190,7 +193,8 @@ export function ScheduleEventComponent({
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: event.job.id
|
||||
jobid: event.job.id,
|
||||
socket
|
||||
});
|
||||
setMessage(
|
||||
t("appointments.labels.reminder", {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
@@ -26,21 +25,16 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text))
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export function JobPayments({
|
||||
job,
|
||||
jobRO,
|
||||
bodyshop,
|
||||
setMessage,
|
||||
openChatByPhone,
|
||||
setPaymentContext,
|
||||
setCardPaymentContext,
|
||||
refetch
|
||||
}) {
|
||||
export function JobPayments({ job, jobRO, bodyshop, setPaymentContext, setCardPaymentContext, refetch }) {
|
||||
const {
|
||||
treatments: { ImEXPay }
|
||||
} = useSplitTreatments({
|
||||
@@ -133,7 +127,7 @@ export function JobPayments({
|
||||
}
|
||||
];
|
||||
|
||||
//Same as in RO guard. If changed, update in both.
|
||||
//Same as in RO guard. If changed, update in both.
|
||||
const total = useMemo(() => {
|
||||
return (
|
||||
job.payments &&
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
@@ -30,6 +30,7 @@ import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -126,6 +127,7 @@ export function JobsDetailHeaderActions({
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const [voidJob] = useMutation(VOID_JOB);
|
||||
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const {
|
||||
treatments: { ImEXPay }
|
||||
@@ -299,7 +301,8 @@ export function JobsDetailHeaderActions({
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
jobid: job.id,
|
||||
socket
|
||||
});
|
||||
setMessage(
|
||||
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}`
|
||||
@@ -342,7 +345,8 @@ export function JobsDetailHeaderActions({
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
jobid: job.id,
|
||||
socket
|
||||
});
|
||||
setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`);
|
||||
} else {
|
||||
|
||||
@@ -3,13 +3,14 @@ import { Button, Form, message, Popover, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import { parsePhoneNumber } from "libphonenumber-js";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -28,6 +29,7 @@ export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, ope
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [paymentLink, setPaymentLink] = useState(null);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const handleFinish = async ({ amount }) => {
|
||||
setLoading(true);
|
||||
@@ -50,7 +52,8 @@ export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, ope
|
||||
if (p) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
jobid: job.id,
|
||||
socket
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
@@ -106,7 +109,8 @@ export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, ope
|
||||
const p = parsePhoneNumber(job.ownr_ph1, "CA");
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
jobid: job.id,
|
||||
socket
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { getAuth, updatePassword, updateProfile } from "firebase/auth";
|
||||
import { getFirestore } from "firebase/firestore";
|
||||
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
||||
import { store } from "../redux/store";
|
||||
import axios from "axios";
|
||||
|
||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||
initializeApp(config);
|
||||
|
||||
@@ -59,6 +59,8 @@ export const GET_CONVERSATION_DETAILS = gql`
|
||||
id
|
||||
phone_num
|
||||
archived
|
||||
updated_at
|
||||
unreadcnt
|
||||
label
|
||||
job_conversations {
|
||||
jobid
|
||||
@@ -119,6 +121,26 @@ export const CREATE_CONVERSATION = gql`
|
||||
insert_conversations(objects: $conversation) {
|
||||
returning {
|
||||
id
|
||||
phone_num
|
||||
archived
|
||||
label
|
||||
unreadcnt
|
||||
job_conversations {
|
||||
jobid
|
||||
conversationid
|
||||
job {
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,13 @@ import axios from "axios";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CONVERSATION_ID_BY_PHONE, CREATE_CONVERSATION } from "../../graphql/conversations.queries";
|
||||
import {
|
||||
CONVERSATION_ID_BY_PHONE,
|
||||
CONVERSATION_LIST_QUERY,
|
||||
CREATE_CONVERSATION,
|
||||
GET_CONVERSATION_DETAILS,
|
||||
TOGGLE_CONVERSATION_ARCHIVE
|
||||
} from "../../graphql/conversations.queries";
|
||||
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import { selectBodyshop } from "../user/user.selectors";
|
||||
@@ -27,23 +33,24 @@ export function* onOpenChatByPhone() {
|
||||
|
||||
export function* openChatByPhone({ payload }) {
|
||||
logImEXEvent("messaging_open_by_phone");
|
||||
const { phone_num, jobid } = payload;
|
||||
|
||||
const { socket, phone_num, jobid } = payload;
|
||||
const p = parsePhoneNumber(phone_num, "CA");
|
||||
const bodyshop = yield select(selectBodyshop);
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { conversations }
|
||||
} = yield client.query({
|
||||
query: CONVERSATION_ID_BY_PHONE,
|
||||
variables: { phone: p.number },
|
||||
fetchPolicy: 'no-cache'
|
||||
fetchPolicy: "no-cache"
|
||||
});
|
||||
|
||||
if (conversations.length === 0) {
|
||||
// No conversation exists, create a new one
|
||||
const {
|
||||
data: {
|
||||
insert_conversations: { returning: newConversationsId }
|
||||
insert_conversations: { returning: newConversations }
|
||||
}
|
||||
} = yield client.mutate({
|
||||
mutation: CREATE_CONVERSATION,
|
||||
@@ -57,26 +64,107 @@ export function* openChatByPhone({ payload }) {
|
||||
]
|
||||
}
|
||||
});
|
||||
yield put(setSelectedConversation(newConversationsId[0].id));
|
||||
} else if (conversations.length === 1) {
|
||||
//got the ID. Open it.
|
||||
yield put(setSelectedConversation(conversations[0].id));
|
||||
|
||||
//Check to see if this job ID is already a child of it. If not add the tag.
|
||||
if (jobid && !conversations[0].job_conversations.find((jc) => jc.jobid === jobid))
|
||||
const createdConversation = newConversations[0]; // Get the newly created conversation
|
||||
|
||||
// Emit event for new conversation with full details
|
||||
if (socket) {
|
||||
socket.emit("conversation-modified", {
|
||||
bodyshopId: bodyshop.id,
|
||||
type: "conversation-created",
|
||||
...createdConversation
|
||||
});
|
||||
}
|
||||
|
||||
// Set the newly created conversation as selected
|
||||
yield put(setSelectedConversation(createdConversation.id));
|
||||
} else if (conversations.length === 1) {
|
||||
const conversation = conversations[0];
|
||||
|
||||
if (conversation.archived) {
|
||||
// Conversation is archived, unarchive it in the DB
|
||||
const {
|
||||
data: { update_conversations_by_pk: updatedConversation }
|
||||
} = yield client.mutate({
|
||||
mutation: TOGGLE_CONVERSATION_ARCHIVE,
|
||||
variables: {
|
||||
id: conversation.id,
|
||||
archived: false
|
||||
}
|
||||
});
|
||||
|
||||
if (socket) {
|
||||
socket.emit("conversation-modified", {
|
||||
type: "conversation-unarchived",
|
||||
conversationId: updatedConversation.id,
|
||||
bodyshopId: bodyshop.id,
|
||||
archived: false
|
||||
});
|
||||
}
|
||||
|
||||
// Update the conversation list in the cache
|
||||
const existingConversations = client.cache.readQuery({
|
||||
query: CONVERSATION_LIST_QUERY,
|
||||
variables: { offset: 0 }
|
||||
});
|
||||
|
||||
client.cache.writeQuery({
|
||||
query: CONVERSATION_LIST_QUERY,
|
||||
variables: { offset: 0 },
|
||||
data: {
|
||||
conversations: [
|
||||
{
|
||||
...conversation,
|
||||
archived: false,
|
||||
updated_at: new Date().toISOString()
|
||||
},
|
||||
...(existingConversations?.conversations || [])
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the conversation exists in the cache
|
||||
const cacheId = client.cache.identify({
|
||||
__typename: "conversations",
|
||||
id: conversation.id
|
||||
});
|
||||
|
||||
if (!cacheId) {
|
||||
// Fetch the conversation details from the database
|
||||
const { data } = yield client.query({
|
||||
query: GET_CONVERSATION_DETAILS,
|
||||
variables: { conversationId: conversation.id }
|
||||
});
|
||||
|
||||
// Write fetched data to the cache
|
||||
client.cache.writeQuery({
|
||||
query: GET_CONVERSATION_DETAILS,
|
||||
variables: { conversationId: conversation.id },
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// Open the conversation
|
||||
yield put(setSelectedConversation(conversation.id));
|
||||
|
||||
// Check and add job tag if needed
|
||||
if (jobid && !conversation.job_conversations.find((jc) => jc.jobid === jobid)) {
|
||||
yield client.mutate({
|
||||
mutation: INSERT_CONVERSATION_TAG,
|
||||
variables: {
|
||||
conversationId: conversations[0].id,
|
||||
conversationId: conversation.id,
|
||||
jobId: jobid
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log("ERROR: Multiple conversations found. ");
|
||||
// Multiple conversations found
|
||||
console.error("ERROR: Multiple conversations found.");
|
||||
yield put(setSelectedConversation(null));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in sendMessage saga.", error);
|
||||
console.error("Error in openChatByPhone saga.", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user