11import { useEffect , useState , useRef , useCallback } from 'react' ;
2- import { createAlertSSE } from '@/api/sseClient ' ;
3- import { alertApi , type Alert } from '@/api/alertApi ' ;
2+ import type { Alert } from '@/api/alertApi ' ;
3+ import { useAlertStore } from '@/shared/store/useAlertStore ' ;
44
55export interface RackAlert {
66 rackId : number ;
77 level : 'CRITICAL' | 'WARNING' ;
88 latestAlert : Alert ;
99}
1010
11+ const ALERT_TTL_MS = 5 * 60 * 1000 ;
12+
1113/**
1214 * 서버실 알림을 실시간으로 수신하고 랙별 알림 상태를 관리하는 Hook
1315 * @param serverRoomId 서버실 ID (없으면 전체 알림 수신)
1416 */
1517export const useServerRoomAlerts = ( serverRoomId ?: number ) => {
1618 const [ rackAlerts , setRackAlerts ] = useState < Map < number , RackAlert > > ( new Map ( ) ) ;
17- const sseConnectionRef = useRef < ReturnType < typeof createAlertSSE > | null > ( null ) ;
18- const clearTimersRef = useRef < number [ ] > ( [ ] ) ;
19+ const clearTimersRef = useRef < Map < number , number > > ( new Map ( ) ) ;
20+ const processedAlertsRef = useRef < Set < number > > ( new Set ( ) ) ;
21+ const alerts = useAlertStore ( ( state ) => state . alerts ) ;
1922
2023 const shouldProcessAlert = useCallback (
2124 ( alert : Alert ) => {
@@ -44,6 +47,9 @@ export const useServerRoomAlerts = (serverRoomId?: number) => {
4447 } , [ ] ) ;
4548
4649 const scheduleAlertCleanup = useCallback ( ( alert : Alert ) => {
50+ const elapsed = Date . now ( ) - new Date ( alert . triggeredAt ) . getTime ( ) ;
51+ const remaining = Math . max ( 0 , ALERT_TTL_MS - elapsed ) ;
52+
4753 const timeoutId = window . setTimeout ( ( ) => {
4854 setRackAlerts ( ( prev ) => {
4955 const newMap = new Map ( prev ) ;
@@ -55,9 +61,15 @@ export const useServerRoomAlerts = (serverRoomId?: number) => {
5561 }
5662 return newMap ;
5763 } ) ;
58- } , 300000 ) ;
64+ clearTimersRef . current . delete ( alert . alertId ) ;
65+ processedAlertsRef . current . delete ( alert . alertId ) ;
66+ } , remaining ) ;
5967
60- clearTimersRef . current . push ( timeoutId ) ;
68+ const previousTimer = clearTimersRef . current . get ( alert . alertId ) ;
69+ if ( previousTimer ) {
70+ clearTimeout ( previousTimer ) ;
71+ }
72+ clearTimersRef . current . set ( alert . alertId , timeoutId ) ;
6173 } , [ ] ) ;
6274
6375 const processAlert = useCallback (
@@ -75,59 +87,35 @@ export const useServerRoomAlerts = (serverRoomId?: number) => {
7587 ) ;
7688
7789 useEffect ( ( ) => {
78- let isMounted = true ;
79-
80- const fetchInitialAlerts = async ( ) => {
81- try {
82- const response = await alertApi . getAlerts ( { page : 0 , size : 200 , days : 1 } ) ;
83- if ( ! isMounted ) return ;
84-
85- const relevantAlerts = response . content . filter ( shouldProcessAlert ) ;
86-
87- setRackAlerts ( ( prev ) => {
88- let newMap = new Map ( prev ) ;
89- relevantAlerts . forEach ( ( alert ) => {
90- newMap = upsertAlert ( newMap , alert ) ;
91- scheduleAlertCleanup ( alert ) ;
92- } ) ;
93- return newMap ;
94- } ) ;
95- } catch ( error ) {
96- console . error ( 'Failed to fetch initial alerts:' , error ) ;
97- }
98- } ;
99-
100- fetchInitialAlerts ( ) ;
90+ clearTimersRef . current . forEach ( ( timeoutId ) => {
91+ clearTimeout ( timeoutId ) ;
92+ } ) ;
93+ clearTimersRef . current . clear ( ) ;
94+ processedAlertsRef . current . clear ( ) ;
95+ setRackAlerts ( new Map ( ) ) ;
96+ } , [ serverRoomId ] ) ;
10197
102- // SSE 연결 생성
103- const connection = createAlertSSE < Alert > ( {
104- onMessage : ( alert ) => {
98+ useEffect ( ( ) => {
99+ alerts . forEach ( ( alert ) => {
100+ if ( ! processedAlertsRef . current . has ( alert . alertId ) && shouldProcessAlert ( alert ) ) {
101+ processedAlertsRef . current . add ( alert . alertId ) ;
105102 processAlert ( alert ) ;
106- } ,
107- onError : ( error ) => {
108- console . error ( 'Alert SSE error in useServerRoomAlerts:' , error ) ;
109- } ,
110- onOpen : ( ) => {
111- console . log ( 'Alert SSE connection established in useServerRoomAlerts' ) ;
112- } ,
103+ }
113104 } ) ;
105+ } , [ alerts , processAlert , serverRoomId , shouldProcessAlert ] ) ;
114106
115- sseConnectionRef . current = connection ;
107+ useEffect ( ( ) => {
108+ const timers = clearTimersRef . current ;
109+ const processed = processedAlertsRef . current ;
116110
117- // 클린업
118111 return ( ) => {
119- isMounted = false ;
120- if ( sseConnectionRef . current ) {
121- sseConnectionRef . current . close ( ) ;
122- sseConnectionRef . current = null ;
123- }
124-
125- clearTimersRef . current . forEach ( ( timeoutId ) => {
112+ timers . forEach ( ( timeoutId ) => {
126113 clearTimeout ( timeoutId ) ;
127114 } ) ;
128- clearTimersRef . current = [ ] ;
115+ timers . clear ( ) ;
116+ processed . clear ( ) ;
129117 } ;
130- } , [ processAlert , scheduleAlertCleanup , shouldProcessAlert , upsertAlert ] ) ;
118+ } , [ ] ) ;
131119
132120 return { rackAlerts } ;
133121} ;
0 commit comments