전략알림 수정
This commit is contained in:
@@ -122,6 +122,8 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
}) => {
|
||||
const alertSoundId = normalizeTradeAlertSoundId(soundId) as TradeAlertSoundId;
|
||||
const [readIds, setReadIds] = useState<Set<string>>(() => loadReadIds());
|
||||
const readIdsRef = useRef(readIds);
|
||||
readIdsRef.current = readIds;
|
||||
const [toastNotifications, setToastNotifications] = useState<TradeNotificationItem[]>([]);
|
||||
const toastNotificationsRef = useRef(toastNotifications);
|
||||
toastNotificationsRef.current = toastNotifications;
|
||||
@@ -137,38 +139,45 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
const refreshHistory = useCallback(async () => {
|
||||
try {
|
||||
const dtos = await loadTradeSignals();
|
||||
const items = dtos.map(d => dtoToItem(d, readIds));
|
||||
/** dismiss 직후 비동기 완료 시 state readIds가 늦게 반영되는 것 방지 */
|
||||
const read = loadReadIds();
|
||||
const items = dtos.map(d => dtoToItem(d, read));
|
||||
|
||||
let newlyArrived: TradeNotificationItem[] = [];
|
||||
|
||||
setAllNotifications(prev => {
|
||||
const isBoot = prev.length === 0;
|
||||
const newlyArrived = items.filter(
|
||||
newlyArrived = items.filter(
|
||||
item => !item.isRead && !prev.some(p => p.id === item.id),
|
||||
);
|
||||
|
||||
if (!isBoot && popupEnabled && newlyArrived.length > 0) {
|
||||
setToastNotifications(tprev => {
|
||||
const map = new Map(tprev.map(t => [t.id, t]));
|
||||
for (const n of newlyArrived) map.set(n.id, n);
|
||||
return [...map.values()].slice(0, MAX_TOAST_QUEUE);
|
||||
});
|
||||
if (soundEnabled) {
|
||||
playTradeAlertSound(alertSoundId);
|
||||
}
|
||||
}
|
||||
|
||||
const map = new Map<string, TradeNotificationItem>();
|
||||
for (const n of items) map.set(n.id, n);
|
||||
for (const n of items) {
|
||||
const wasRead = read.has(n.id) || prev.find(p => p.id === n.id)?.isRead;
|
||||
map.set(n.id, wasRead ? { ...n, isRead: true } : n);
|
||||
}
|
||||
for (const n of prev) {
|
||||
if (!map.has(n.id)) map.set(n.id, n);
|
||||
else if (n.isRead) map.set(n.id, { ...map.get(n.id)!, isRead: true });
|
||||
}
|
||||
return [...map.values()]
|
||||
.sort((a, b) => b.receivedAt - a.receivedAt)
|
||||
.slice(0, MAX_HISTORY);
|
||||
});
|
||||
|
||||
if (popupEnabled && newlyArrived.length > 0) {
|
||||
setToastNotifications(tprev => {
|
||||
const map = new Map(tprev.map(t => [t.id, t]));
|
||||
for (const n of newlyArrived) map.set(n.id, n);
|
||||
return [...map.values()].slice(0, MAX_TOAST_QUEUE);
|
||||
});
|
||||
if (soundEnabled) {
|
||||
playTradeAlertSound(alertSoundId);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[TradeNotification] 이력 로드 실패:', e);
|
||||
}
|
||||
}, [readIds, popupEnabled, soundEnabled, alertSoundId]);
|
||||
}, [popupEnabled, soundEnabled, alertSoundId]);
|
||||
|
||||
useEffect(() => {
|
||||
void refreshHistory();
|
||||
@@ -179,6 +188,7 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
const next = new Set(prev);
|
||||
next.add(id);
|
||||
saveReadIds(next);
|
||||
readIdsRef.current = next;
|
||||
return next;
|
||||
});
|
||||
setToastNotifications(prev => prev.filter(n => n.id !== id));
|
||||
@@ -195,6 +205,7 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
saveReadIds(next);
|
||||
readIdsRef.current = next;
|
||||
return next;
|
||||
});
|
||||
const item: TradeNotificationItem = {
|
||||
@@ -238,16 +249,30 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
const dismissToasts = useCallback((ids: string[]) => {
|
||||
if (ids.length === 0) return;
|
||||
const idSet = new Set(ids);
|
||||
const toastSnapshot = toastNotificationsRef.current;
|
||||
setReadIds(r => {
|
||||
const next = new Set(r);
|
||||
ids.forEach(i => next.add(i));
|
||||
saveReadIds(next);
|
||||
readIdsRef.current = next;
|
||||
return next;
|
||||
});
|
||||
setToastNotifications(prev => prev.filter(n => !idSet.has(n.id)));
|
||||
setAllNotifications(all =>
|
||||
all.map(n => (idSet.has(n.id) ? { ...n, isRead: true } : n)),
|
||||
);
|
||||
setAllNotifications(all => {
|
||||
const byId = new Map(all.map(n => [n.id, n]));
|
||||
for (const id of ids) {
|
||||
const existing = byId.get(id);
|
||||
if (existing) {
|
||||
byId.set(id, { ...existing, isRead: true });
|
||||
} else {
|
||||
const fromToast = toastSnapshot.find(n => n.id === id);
|
||||
if (fromToast) byId.set(id, { ...fromToast, isRead: true });
|
||||
}
|
||||
}
|
||||
return [...byId.values()]
|
||||
.sort((a, b) => b.receivedAt - a.receivedAt)
|
||||
.slice(0, MAX_HISTORY);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const dismissAllToasts = useCallback(() => {
|
||||
@@ -259,24 +284,33 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
const next = new Set(r);
|
||||
idSet.forEach(i => next.add(i));
|
||||
saveReadIds(next);
|
||||
readIdsRef.current = next;
|
||||
return next;
|
||||
});
|
||||
setToastNotifications([]);
|
||||
setAllNotifications(all =>
|
||||
all.map(n => (idSet.has(n.id) ? { ...n, isRead: true } : n)),
|
||||
);
|
||||
setAllNotifications(all => {
|
||||
const byId = new Map(all.map(n => [n.id, n]));
|
||||
for (const n of snapshot) {
|
||||
const existing = byId.get(n.id);
|
||||
byId.set(n.id, existing ? { ...existing, isRead: true } : { ...n, isRead: true });
|
||||
}
|
||||
return [...byId.values()]
|
||||
.sort((a, b) => b.receivedAt - a.receivedAt)
|
||||
.slice(0, MAX_HISTORY);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const markAllAsRead = useCallback(() => {
|
||||
setAllNotifications(prev => {
|
||||
const nextRead = new Set(readIds);
|
||||
const nextRead = new Set(loadReadIds());
|
||||
prev.forEach(n => nextRead.add(n.id));
|
||||
saveReadIds(nextRead);
|
||||
readIdsRef.current = nextRead;
|
||||
setReadIds(nextRead);
|
||||
return prev.map(n => ({ ...n, isRead: true }));
|
||||
});
|
||||
setToastNotifications([]);
|
||||
}, [readIds]);
|
||||
}, []);
|
||||
|
||||
const openDetail = useCallback((item: TradeNotificationItem, options?: { centered?: boolean }) => {
|
||||
setDetailCentered(options?.centered ?? false);
|
||||
|
||||
Reference in New Issue
Block a user