Initial
This commit is contained in:
@@ -267,7 +267,17 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
||||
return;
|
||||
}
|
||||
|
||||
const { conversationId, type, job_conversations, messageIds, ...fields } = data;
|
||||
const {
|
||||
conversationId,
|
||||
type,
|
||||
job_conversations,
|
||||
messageIds, // used by "conversation-marked-read"
|
||||
messageIdsMarkedRead, // used by "conversation-marked-unread"
|
||||
lastUnreadMessageId, // used by "conversation-marked-unread"
|
||||
unreadCount, // used by "conversation-marked-unread"
|
||||
...fields
|
||||
} = data;
|
||||
|
||||
logLocal("handleConversationChanged - Start", data);
|
||||
|
||||
const updatedAt = new Date().toISOString();
|
||||
@@ -313,15 +323,65 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
||||
return message;
|
||||
});
|
||||
},
|
||||
// Keep unread badge in sync (badge uses messages_aggregate.aggregate.count)
|
||||
messages_aggregate: () => ({
|
||||
__typename: "messages_aggregate",
|
||||
aggregate: { __typename: "messages_aggregate_fields", count: 0 }
|
||||
})
|
||||
}),
|
||||
unreadcnt: () => 0
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "conversation-marked-unread": {
|
||||
if (!conversationId) break;
|
||||
|
||||
const safeUnreadCount = typeof unreadCount === "number" ? unreadCount : 1;
|
||||
const idsMarkedRead = Array.isArray(messageIdsMarkedRead) ? messageIdsMarkedRead : [];
|
||||
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
||||
fields: {
|
||||
// Bubble the conversation up in the list (since UI sorts by updated_at)
|
||||
updated_at: () => updatedAt,
|
||||
|
||||
// If details are already cached, flip the read flags appropriately
|
||||
messages(existingMessages = [], { readField }) {
|
||||
if (!Array.isArray(existingMessages) || existingMessages.length === 0) return existingMessages;
|
||||
|
||||
return existingMessages.map((msg) => {
|
||||
const id = readField("id", msg);
|
||||
|
||||
if (lastUnreadMessageId && id === lastUnreadMessageId) {
|
||||
return { ...msg, read: false };
|
||||
}
|
||||
|
||||
if (idsMarkedRead.includes(id)) {
|
||||
return { ...msg, read: true };
|
||||
}
|
||||
|
||||
return msg;
|
||||
});
|
||||
},
|
||||
|
||||
// Update unread badge
|
||||
messages_aggregate: () => ({
|
||||
__typename: "messages_aggregate",
|
||||
aggregate: {
|
||||
__typename: "messages_aggregate_fields",
|
||||
count: safeUnreadCount
|
||||
}
|
||||
}),
|
||||
|
||||
// Optional: keep legacy/parallel unread field consistent if present
|
||||
unreadcnt: () => safeUnreadCount
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "conversation-created":
|
||||
updateConversationList({ ...fields, job_conversations, updated_at: updatedAt });
|
||||
break;
|
||||
|
||||
@@ -12,7 +12,7 @@ import _ from "lodash";
|
||||
import { ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries.js";
|
||||
import { GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS } from "../../graphql/phone-number-opt-out.queries.js";
|
||||
import { phone } from "phone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
@@ -29,13 +29,26 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [, forceUpdate] = useState(false);
|
||||
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
|
||||
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
||||
|
||||
const phoneNumbers = useMemo(() => {
|
||||
return (conversationList || [])
|
||||
.map((item) => {
|
||||
try {
|
||||
const p = phone(item.phone_num, "CA")?.phoneNumber;
|
||||
return p ? p.replace(/^\+1/, "") : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, [conversationList]);
|
||||
|
||||
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS, {
|
||||
variables: {
|
||||
bodyshopid: bodyshop.id,
|
||||
bodyshopid: bodyshop?.id,
|
||||
phone_numbers: phoneNumbers
|
||||
},
|
||||
skip: !conversationList.length,
|
||||
skip: !bodyshop?.id || phoneNumbers.length === 0,
|
||||
fetchPolicy: "cache-and-network"
|
||||
});
|
||||
|
||||
@@ -58,15 +71,25 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
return _.orderBy(conversationList, ["updated_at"], ["desc"]);
|
||||
}, [conversationList]);
|
||||
|
||||
const renderConversation = (index, t) => {
|
||||
const renderConversation = (index) => {
|
||||
const item = sortedConversationList[index];
|
||||
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
|
||||
const hasOptOutEntry = optOutMap.has(normalizedPhone);
|
||||
|
||||
const normalizedPhone = (() => {
|
||||
try {
|
||||
return phone(item.phone_num, "CA")?.phoneNumber?.replace(/^\+1/, "") || "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
})();
|
||||
|
||||
const hasOptOutEntry = normalizedPhone ? optOutMap.has(normalizedPhone) : false;
|
||||
|
||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||
const cardContentLeft =
|
||||
item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>)
|
||||
: null;
|
||||
|
||||
const names = <>{_.uniq(item.job_conversations.map((j) => OwnerNameDisplayFunction(j.job)))}</>;
|
||||
const cardTitle = (
|
||||
<>
|
||||
@@ -80,9 +103,10 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const cardExtra = (
|
||||
<>
|
||||
<Badge count={item.messages_aggregate.aggregate.count} />
|
||||
<Badge count={item.messages_aggregate?.aggregate?.count || 0} />
|
||||
{hasOptOutEntry && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<Tag color="red" icon={<ExclamationCircleOutlined />}>
|
||||
@@ -92,6 +116,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const getCardStyle = () =>
|
||||
item.id === selectedConversation
|
||||
? { backgroundColor: "var(--card-selected-bg)" }
|
||||
@@ -104,24 +129,8 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`}
|
||||
>
|
||||
<Card style={getCardStyle()} variant={true} size="small" extra={cardExtra} title={cardTitle}>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "70%",
|
||||
textAlign: "left"
|
||||
}}
|
||||
>
|
||||
{cardContentLeft}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "30%",
|
||||
textAlign: "right"
|
||||
}}
|
||||
>
|
||||
{cardContentRight}
|
||||
</div>
|
||||
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div>
|
||||
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
);
|
||||
@@ -131,7 +140,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
<div className="chat-list-container">
|
||||
<Virtuoso
|
||||
data={sortedConversationList}
|
||||
itemContent={(index) => renderConversation(index, t)}
|
||||
itemContent={(index) => renderConversation(index)}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,24 +5,24 @@ import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conv
|
||||
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";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { connect } from "react-redux";
|
||||
import ChatMarkUnreadButton from "../chat-mark-unread-button/chat-mark-unread-button.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export function ChatConversationTitle({ conversation }) {
|
||||
export function ChatConversationTitle({ conversation, onMarkUnread, markUnreadDisabled, markUnreadLoading }) {
|
||||
return (
|
||||
<Space className="chat-title" wrap>
|
||||
<PhoneNumberFormatter>{conversation?.phone_num}</PhoneNumberFormatter>
|
||||
|
||||
<ChatLabelComponent conversation={conversation} />
|
||||
<ChatPrintButton conversation={conversation} />
|
||||
|
||||
<ChatConversationTitleTags jobConversations={conversation?.job_conversations || []} />
|
||||
<ChatTagRoContainer conversation={conversation || []} />
|
||||
|
||||
<ChatMarkUnreadButton disabled={markUnreadDisabled} loading={markUnreadLoading} onMarkUnread={onMarkUnread} />
|
||||
|
||||
<ChatArchiveButton conversation={conversation} />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitle);
|
||||
export default ChatConversationTitle;
|
||||
|
||||
@@ -19,7 +19,9 @@ export function ChatConversationComponent({
|
||||
conversation,
|
||||
messages,
|
||||
handleMarkConversationAsRead,
|
||||
bodyshop
|
||||
handleMarkLastMessageAsUnread,
|
||||
markingAsUnreadInProgress,
|
||||
canMarkUnread
|
||||
}) {
|
||||
const [loading, error] = subState;
|
||||
|
||||
@@ -33,7 +35,12 @@ export function ChatConversationComponent({
|
||||
onMouseDown={handleMarkConversationAsRead}
|
||||
onKeyDown={handleMarkConversationAsRead}
|
||||
>
|
||||
<ChatConversationTitle conversation={conversation} bodyshop={bodyshop} />
|
||||
<ChatConversationTitle
|
||||
conversation={conversation}
|
||||
onMarkUnread={handleMarkLastMessageAsUnread}
|
||||
markUnreadDisabled={!canMarkUnread}
|
||||
markUnreadLoading={markingAsUnreadInProgress}
|
||||
/>
|
||||
<ChatMessageListComponent messages={messages} />
|
||||
<ChatSendMessage conversation={conversation} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { gql, useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||
import axios from "axios";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||
@@ -18,8 +18,8 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
const client = useApolloClient();
|
||||
const { socket } = useSocket();
|
||||
const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
|
||||
const [markingAsUnreadInProgress, setMarkingAsUnreadInProgress] = useState(false);
|
||||
|
||||
// Fetch conversation details
|
||||
const {
|
||||
loading: convoLoading,
|
||||
error: convoError,
|
||||
@@ -27,24 +27,23 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
} = useQuery(GET_CONVERSATION_DETAILS, {
|
||||
variables: { conversationId: selectedConversation },
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !selectedConversation
|
||||
});
|
||||
|
||||
// Subscription for conversation updates
|
||||
const conversation = convoData?.conversations_by_pk;
|
||||
|
||||
// Subscription for conversation updates (used when socket is NOT connected)
|
||||
useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, {
|
||||
skip: socket?.connected,
|
||||
skip: socket?.connected || !selectedConversation,
|
||||
variables: { conversationId: selectedConversation },
|
||||
onData: ({ data: subscriptionResult, client }) => {
|
||||
// Extract the messages array from the result
|
||||
const messages = subscriptionResult?.data?.messages;
|
||||
if (!messages || messages.length === 0) {
|
||||
console.warn("No messages found in subscription result.");
|
||||
return;
|
||||
}
|
||||
if (!messages || messages.length === 0) return;
|
||||
|
||||
messages.forEach((message) => {
|
||||
const messageRef = client.cache.identify(message);
|
||||
// Write the new message to the cache
|
||||
|
||||
client.cache.writeFragment({
|
||||
id: messageRef,
|
||||
fragment: gql`
|
||||
@@ -64,7 +63,6 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
data: message
|
||||
});
|
||||
|
||||
// Update the conversation cache to include the new message
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({ __typename: "conversations", id: selectedConversation }),
|
||||
fields: {
|
||||
@@ -82,6 +80,28 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Best-effort badge update:
|
||||
* This assumes your list query uses messages_aggregate.aggregate.count as UNREAD inbound count.
|
||||
* If it’s total messages, rename/create a dedicated unread aggregate in the list query and update that field instead.
|
||||
*/
|
||||
const setConversationUnreadCountBestEffort = useCallback(
|
||||
(conversationId, unreadCount) => {
|
||||
if (!conversationId) return;
|
||||
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
||||
fields: {
|
||||
messages_aggregate(existing) {
|
||||
if (!existing?.aggregate) return existing;
|
||||
return { ...existing, aggregate: { ...existing.aggregate, count: unreadCount } };
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
[client.cache]
|
||||
);
|
||||
|
||||
const updateCacheWithReadMessages = useCallback(
|
||||
(conversationId, messageIds) => {
|
||||
if (!conversationId || !messageIds?.length) return;
|
||||
@@ -89,13 +109,34 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
messageIds.forEach((messageId) => {
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({ __typename: "messages", id: messageId }),
|
||||
fields: {
|
||||
read: () => true
|
||||
}
|
||||
fields: { read: () => true }
|
||||
});
|
||||
});
|
||||
|
||||
setConversationUnreadCountBestEffort(conversationId, 0);
|
||||
},
|
||||
[client.cache]
|
||||
[client.cache, setConversationUnreadCountBestEffort]
|
||||
);
|
||||
|
||||
const applyUnreadStateWithMaxOneUnread = useCallback(
|
||||
({ conversationId, lastUnreadMessageId, messageIdsMarkedRead = [], unreadCount = 1 }) => {
|
||||
if (!conversationId || !lastUnreadMessageId) return;
|
||||
|
||||
messageIdsMarkedRead.forEach((id) => {
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({ __typename: "messages", id }),
|
||||
fields: { read: () => true }
|
||||
});
|
||||
});
|
||||
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({ __typename: "messages", id: lastUnreadMessageId }),
|
||||
fields: { read: () => false }
|
||||
});
|
||||
|
||||
setConversationUnreadCountBestEffort(conversationId, unreadCount);
|
||||
},
|
||||
[client.cache, setConversationUnreadCountBestEffort]
|
||||
);
|
||||
|
||||
// WebSocket event handlers
|
||||
@@ -103,20 +144,25 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
if (!socket?.connected) return;
|
||||
|
||||
const handleConversationChange = (data) => {
|
||||
if (data.type === "conversation-marked-read") {
|
||||
const { conversationId, messageIds } = data;
|
||||
updateCacheWithReadMessages(conversationId, messageIds);
|
||||
if (data?.type === "conversation-marked-read") {
|
||||
updateCacheWithReadMessages(data.conversationId, data.messageIds);
|
||||
}
|
||||
|
||||
if (data?.type === "conversation-marked-unread") {
|
||||
applyUnreadStateWithMaxOneUnread({
|
||||
conversationId: data.conversationId,
|
||||
lastUnreadMessageId: data.lastUnreadMessageId,
|
||||
messageIdsMarkedRead: data.messageIdsMarkedRead,
|
||||
unreadCount: data.unreadCount
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
socket.on("conversation-changed", handleConversationChange);
|
||||
return () => socket.off("conversation-changed", handleConversationChange);
|
||||
}, [socket, updateCacheWithReadMessages, applyUnreadStateWithMaxOneUnread]);
|
||||
|
||||
return () => {
|
||||
socket.off("conversation-changed", handleConversationChange);
|
||||
};
|
||||
}, [socket, updateCacheWithReadMessages]);
|
||||
|
||||
// Join and leave conversation via WebSocket
|
||||
// Join/leave conversation via WebSocket
|
||||
useEffect(() => {
|
||||
if (!socket?.connected || !selectedConversation || !bodyshop?.id) return;
|
||||
|
||||
@@ -133,15 +179,21 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
};
|
||||
}, [socket, bodyshop, selectedConversation]);
|
||||
|
||||
// Mark conversation as read
|
||||
const handleMarkConversationAsRead = async () => {
|
||||
if (!convoData || markingAsReadInProgress) return;
|
||||
const inboundNonSystemMessages = useMemo(() => {
|
||||
const msgs = conversation?.messages || [];
|
||||
return msgs
|
||||
.filter((m) => m && !m.isoutbound && !m.is_system)
|
||||
.slice()
|
||||
.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
||||
}, [conversation?.messages]);
|
||||
|
||||
const conversation = convoData.conversations_by_pk;
|
||||
if (!conversation) return;
|
||||
const canMarkUnread = inboundNonSystemMessages.length > 0;
|
||||
|
||||
const handleMarkConversationAsRead = async () => {
|
||||
if (!conversation || markingAsReadInProgress) return;
|
||||
|
||||
const unreadMessageIds = conversation.messages
|
||||
?.filter((message) => !message.read && !message.isoutbound)
|
||||
?.filter((message) => !message.read && !message.isoutbound && !message.is_system)
|
||||
.map((message) => message.id);
|
||||
|
||||
if (unreadMessageIds?.length > 0) {
|
||||
@@ -162,12 +214,48 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkLastMessageAsUnread = async () => {
|
||||
if (!conversation || markingAsUnreadInProgress) return;
|
||||
if (!bodyshop?.id || !bodyshop?.imexshopid) return;
|
||||
|
||||
const lastInbound = inboundNonSystemMessages[inboundNonSystemMessages.length - 1];
|
||||
if (!lastInbound?.id) return;
|
||||
|
||||
setMarkingAsUnreadInProgress(true);
|
||||
try {
|
||||
const res = await axios.post("/sms/markLastMessageUnread", {
|
||||
conversationId: conversation.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
bodyshopid: bodyshop.id
|
||||
});
|
||||
|
||||
const payload = res?.data || {};
|
||||
if (payload.lastUnreadMessageId) {
|
||||
applyUnreadStateWithMaxOneUnread({
|
||||
conversationId: conversation.id,
|
||||
lastUnreadMessageId: payload.lastUnreadMessageId,
|
||||
messageIdsMarkedRead: payload.messageIdsMarkedRead || [],
|
||||
unreadCount: typeof payload.unreadCount === "number" ? payload.unreadCount : 1
|
||||
});
|
||||
} else {
|
||||
setConversationUnreadCountBestEffort(conversation.id, 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error marking last message unread:", error.message);
|
||||
} finally {
|
||||
setMarkingAsUnreadInProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ChatConversationComponent
|
||||
subState={[convoLoading, convoError]}
|
||||
conversation={convoData?.conversations_by_pk || {}}
|
||||
messages={convoData?.conversations_by_pk?.messages || []}
|
||||
conversation={conversation || {}}
|
||||
messages={conversation?.messages || []}
|
||||
handleMarkConversationAsRead={handleMarkConversationAsRead}
|
||||
handleMarkLastMessageAsUnread={handleMarkLastMessageAsUnread}
|
||||
markingAsUnreadInProgress={markingAsUnreadInProgress}
|
||||
canMarkUnread={canMarkUnread}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MailOutlined } from "@ant-design/icons";
|
||||
import { Button, Tooltip } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ChatMarkUnreadButton({ disabled, loading, onMarkUnread }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tooltip title={t("messaging.labels.mark_unread")}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<MailOutlined />}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
onMouseDown={(e) => e.stopPropagation()} // prevent parent mark-read handler
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onMarkUnread?.();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,27 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
query UNREAD_CONVERSATION_COUNT {
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) {
|
||||
# How many conversations have at least one unread inbound, non-system message
|
||||
conversations_aggregate(
|
||||
where: {
|
||||
archived: { _eq: false }
|
||||
messages: { read: { _eq: false }, isoutbound: { _eq: false }, is_system: { _eq: false } }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
# How many unread inbound, non-system messages exist (excluding archived conversations)
|
||||
messages_aggregate(
|
||||
where: {
|
||||
read: { _eq: false }
|
||||
isoutbound: { _eq: false }
|
||||
is_system: { _eq: false }
|
||||
conversation: { archived: { _eq: false } }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
@@ -19,7 +39,7 @@ export const CONVERSATION_LIST_QUERY = gql`
|
||||
unreadcnt
|
||||
archived
|
||||
label
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) {
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false }, is_system: { _eq: false } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
@@ -41,6 +61,7 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
|
||||
subscription CONVERSATION_SUBSCRIPTION_BY_PK($conversationId: uuid!) {
|
||||
messages(order_by: { created_at: asc_nulls_first }, where: { conversationid: { _eq: $conversationId } }) {
|
||||
id
|
||||
conversationid
|
||||
status
|
||||
text
|
||||
is_system
|
||||
@@ -76,6 +97,7 @@ export const GET_CONVERSATION_DETAILS = gql`
|
||||
}
|
||||
messages(order_by: { created_at: asc_nulls_first }) {
|
||||
id
|
||||
conversationid
|
||||
status
|
||||
text
|
||||
is_system
|
||||
@@ -110,7 +132,7 @@ export const CONVERSATION_ID_BY_PHONE = gql`
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) {
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false }, is_system: { _eq: false } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
@@ -139,7 +161,7 @@ export const CREATE_CONVERSATION = gql`
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) {
|
||||
messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false }, is_system: { _eq: false } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
|
||||
@@ -2430,7 +2430,8 @@
|
||||
"selectmedia": "Select Media",
|
||||
"sentby": "Sent by {{by}} at {{time}}",
|
||||
"typeamessage": "Send a message...",
|
||||
"unarchive": "Unarchive"
|
||||
"unarchive": "Unarchive",
|
||||
"mark_unread": "Mark as Unread"
|
||||
},
|
||||
"render": {
|
||||
"conversation_list": "Conversation List"
|
||||
|
||||
@@ -2430,7 +2430,8 @@
|
||||
"selectmedia": "",
|
||||
"sentby": "",
|
||||
"typeamessage": "Enviar un mensaje...",
|
||||
"unarchive": ""
|
||||
"unarchive": "",
|
||||
"mark_unread": ""
|
||||
},
|
||||
"render": {
|
||||
"conversation_list": ""
|
||||
|
||||
@@ -2430,7 +2430,8 @@
|
||||
"selectmedia": "",
|
||||
"sentby": "",
|
||||
"typeamessage": "Envoyer un message...",
|
||||
"unarchive": ""
|
||||
"unarchive": "",
|
||||
"mark_unread": ""
|
||||
},
|
||||
"render": {
|
||||
"conversation_list": ""
|
||||
|
||||
Reference in New Issue
Block a user