알림닫기 오류 수정

This commit is contained in:
Macbook
2026-06-04 13:28:55 +09:00
parent 128b589369
commit c84bb43450
@@ -149,6 +149,8 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
const toastNotificationsRef = useRef(toastNotifications);
toastNotificationsRef.current = toastNotifications;
const [allNotifications, setAllNotifications] = useState<TradeNotificationItem[]>([]);
const allNotificationsRef = useRef(allNotifications);
allNotificationsRef.current = allNotifications;
const [detailSignal, setDetailSignal] = useState<TradeSignalInfo | null>(null);
const [detailCentered, setDetailCentered] = useState(false);
@@ -161,11 +163,14 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
try {
const dtos = await loadTradeSignals();
/**
* 항상 DB 캐시의 최신 readIds 사용.
* rawServerList=true 시 빈 Set을 쓰던 기존 코드는 새로고침 후 모든 알림이
* 미읽음으로 복원되는 버그를 유발했으므로 제거.
* readIds 의 최신 상태를 가져올 때:
* - readIdsRef.current : dismissAllToasts / markAllAsRead 직후 즉시 반영되는 인메모리 값
* - loadReadIds() : DB 캐시 기반 값 (약간의 지연 가능)
* 두 Set 의 합집합(union)을 사용하면 어느 쪽 타이밍 경쟁에서도 읽음 상태가 보존된다.
*/
const read = loadReadIds();
const cacheRead = loadReadIds();
const memRead = readIdsRef.current;
const read = new Set([...cacheRead, ...memRead]);
const hidden = opts?.rawServerList ? new Set<string>() : loadHiddenIds();
const items = dtos
.map(d => dtoToItem(d, read))
@@ -338,35 +343,39 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
const dismissAllToasts = useCallback(() => {
/**
* 전체 닫기 = 토스트 + allNotifications 의 모든 항목을 즉시 읽음 처리.
* patchUiPreferences immediate=true 로 디바운스 없이 즉시 DB에 반영해
* 이후 폴링·STOMP 재수신 시에도 readIds 가 항상 최신 상태를 유지한다.
* 전체 닫기 — 현재 표시된 모든 알림을 읽음 처리하고 즉시 DB 저장.
*
* 핵심 원칙:
* 1. 사이드 이펙트(readIdsRef 갱신, DB 저장)는 setState 업데이터 밖에서 실행.
* 2. allNotificationsRef 로 현재 목록을 동기적으로 읽어 nextRead 를 구성.
* 3. immediate=true 로 400ms 디바운스 없이 즉시 저장해 이후 폴링과의
* 경쟁 조건(readIds 가 아직 DB 에 없는 상태)을 제거.
*/
const nextRead = new Set(readIdsRef.current);
toastNotificationsRef.current.forEach(n => nextRead.add(n.id));
// allNotifications 는 state 업데이트 전이므로 ref가 아닌 최신 snapshot이 필요.
// setAllNotifications 콜백 안에서 nextRead를 채운 뒤 즉시 저장한다.
setAllNotifications(prev => {
prev.forEach(n => nextRead.add(n.id));
allNotificationsRef.current.forEach(n => nextRead.add(n.id));
// 1) 인메모리 ref 즉시 갱신 — addNotification / refreshHistory 가 즉시 참조
readIdsRef.current = nextRead;
// 2) React state 갱신 (렌더링 예약)
setReadIds(nextRead);
// immediate=true 로 400ms 디바운스 없이 즉시 저장
patchUiPreferences({ tradeNotifications: { readIds: [...nextRead] } }, true);
return prev.map(n => ({ ...n, isRead: true }));
});
setAllNotifications(prev => prev.map(n => ({ ...n, isRead: true })));
setToastNotifications([]);
// 3) 캐시 갱신 + 즉시 DB 저장 (immediate=true)
saveReadIds(nextRead, true);
}, []);
const markAllAsRead = useCallback(() => {
setAllNotifications(prev => {
const nextRead = new Set(loadReadIds());
prev.forEach(n => nextRead.add(n.id));
saveReadIds(nextRead);
const nextRead = new Set(readIdsRef.current);
allNotificationsRef.current.forEach(n => nextRead.add(n.id));
readIdsRef.current = nextRead;
setReadIds(nextRead);
return prev.map(n => ({ ...n, isRead: true }));
});
setAllNotifications(prev => prev.map(n => ({ ...n, isRead: true })));
setToastNotifications([]);
saveReadIds(nextRead, true);
}, []);
const purgeNotifications = useCallback((ids: string[]) => {