mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
usePerformanceAlerts now fetches /api/supervisor/performance-alerts every 60s instead of computing client-side. Dismiss + dismiss-all hit the sidecar so state survives reload. Toast fires when new alerts arrive. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
import { useAuth } from '@/providers/auth-provider';
|
|
import { notify } from '@/lib/toast';
|
|
|
|
export type PerformanceAlert = {
|
|
id: string;
|
|
agent: string;
|
|
agentId: string | null;
|
|
type: string;
|
|
value: string;
|
|
severity: 'error' | 'warning' | 'info';
|
|
message?: string | null;
|
|
firedAt?: string;
|
|
dismissed: boolean;
|
|
};
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
|
|
const POLL_INTERVAL_MS = 60_000;
|
|
|
|
const sevToFront = (s: string): 'error' | 'warning' | 'info' => {
|
|
const v = (s ?? '').toLowerCase();
|
|
if (v === 'critical') return 'error';
|
|
if (v === 'warning') return 'warning';
|
|
return 'info';
|
|
};
|
|
|
|
export const usePerformanceAlerts = () => {
|
|
const { isAdmin } = useAuth();
|
|
const [alerts, setAlerts] = useState<PerformanceAlert[]>([]);
|
|
const lastSeenIdsRef = useRef<Set<string>>(new Set());
|
|
|
|
const load = useCallback(async () => {
|
|
if (!isAdmin) return;
|
|
const token = localStorage.getItem('helix_access_token') ?? '';
|
|
try {
|
|
const res = await fetch(`${API_URL}/api/supervisor/performance-alerts`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) return;
|
|
const json = await res.json();
|
|
const list: PerformanceAlert[] = (json?.alerts ?? []).map((a: any) => ({
|
|
id: a.id,
|
|
agent: a.agent,
|
|
agentId: a.agentId ?? null,
|
|
type: a.type,
|
|
value: a.value ?? '',
|
|
severity: sevToFront(a.severity),
|
|
message: a.message,
|
|
firedAt: a.firedAt,
|
|
dismissed: false,
|
|
}));
|
|
setAlerts(list);
|
|
|
|
// Toast for newly arrived alerts
|
|
const fresh = list.filter((a) => !lastSeenIdsRef.current.has(a.id));
|
|
if (fresh.length > 0 && lastSeenIdsRef.current.size > 0) {
|
|
notify.error('Performance Alerts', `${fresh.length} new alert(s)`);
|
|
}
|
|
lastSeenIdsRef.current = new Set(list.map((a) => a.id));
|
|
} catch {
|
|
// Silent — sidecar may be temporarily down
|
|
}
|
|
}, [isAdmin]);
|
|
|
|
useEffect(() => {
|
|
if (!isAdmin) return;
|
|
load();
|
|
const id = setInterval(load, POLL_INTERVAL_MS);
|
|
return () => clearInterval(id);
|
|
}, [isAdmin, load]);
|
|
|
|
const dismiss = useCallback(async (id: string) => {
|
|
// Optimistic
|
|
setAlerts((prev) => prev.filter((a) => a.id !== id));
|
|
const token = localStorage.getItem('helix_access_token') ?? '';
|
|
try {
|
|
await fetch(`${API_URL}/api/supervisor/performance-alerts/${id}/dismiss`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
} catch {
|
|
// Reload on failure to restore truth
|
|
load();
|
|
}
|
|
}, [load]);
|
|
|
|
const dismissAll = useCallback(async () => {
|
|
setAlerts([]);
|
|
const token = localStorage.getItem('helix_access_token') ?? '';
|
|
try {
|
|
await fetch(`${API_URL}/api/supervisor/performance-alerts/dismiss-all`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
} catch {
|
|
load();
|
|
}
|
|
}, [load]);
|
|
|
|
return { alerts, allAlerts: alerts, dismiss, dismissAll };
|
|
};
|