mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 17:29:45 +00:00
Two issues: 1. Unread count didn't always clear when opening / viewing a conversation. The reset effect keyed off conversationId only, so a new inbound message arriving while the thread was already open would get the server to bump unread_count to 1, but the reset effect wouldn't re-fire. Split the fetch and unread-reset effects: fetch stays keyed on conversationId (no unnecessary refetches on every new message), and a second effect keyed on [conversationId, hasUnread] flips unread back to 0 whenever it surfaces on the active conversation. The hasUnread gate still prevents the reset→update→realtime→re-run loop. 2. No indicator elsewhere in the app when new messages arrive. Added useTotalUnread() hook — one-time query + realtime subscription on the conversations table — and a pulsing green dot on the sidebar Inbox entry when totalUnread > 0. Dot is hidden while on /inbox itself (redundant with the per-conversation badges there). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { createClient } from "@/lib/supabase/client";
|
|
import type { Conversation } from "@/types";
|
|
|
|
/**
|
|
* Count of conversations with at least one unread inbound message for
|
|
* the current user. Used by the sidebar to surface a green dot on the
|
|
* Inbox nav entry when the user is elsewhere in the app.
|
|
*
|
|
* Lives on its own realtime channel (distinct from the inbox page's
|
|
* "inbox-realtime") so both can coexist without sharing state.
|
|
*/
|
|
export function useTotalUnread(): number {
|
|
const [total, setTotal] = useState(0);
|
|
|
|
// Keep a live local mirror of {id: unread_count} so INSERT/UPDATE/DELETE
|
|
// events can adjust the total in O(1) without refetching.
|
|
const countsRef = useRef<Map<string, number>>(new Map());
|
|
|
|
useEffect(() => {
|
|
const supabase = createClient();
|
|
let cancelled = false;
|
|
|
|
// Initial load. RLS scopes this to the signed-in user automatically —
|
|
// no explicit user_id filter needed here.
|
|
(async () => {
|
|
const { data, error } = await supabase
|
|
.from("conversations")
|
|
.select("id, unread_count");
|
|
if (cancelled || error || !data) return;
|
|
|
|
const map = new Map<string, number>();
|
|
let sum = 0;
|
|
for (const row of data as { id: string; unread_count: number }[]) {
|
|
const n = row.unread_count ?? 0;
|
|
map.set(row.id, n);
|
|
if (n > 0) sum += 1;
|
|
}
|
|
countsRef.current = map;
|
|
setTotal(sum);
|
|
})();
|
|
|
|
const channel = supabase
|
|
.channel("total-unread-realtime")
|
|
.on(
|
|
"postgres_changes",
|
|
{ event: "*", schema: "public", table: "conversations" },
|
|
(payload) => {
|
|
const map = countsRef.current;
|
|
if (payload.eventType === "DELETE") {
|
|
const oldRow = payload.old as Partial<Conversation>;
|
|
if (oldRow.id) map.delete(oldRow.id);
|
|
} else {
|
|
const row = payload.new as Conversation;
|
|
map.set(row.id, row.unread_count ?? 0);
|
|
}
|
|
// Recompute — cheap, conversations per user stay small.
|
|
let sum = 0;
|
|
for (const n of map.values()) if (n > 0) sum += 1;
|
|
setTotal(sum);
|
|
},
|
|
)
|
|
.subscribe();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
supabase.removeChannel(channel);
|
|
};
|
|
}, []);
|
|
|
|
return total;
|
|
}
|