}>
@@ -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" : ""}`}
>
-
- {cardContentLeft}
-
-
- {cardContentRight}
-
+ {cardContentLeft}
+ {cardContentRight}
);
@@ -131,7 +140,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
renderConversation(index, t)}
+ itemContent={(index) => renderConversation(index)}
style={{ height: "100%", width: "100%" }}
/>
diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx
index 86b8cca60..9c3bbaf11 100644
--- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx
+++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx
@@ -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 (
{conversation?.phone_num}
+
+
+
+
+
);
}
-export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitle);
+export default ChatConversationTitle;
diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx
index f5812e34f..c43443812 100644
--- a/client/src/components/chat-conversation/chat-conversation.component.jsx
+++ b/client/src/components/chat-conversation/chat-conversation.component.jsx
@@ -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}
>
-
+
diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx
index e53ec8172..ed90b9053 100644
--- a/client/src/components/chat-conversation/chat-conversation.container.jsx
+++ b/client/src/components/chat-conversation/chat-conversation.container.jsx
@@ -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 (
);
}
diff --git a/client/src/components/chat-mark-unread-button/chat-mark-unread-button.component.jsx b/client/src/components/chat-mark-unread-button/chat-mark-unread-button.component.jsx
new file mode 100644
index 000000000..b8e70a43a
--- /dev/null
+++ b/client/src/components/chat-mark-unread-button/chat-mark-unread-button.component.jsx
@@ -0,0 +1,24 @@
+import { Button, Tooltip } from "antd";
+import { useTranslation } from "react-i18next";
+
+export default function ChatMarkUnreadButton({ disabled, loading, onMarkUnread }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+ );
+}
diff --git a/client/src/components/chat-messages-list/chat-message-list.styles.scss b/client/src/components/chat-messages-list/chat-message-list.styles.scss
index 1c55a50aa..e833206bc 100644
--- a/client/src/components/chat-messages-list/chat-message-list.styles.scss
+++ b/client/src/components/chat-messages-list/chat-message-list.styles.scss
@@ -10,6 +10,11 @@
border-radius: 4px;
}
+.unread-button {
+ height: 20px;
+ border-radius: 4px;
+}
+
.chat-title {
margin-bottom: 5px;
}
diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js
index 598be73dd..fa308a3ff 100644
--- a/client/src/graphql/conversations.queries.js
+++ b/client/src/graphql/conversations.queries.js
@@ -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
}
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index ead7b78c6..f8a3bc76c 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -2445,7 +2445,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"
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 81cfa9472..c1ecea57c 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -2442,7 +2442,8 @@
"selectmedia": "",
"sentby": "",
"typeamessage": "Enviar un mensaje...",
- "unarchive": ""
+ "unarchive": "",
+ "mark_unread": ""
},
"render": {
"conversation_list": ""
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index f321608fa..cd6757406 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -2442,7 +2442,8 @@
"selectmedia": "",
"sentby": "",
"typeamessage": "Envoyer un message...",
- "unarchive": ""
+ "unarchive": "",
+ "mark_unread": ""
},
"render": {
"conversation_list": ""
diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js
index 82964d49b..6efc55cde 100644
--- a/client/src/utils/GraphQLClient.js
+++ b/client/src/utils/GraphQLClient.js
@@ -148,6 +148,26 @@ const cache = new InMemoryCache({
Query: {
fields: {
// Note: This is required because we switch from a read to an unread state with a toggle,
+ conversations: {
+ keyArgs: ["where", "order_by"], // keep separate caches for archived/unarchived + sort
+ merge(existing = [], incoming = [], { args, readField }) {
+ const offset = args?.offset ?? 0;
+ const merged = existing ? existing.slice(0) : [];
+
+ for (let i = 0; i < incoming.length; i++) {
+ merged[offset + i] = incoming[i];
+ }
+
+ // Deduplicate by id (important when you also upsert via sockets)
+ const seen = new Set();
+ return merged.filter((ref) => {
+ const id = readField("id", ref);
+ if (!id || seen.has(id)) return false;
+ seen.add(id);
+ return true;
+ });
+ }
+ },
notifications: {
merge(existing = [], incoming = [], { readField }) {
// Create a map to deduplicate by __ref
diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js
index b93b1c4e0..d190ea858 100644
--- a/server/graphql-client/queries.js
+++ b/server/graphql-client/queries.js
@@ -123,7 +123,7 @@ mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
exports.INSERT_MESSAGE = `
mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!) {
- update_conversations_by_pk(pk_columns: {id: $conversationid}, _set: {archived: false}) {
+ update_conversations_by_pk(pk_columns: { id: $conversationid }, _set: { archived: false }) {
id
archived
}
@@ -147,6 +147,7 @@ mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!)
image_path
image
isoutbound
+ is_system
msid
read
text
diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js
index c09cc1632..19931318d 100644
--- a/server/routes/smsRoutes.js
+++ b/server/routes/smsRoutes.js
@@ -3,7 +3,7 @@ const router = express.Router();
const twilio = require("twilio");
const { receive } = require("../sms/receive");
const { send } = require("../sms/send");
-const { status, markConversationRead } = require("../sms/status");
+const { status, markConversationRead, markLastMessageUnread } = require("../sms/status");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
// Twilio Webhook Middleware for production
@@ -14,5 +14,6 @@ router.post("/receive", twilioWebhookMiddleware, receive);
router.post("/send", validateFirebaseIdTokenMiddleware, send);
router.post("/status", twilioWebhookMiddleware, status);
router.post("/markConversationRead", validateFirebaseIdTokenMiddleware, markConversationRead);
+router.post("/markLastMessageUnread", validateFirebaseIdTokenMiddleware, markLastMessageUnread);
module.exports = router;
diff --git a/server/sms/send.js b/server/sms/send.js
index db3c35e43..90a98588c 100644
--- a/server/sms/send.js
+++ b/server/sms/send.js
@@ -61,7 +61,8 @@ const send = async (req, res) => {
isoutbound: true,
userid: req.user.email,
image: selectedMedia.length > 0,
- image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : []
+ image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [],
+ is_system: false
};
try {
diff --git a/server/sms/status.js b/server/sms/status.js
index 437da73ab..7dc11d65b 100644
--- a/server/sms/status.js
+++ b/server/sms/status.js
@@ -8,6 +8,46 @@ const {
const logger = require("../utils/logger");
const { phone } = require("phone");
+// Local GraphQL for “mark unread” (kept here to avoid requiring edits to graphql-client/queries.js)
+const GET_LAST_INBOUND_NON_SYSTEM_MESSAGE = `
+ query GetLastInboundNonSystemMessage($conversationId: uuid!) {
+ messages(
+ where: {
+ conversationid: { _eq: $conversationId }
+ isoutbound: { _eq: false }
+ is_system: { _eq: false }
+ }
+ order_by: { created_at: desc }
+ limit: 1
+ ) {
+ id
+ created_at
+ read
+ }
+ }
+`;
+
+const MARK_LAST_INBOUND_MESSAGE_UNREAD_MAX_ONE = `
+ mutation MarkLastInboundMessageUnreadMaxOne($conversationId: uuid!, $lastId: uuid!) {
+ markOthersRead: update_messages(
+ _set: { read: true }
+ where: {
+ conversationid: { _eq: $conversationId }
+ isoutbound: { _eq: false }
+ is_system: { _eq: false }
+ id: { _neq: $lastId }
+ }
+ ) {
+ affected_rows
+ returning { id }
+ }
+
+ markLastUnread: update_messages_by_pk(pk_columns: { id: $lastId }, _set: { read: false }) {
+ id
+ }
+ }
+`;
+
/**
* Handle the status of an SMS message
* @param req
@@ -176,7 +216,80 @@ const markConversationRead = async (req, res) => {
}
};
+/**
+ * Mark last inbound (customer) non-system message as unread.
+ * Enforces: max unread inbound non-system messages per thread = 1
+ *
+ * Body: { conversationId, imexshopid, bodyshopid }
+ */
+const markLastMessageUnread = async (req, res) => {
+ const {
+ ioRedis,
+ ioHelpers: { getBodyshopRoom }
+ } = req;
+
+ const { conversationId, imexshopid, bodyshopid } = req.body;
+
+ if (!conversationId || !imexshopid || !bodyshopid) {
+ return res.status(400).json({ error: "Invalid conversation data provided." });
+ }
+
+ try {
+ const lastResp = await client.request(GET_LAST_INBOUND_NON_SYSTEM_MESSAGE, { conversationId });
+ const last = lastResp?.messages?.[0];
+
+ if (!last?.id) {
+ // No inbound message to mark unread
+ const broadcastRoom = getBodyshopRoom(bodyshopid);
+ ioRedis.to(broadcastRoom).emit("conversation-changed", {
+ type: "conversation-marked-unread",
+ conversationId,
+ lastUnreadMessageId: null,
+ messageIdsMarkedRead: [],
+ unreadCount: 0
+ });
+
+ return res.status(200).json({
+ success: true,
+ conversationId,
+ lastUnreadMessageId: null,
+ messageIdsMarkedRead: [],
+ unreadCount: 0
+ });
+ }
+
+ const mutResp = await client.request(MARK_LAST_INBOUND_MESSAGE_UNREAD_MAX_ONE, {
+ conversationId,
+ lastId: last.id
+ });
+
+ const messageIdsMarkedRead = mutResp?.markOthersRead?.returning?.map((m) => m.id) || [];
+
+ const broadcastRoom = getBodyshopRoom(bodyshopid);
+
+ ioRedis.to(broadcastRoom).emit("conversation-changed", {
+ type: "conversation-marked-unread",
+ conversationId,
+ lastUnreadMessageId: last.id,
+ messageIdsMarkedRead,
+ unreadCount: 1
+ });
+
+ return res.status(200).json({
+ success: true,
+ conversationId,
+ lastUnreadMessageId: last.id,
+ messageIdsMarkedRead,
+ unreadCount: 1
+ });
+ } catch (error) {
+ console.error("Error marking last message unread:", error);
+ return res.status(500).json({ error: "Failed to mark last message unread." });
+ }
+};
+
module.exports = {
status,
- markConversationRead
+ markConversationRead,
+ markLastMessageUnread
};