38 lines
2.5 KiB
TypeScript
38 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { commentRequest } from "./client";
|
|
import { createContext, useCallback, useContext, useEffect, useRef, useState, type ReactNode } from "react";
|
|
import Link from "next/link";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { useCommentEvents } from "./use-comment-events";
|
|
|
|
type Counts = { counts: Record<string, number>; total: number };
|
|
const UnreadContext = createContext<Counts & { markRead: (target: string) => Promise<void> }>({ counts: {}, total: 0, markRead: async () => {} });
|
|
export function AdminUnreadCommentsProvider({ children }: { children: ReactNode }) {
|
|
const [counts, setCounts] = useState<Counts>({ counts: {}, total: 0 });
|
|
const generation = useRef(0);
|
|
const refresh = useCallback(async () => {
|
|
try { const sequence = ++generation.current; const result = await commentRequest<Counts>("/api/admin/comments/unread"); if (sequence === generation.current) setCounts(result); } catch { /* Protected routes handle expired admin sessions. */ }
|
|
}, []);
|
|
const markRead = useCallback(async (target: string) => {
|
|
try {
|
|
await commentRequest("/api/admin/comments/unread", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ target }) });
|
|
await refresh();
|
|
} catch { /* Reading stays available when the counter cannot be saved. */ }
|
|
}, [refresh]);
|
|
useEffect(() => { void Promise.resolve().then(refresh); }, [refresh]);
|
|
useCommentEvents({ admin: true }, refresh);
|
|
return <UnreadContext value={{ ...counts, markRead }}>{children}</UnreadContext>;
|
|
}
|
|
export function useMarkCommentsRead() { return useContext(UnreadContext).markRead; }
|
|
export function AdminUnreadCount({ target }: { target?: string }) {
|
|
const state = useContext(UnreadContext);
|
|
const count = target ? state.counts[target] ?? 0 : state.total;
|
|
return count > 0 ? <Badge variant="destructive" className="min-w-5 justify-center rounded-full bg-destructive px-1.5 text-background dark:bg-destructive" aria-label={`ความคิดเห็นใหม่ ${count} รายการ`}>{count > 99 ? "99+" : count}</Badge> : null;
|
|
}
|
|
export function AdminUnreadBadge({ target }: { target: string }) {
|
|
const state = useContext(UnreadContext);
|
|
if (!(state.counts[target] > 0)) return null;
|
|
return <Link href={`/admin/comments?target=${encodeURIComponent(target)}`} prefetch={false} className="relative z-20 self-end" aria-label={"ดูความคิดเห็นใหม่"}><AdminUnreadCount target={target} /></Link>;
|
|
}
|