import { useCallback, useEffect, useMemo, useState } from "react"; import type { OperationalTaskSummary } from "../api/operational-task-client"; import type { WorkbenchAlert } from "../types"; export type ScadaEventTask = Pick; export type ScadaAlertAtmosphereState = { id: string; projectId: string | null; alert: WorkbenchAlert | null; preview: boolean; count?: number; }; const storageKey = (project: string | null) => `tjwater.scada-visual-dismissed@1:${project}`; function readDismissed(project: string | null): string[] { try { const value: unknown = JSON.parse(sessionStorage.getItem(storageKey(project)) ?? "[]"); return Array.isArray(value) ? value.filter((id): id is string => typeof id === "string") : []; } catch { return []; } } /** Visual state follows event completion, independently of sensor recovery or run outcomes. */ export function useScadaEventAtmosphere(projectId: string | null, alerts: WorkbenchAlert[] | null, tasks: readonly ScadaEventTask[] | null) { const storedDismissed = useMemo(() => readDismissed(projectId), [projectId]); const [dismissed, setDismissed] = useState<{ project: string | null; ids: string[] } | null>(null); const muted = dismissed?.project === projectId ? dismissed.ids : storedDismissed; const [preview, setPreview] = useState(null); useEffect(() => setPreview(null), [projectId]); const candidates = new Map(); const currentTasks = tasks?.filter((task) => task.project_id === projectId) ?? []; const ended = new Set(currentTasks.filter((task) => ["completed", "cancelled"].includes(task.lifecycle_status)) .map((task) => `operational-task:${task.task_id}`)); for (const alert of alerts ?? []) { if (alert.source && "kind" in alert.source && alert.source.kind === "scada_replay" && alert.source.ruleId === "scada_diagnosis" && alert.conditionId && !ended.has(alert.conditionId)) { candidates.set(alert.conditionId, alert); } } for (const task of currentTasks) { const id = `operational-task:${task.task_id}`; if (task.task_type !== "event_investigation" || !task.source.replay_context || !["scada_anomaly", "burst_report"].includes(task.workflow) || ended.has(id) || candidates.has(id)) continue; candidates.set(id, { id, conditionId: id, title: task.title, description: "监测事件持续处理中", occurredAt: task.created_at, time: "" }); } const ids = [...candidates.keys()].filter((id) => !muted.includes(id)).sort(); const realKey = ids.join("|"); useEffect(() => { if (realKey) setPreview(null); }, [realKey]); const atmosphere: ScadaAlertAtmosphereState | null = ids.length ? { id: realKey, projectId, alert: candidates.get(ids[0])!, preview: false, count: ids.length } : preview?.projectId === projectId ? preview : null; const previewAtmosphere = useCallback(() => { if (realKey) return; setPreview({ id: `preview:${crypto.randomUUID()}`, projectId, alert: null, preview: true }); }, [projectId, realKey]); const endAtmospherePreview = useCallback(() => setPreview(null), []); const dismissAtmosphere = (id: string) => { if (id !== atmosphere?.id) return; setPreview(null); if (atmosphere.preview) return; const next = [...new Set([...muted, ...ids])]; setDismissed({ project: projectId, ids: next }); try { sessionStorage.setItem(storageKey(projectId), JSON.stringify(next)); } catch { /* Retain the current page preference. */ } }; return { atmosphere, previewAtmosphere, endAtmospherePreview, dismissAtmosphere }; }