알림 전체닫기 오류 수정
This commit is contained in:
@@ -100,8 +100,8 @@ function loadReadIds(): Set<string> {
|
||||
return new Set(ids ?? []);
|
||||
}
|
||||
|
||||
function saveReadIds(ids: Set<string>) {
|
||||
patchUiPreferences({ tradeNotifications: { readIds: [...ids] } });
|
||||
function saveReadIds(ids: Set<string>, immediate = false) {
|
||||
patchUiPreferences({ tradeNotifications: { readIds: [...ids] } }, immediate);
|
||||
}
|
||||
|
||||
function loadHiddenIds(): Set<string> {
|
||||
@@ -181,7 +181,10 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
*/
|
||||
if (!opts?.rawServerList) {
|
||||
newlyArrived = items.filter(
|
||||
item => !item.isRead && !prev.some(p => p.id === item.id),
|
||||
item =>
|
||||
!item.isRead &&
|
||||
!readIdsRef.current.has(item.id) && // readIds 에 없는(진짜 새) 알림만
|
||||
!prev.some(p => p.id === item.id), // 이미 목록에 없는 알림만
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,43 +248,58 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
|
||||
const addNotification = useCallback((signal: TradeSignalInfo & { dbId?: number }) => {
|
||||
const id = makeId(signal);
|
||||
/**
|
||||
* 이전에 읽음 처리(닫기)한 동일 키를 readIds 에서 제거하던 로직을 삭제.
|
||||
* STOMP 재연결 시 동일 신호가 재수신되어도 사용자가 명시적으로 닫은 알림은
|
||||
* 다시 열리지 않아야 한다. 새로운 candleTime 을 가진 신호는 키가 달라
|
||||
* readIds 에 없으므로 정상적으로 미읽음 처리된다.
|
||||
*/
|
||||
const item: TradeNotificationItem = {
|
||||
...signal,
|
||||
id,
|
||||
dbId: signal.dbId,
|
||||
candleType: signal.candleType ?? '1m',
|
||||
isRead: false,
|
||||
receivedAt: Date.now(),
|
||||
};
|
||||
|
||||
// 배지(미확인 건수)는 팝업 설정과 무관하게 항상 갱신
|
||||
/**
|
||||
* 이미 읽음(닫기) 처리된 알림 ID 이면:
|
||||
* - allNotifications 에 이미 존재하면 isRead 상태를 건드리지 않는다.
|
||||
* - 새로운 항목이면 isRead: true 로 조용히 이력에만 추가한다.
|
||||
* - 토스트 팝업·알림음은 표시하지 않는다.
|
||||
* STOMP 재연결 시 동일 신호가 재수신되거나 20 초 폴링에서 새로 발견되어도
|
||||
* 사용자가 명시적으로 닫은 알림은 다시 열리지 않는다.
|
||||
*/
|
||||
const alreadyRead = readIdsRef.current.has(id);
|
||||
|
||||
setAllNotifications(prev => {
|
||||
const existing = prev.find(n => n.id === id);
|
||||
if (existing) {
|
||||
// 읽음 처리된 항목은 isRead 를 false 로 되돌리지 않는다
|
||||
if (existing.isRead || alreadyRead) return prev;
|
||||
// 미읽음 항목은 receivedAt 만 갱신
|
||||
return prev.map(n =>
|
||||
n.id === id
|
||||
? { ...n, ...item, isRead: false, receivedAt: item.receivedAt }
|
||||
: n,
|
||||
n.id === id ? { ...n, isRead: false, receivedAt: Date.now() } : n,
|
||||
);
|
||||
}
|
||||
const item: TradeNotificationItem = {
|
||||
...signal,
|
||||
id,
|
||||
dbId: signal.dbId,
|
||||
candleType: signal.candleType ?? '1m',
|
||||
isRead: alreadyRead,
|
||||
receivedAt: Date.now(),
|
||||
};
|
||||
return [item, ...prev].slice(0, MAX_HISTORY);
|
||||
});
|
||||
|
||||
// 이미 읽음 처리된 알림은 팝업·알림음 모두 생략
|
||||
if (alreadyRead) return;
|
||||
|
||||
if (soundEnabled) {
|
||||
playTradeAlertSound(alertSoundId);
|
||||
}
|
||||
|
||||
if (!popupEnabled) return;
|
||||
|
||||
const toastItem: TradeNotificationItem = {
|
||||
...signal,
|
||||
id,
|
||||
dbId: signal.dbId,
|
||||
candleType: signal.candleType ?? '1m',
|
||||
isRead: false,
|
||||
receivedAt: Date.now(),
|
||||
};
|
||||
setToastNotifications(prev => {
|
||||
const rest = prev.filter(n => n.id !== id);
|
||||
return [item, ...rest].slice(0, MAX_TOAST_QUEUE);
|
||||
return [toastItem, ...rest].slice(0, MAX_TOAST_QUEUE);
|
||||
});
|
||||
}, [popupEnabled, soundEnabled, alertSoundId]);
|
||||
|
||||
@@ -320,19 +338,20 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
|
||||
const dismissAllToasts = useCallback(() => {
|
||||
/**
|
||||
* 전체 닫기 = 현재 토스트 + allNotifications 의 모든 미읽음 항목을 읽음 처리.
|
||||
* 기존에는 toastNotifications 에 있는 항목만 처리해서, 토스트에 없는 미읽음 항목은
|
||||
* 다음 refreshHistory 폴링 시 다시 팝업으로 나타나는 문제가 있었다.
|
||||
* 전체 닫기 = 토스트 + allNotifications 의 모든 항목을 즉시 읽음 처리.
|
||||
* patchUiPreferences immediate=true 로 디바운스 없이 즉시 DB에 반영해
|
||||
* 이후 폴링·STOMP 재수신 시에도 readIds 가 항상 최신 상태를 유지한다.
|
||||
*/
|
||||
const nextRead = new Set(readIdsRef.current);
|
||||
toastNotificationsRef.current.forEach(n => nextRead.add(n.id));
|
||||
// allNotifications 는 state 업데이트 전이므로 ref가 아닌 최신 snapshot이 필요.
|
||||
// setAllNotifications 콜백 안에서 nextRead를 채운 뒤 즉시 저장한다.
|
||||
setAllNotifications(prev => {
|
||||
const nextRead = new Set(readIdsRef.current);
|
||||
// 토스트 항목
|
||||
toastNotificationsRef.current.forEach(n => nextRead.add(n.id));
|
||||
// allNotifications 의 미읽음 항목 전체
|
||||
prev.forEach(n => nextRead.add(n.id));
|
||||
saveReadIds(nextRead);
|
||||
readIdsRef.current = nextRead;
|
||||
setReadIds(nextRead);
|
||||
// immediate=true 로 400ms 디바운스 없이 즉시 저장
|
||||
patchUiPreferences({ tradeNotifications: { readIds: [...nextRead] } }, true);
|
||||
return prev.map(n => ({ ...n, isRead: true }));
|
||||
});
|
||||
setToastNotifications([]);
|
||||
|
||||
Reference in New Issue
Block a user