import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { BellOff, CheckCheck, Loader2, Maximize2, Trash2 } from "lucide-react"; import { usePluginApi, useTabs, useTranslation, } from "@termix/plugin-sdk/frontend"; import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, cn, } from "@termix/plugin-sdk/ui"; import { SEVERITIES, type AlertItem, type Severity } from "../types"; import { createAlertsApi } from "./api"; import { AlertList } from "./AlertList"; import { sourceLabel } from "./format"; import { useAlertsVersion, type AlertsStore } from "./store"; const ALL = "__all"; export function InboxView({ store, compact = false, onOpenAll, }: { store: AlertsStore; compact?: boolean; onOpenAll?: () => void; }) { const { t } = useTranslation(); const client = usePluginApi(); const api = useMemo(() => createAlertsApi(client), [client]); const tabs = useTabs(); const version = useAlertsVersion(store); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [hasMore, setHasMore] = useState(false); const [unreadOnly, setUnreadOnly] = useState(false); const [severity, setSeverity] = useState(""); const [source, setSource] = useState(""); const [sources, setSources] = useState([]); const pageSize = compact ? 25 : 50; const load = useCallback(async () => { try { const result = await api.items({ unread: unreadOnly, severity: severity || undefined, source: source || undefined, limit: pageSize, }); setItems(result.items); setHasMore(result.items.length === pageSize); store.setUnread(result.unread); } catch (error) { toast.error(error instanceof Error ? error.message : String(error)); } finally { setLoading(false); } }, [api, pageSize, severity, source, store, unreadOnly]); useEffect(() => { void load(); }, [load, version]); useEffect(() => { if (compact) return; api .categories() .then((rows) => setSources([...new Set(rows.map((row) => row.source))].sort()), ) .catch(() => {}); }, [api, compact, version]); const loadMore = async () => { const last = items[items.length - 1]; if (!last) return; try { const result = await api.items({ unread: unreadOnly, severity: severity || undefined, source: source || undefined, limit: pageSize, before: last.id, }); setItems((current) => [...current, ...result.items]); setHasMore(result.items.length === pageSize); } catch (error) { toast.error(error instanceof Error ? error.message : String(error)); } }; const setRead = async (ids: number[] | "all", read: boolean) => { const { count } = await api.setRead(ids, read); store.setUnread(count); const now = new Date().toISOString(); setItems((current) => current .map((item) => ids === "all" || ids.includes(item.id) ? { ...item, readAt: read ? (item.readAt ?? now) : null } : item, ) .filter((item) => !unreadOnly || !item.readAt), ); }; const open = (item: AlertItem) => { if (!item.readAt) void setRead([item.id], true).catch(() => {}); if (item.link?.tab) tabs.openSingletonTab(item.link.tab); }; const remove = async (item: AlertItem) => { try { await api.remove(item.id); setItems((current) => current.filter((entry) => entry.id !== item.id)); store.setUnread((await api.unread()).count); } catch (error) { toast.error(error instanceof Error ? error.message : String(error)); } }; const clearRead = async () => { try { const { removed } = await api.clear(true); toast.success(t("inbox.cleared", { count: removed })); await load(); } catch (error) { toast.error(error instanceof Error ? error.message : String(error)); } }; const unread = items.some((item) => !item.readAt); return (
{[false, true].map((value) => ( ))}
{!compact && ( <> )} {!compact && ( )} {onOpenAll && ( )}
{loading ? (
) : items.length === 0 ? (

{unreadOnly ? t("inbox.emptyUnread") : t("inbox.empty")}

) : ( <> void setRead([item.id], !item.readAt).catch(() => {}) } onDelete={(item) => void remove(item)} /> {hasMore && (
)} )}
); }