Initial commit: SKEEN Derma Experts - Sistema Integral de Gestión Clínica
- Frontend React (SKEEN Brand) con Vite, TypeScript, Tailwind - Frontend Homenest (versión alternativa) - Módulos Odoo 17 custom (citas, pacientes, monedero, pagos, ventas, inventario, whatsapp) - WACRM fork (Next.js 16 + Supabase) - Hermes + Bridge + Skills (Qwen3.6 via Nan Builders) - Scripts de migración y operación - Documentación extensiva en docs/
This commit is contained in:
63
wacrm/src/hooks/use-unread-notifications.ts
Normal file
63
wacrm/src/hooks/use-unread-notifications.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
import type { Notification } from "@/types";
|
||||
|
||||
/**
|
||||
* Count of unread notifications for the current user. Used by the
|
||||
* sidebar to surface a badge on the Notifications nav entry.
|
||||
*
|
||||
* RLS on `notifications` already scopes every read to `auth.uid() =
|
||||
* user_id`, so no explicit filter is needed here — same pattern as
|
||||
* `useTotalUnread` for conversations.
|
||||
*/
|
||||
export function useUnreadNotifications(): number {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const supabase = createClient();
|
||||
let cancelled = false;
|
||||
|
||||
(async () => {
|
||||
// head:true skips fetching rows — we only need the `count`
|
||||
// supabase-js returns alongside the (empty) response body.
|
||||
const { count: unreadCount, error } = await supabase
|
||||
.from("notifications")
|
||||
.select("*", { count: "exact", head: true })
|
||||
.is("read_at", null);
|
||||
if (cancelled || error) return;
|
||||
setCount(unreadCount ?? 0);
|
||||
})();
|
||||
|
||||
const channel = supabase
|
||||
.channel("notifications-unread-count")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{ event: "*", schema: "public", table: "notifications" },
|
||||
(payload) => {
|
||||
if (payload.eventType === "INSERT") {
|
||||
const row = payload.new as Notification;
|
||||
if (!row.read_at) setCount((n) => n + 1);
|
||||
} else if (payload.eventType === "UPDATE") {
|
||||
// Updates here only ever set read_at (marking a notification
|
||||
// read). Derive purely from the new row so we don't rely on
|
||||
// payload.old columns, which require REPLICA IDENTITY FULL.
|
||||
const newRow = payload.new as Notification;
|
||||
if (newRow.read_at) setCount((n) => Math.max(0, n - 1));
|
||||
} else if (payload.eventType === "DELETE") {
|
||||
const oldRow = payload.old as Partial<Notification>;
|
||||
if (!oldRow.read_at) setCount((n) => Math.max(0, n - 1));
|
||||
}
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
supabase.removeChannel(channel);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return count;
|
||||
}
|
||||
Reference in New Issue
Block a user