shoc-frontend-new/src/components/notifications/use-notification-center.ts
Alexandre Brandizzi 31e9db2d02 feat(notifications): SLA at-risk banner, 50% toast and breach acknowledgement
Dispatchers see a one-line banner and a one-time toast while a Reactive/Emergency work order is past half of its SEV response window; the banner opens the board on exactly those work orders and dismisses for the session. Missed deadlines stay in the notification center until acknowledged, and Clear all skips them.
2026-09-25 11:36:25 -03:00

59 lines
2.3 KiB
TypeScript

import { useMemo } from "react";
import { useNavigate } from "react-router";
import type { NotificationItem } from "@/domain/notifications/types/notification-feed";
import { useNotificationSession } from "@/domain/notifications/session/notification-session-context";
import { useAcknowledgeSlaBreach } from "@/domain/notifications/use-cases/use-acknowledge-sla-breach";
import { useNotificationFeed } from "@/domain/notifications/use-cases/use-notification-feed";
import { notificationTargetUrl } from "@/domain/notifications/utils/notification-target-url";
import {
isDismissable,
visibleNotificationSections,
} from "@/domain/notifications/utils/visible-notification-sections";
import { useAuthContext } from "@/providers/auth-context";
export function useNotificationCenter(onNavigate?: () => void) {
const navigate = useNavigate();
const { user } = useAuthContext();
const feed = useNotificationFeed();
const { dismissedIds, readIds, dismiss, markRead } = useNotificationSession();
const sections = useMemo(
() => visibleNotificationSections(feed.data?.sections ?? [], dismissedIds),
[feed.data, dismissedIds],
);
const items = useMemo(() => sections.flatMap((section) => section.items), [sections]);
const unreadIds = useMemo(
() => new Set(items.filter((item) => !readIds.has(item.id)).map((item) => item.id)),
[items, readIds],
);
const acknowledgeBreach = useAcknowledgeSlaBreach();
const acknowledge = (item: NotificationItem) => {
if (item.rowType !== "acknowledge" || item.target.workOrderId == null) return;
acknowledgeBreach.mutate(item.target.workOrderId);
};
const acknowledgingWorkOrderId = acknowledgeBreach.isPending ? acknowledgeBreach.variables : null;
const open = (item: NotificationItem, url = notificationTargetUrl(item, user?.id)) => {
markRead([item.id]);
if (url == null) return;
onNavigate?.();
navigate(url);
};
return {
sections,
unreadIds,
isLoading: feed.isLoading,
hasData: feed.data != null,
error: feed.error,
open,
dismiss: (item: NotificationItem) => dismiss([item.id]),
acknowledge,
acknowledgingWorkOrderId,
dismissAll: () => dismiss(items.filter(isDismissable).map((item) => item.id)),
markAllRead: () => markRead(items.map((item) => item.id)),
};
}
export type NotificationCenter = ReturnType<typeof useNotificationCenter>;