알림닫기 오류 수정

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