알림목록 종목별 정렬시 시간별로 정렬되도록 수정

This commit is contained in:
Macbook
2026-06-03 11:33:18 +09:00
parent 5ced979212
commit c228f18c25
5 changed files with 92 additions and 27 deletions
@@ -1,5 +1,6 @@
import React from 'react'; import React, { useMemo } from 'react';
import type { PaperTradeDto } from '../../lib/shared'; import type { PaperTradeDto } from '../../lib/shared';
import { compareTimeAsc } from '@frontend/utils/tradeListSort';
import MobileStackHeader from '../../components/MobileStackHeader'; import MobileStackHeader from '../../components/MobileStackHeader';
interface Props { interface Props {
@@ -9,7 +10,13 @@ interface Props {
} }
export default function VirtualHistoryScreen({ market, trades, onBack }: Props) { export default function VirtualHistoryScreen({ market, trades, onBack }: Props) {
const filtered = trades.filter(t => t.symbol === market).slice(0, 50); const filtered = useMemo(
() => trades
.filter(t => t.symbol === market)
.sort((a, b) => compareTimeAsc(a.createdAt, b.createdAt))
.slice(0, 50),
[trades, market],
);
return ( return (
<div className="screen stack-screen"> <div className="screen stack-screen">
@@ -45,6 +45,7 @@ import {
getTradeNotificationGridPreset, getTradeNotificationGridPreset,
type TradeNotificationGridPresetId, type TradeNotificationGridPresetId,
} from '../utils/tradeNotificationGridPresets'; } from '../utils/tradeNotificationGridPresets';
import { sortTradeNotifications } from '../utils/tradeListSort';
type RightTab = 'trade' | 'orderbook'; type RightTab = 'trade' | 'orderbook';
@@ -88,24 +89,6 @@ function fillBothSides(
setFillSell({ market, price, side: 'sell', seq: seq + 1 }); setFillSell({ market, price, side: 'sell', seq: seq + 1 });
} }
function sortNotifications(
items: TradeNotificationItem[],
sort: TradeNotificationListSort,
): TradeNotificationItem[] {
const arr = [...items];
if (sort === 'market') {
return arr.sort((a, b) => {
const byMarket = a.market.localeCompare(b.market, 'ko');
if (byMarket !== 0) return byMarket;
return b.receivedAt - a.receivedAt;
});
}
if (sort === 'oldest') {
return arr.sort((a, b) => a.receivedAt - b.receivedAt);
}
return arr.sort((a, b) => b.receivedAt - a.receivedAt);
}
export const TradeNotificationListPage: React.FC<Props> = ({ export const TradeNotificationListPage: React.FC<Props> = ({
theme, theme,
onGoToChart, onGoToChart,
@@ -273,7 +256,7 @@ export const TradeNotificationListPage: React.FC<Props> = ({
}, [allNotifications, filter, candleFilter]); }, [allNotifications, filter, candleFilter]);
const sorted = useMemo( const sorted = useMemo(
() => sortNotifications(filtered, listSort), () => sortTradeNotifications(filtered, listSort),
[filtered, listSort], [filtered, listSort],
); );
@@ -13,6 +13,7 @@ import {
toUpbitMarket, toUpbitMarket,
} from '../../utils/backtestUiUtils'; } from '../../utils/backtestUiUtils';
import { getKoreanName } from '../../utils/marketNameCache'; import { getKoreanName } from '../../utils/marketNameCache';
import { compareMarketSymbol, compareTimeAsc } from '../../utils/tradeListSort';
export type ExecutionListTab = 'backtest' | 'live'; export type ExecutionListTab = 'backtest' | 'live';
export type BacktestListSort = 'strategy' | 'symbol' | 'return'; export type BacktestListSort = 'strategy' | 'symbol' | 'return';
@@ -58,7 +59,13 @@ export default function BacktestExecutionList({
const sortedBacktests = useMemo(() => { const sortedBacktests = useMemo(() => {
const list = [...backtestRecords]; const list = [...backtestRecords];
if (sort === 'strategy') list.sort((a, b) => (a.strategyName ?? '').localeCompare(b.strategyName ?? '')); if (sort === 'strategy') list.sort((a, b) => (a.strategyName ?? '').localeCompare(b.strategyName ?? ''));
else if (sort === 'symbol') list.sort((a, b) => a.symbol.localeCompare(b.symbol)); else if (sort === 'symbol') {
list.sort((a, b) => {
const bySymbol = compareMarketSymbol(a.symbol, b.symbol);
if (bySymbol !== 0) return bySymbol;
return compareTimeAsc(a.createdAt, b.createdAt);
});
}
else list.sort((a, b) => (b.totalReturn ?? 0) - (a.totalReturn ?? 0)); else list.sort((a, b) => (b.totalReturn ?? 0) - (a.totalReturn ?? 0));
return list; return list;
}, [backtestRecords, sort]); }, [backtestRecords, sort]);
@@ -117,7 +124,13 @@ export default function BacktestExecutionList({
<p className="btd-sidebar-hint"> ·<br /> .</p> <p className="btd-sidebar-hint"> ·<br /> .</p>
</div> </div>
) : ( ) : (
liveItems.map(item => { [...liveItems]
.sort((a, b) => {
const bySymbol = compareMarketSymbol(a.symbol, b.symbol);
if (bySymbol !== 0) return bySymbol;
return compareTimeAsc(a.createdAt, b.createdAt);
})
.map(item => {
const positive = item.totalReturnPct >= 0; const positive = item.totalReturnPct >= 0;
const active = selectedLiveId === item.id; const active = selectedLiveId === item.id;
const market = toUpbitMarket(item.symbol); const market = toUpbitMarket(item.symbol);
@@ -1,8 +1,9 @@
import React from 'react'; import React, { useMemo } from 'react';
import type { PaperTradeDto } from '../../utils/backendApi'; import type { PaperTradeDto } from '../../utils/backendApi';
import type { TickerData } from '../../hooks/useMarketTicker'; import type { TickerData } from '../../hooks/useMarketTicker';
import { resolveVirtualTargetNames } from '../../utils/virtualTargetNames'; import { resolveVirtualTargetNames } from '../../utils/virtualTargetNames';
import { fmtKrw } from '../TradeOrderPanel'; import { fmtKrw } from '../TradeOrderPanel';
import { sortPaperTradesForDisplay } from '../../utils/tradeListSort';
function coinCode(symbol: string): string { function coinCode(symbol: string): string {
return symbol.replace(/^KRW-/, ''); return symbol.replace(/^KRW-/, '');
@@ -43,7 +44,10 @@ const PaperTradeHistoryList: React.FC<Props> = ({
selectedTradeId = null, selectedTradeId = null,
onSelectTrade, onSelectTrade,
onSelectMarket, onSelectMarket,
}) => ( }) => {
const displayTrades = useMemo(() => sortPaperTradesForDisplay(trades), [trades]);
return (
<div className={`ptd-trade-history vtd-trade-history${className ? ` ${className}` : ''}`}> <div className={`ptd-trade-history vtd-trade-history${className ? ` ${className}` : ''}`}>
<section className="vtd-target-section vtd-trade-section"> <section className="vtd-target-section vtd-trade-section">
<div className="vtd-target-list-head"> <div className="vtd-target-list-head">
@@ -56,7 +60,7 @@ const PaperTradeHistoryList: React.FC<Props> = ({
) : ( ) : (
<div className="ptd-trade-history-scroll"> <div className="ptd-trade-history-scroll">
<div className="vtd-target-list vtd-trade-list"> <div className="vtd-target-list vtd-trade-list">
{trades.map(t => { {displayTrades.map(t => {
const { koreanName: ko, englishName: en } = resolveVirtualTargetNames( const { koreanName: ko, englishName: en } = resolveVirtualTargetNames(
t.symbol, t.symbol,
tickers?.get(t.symbol)?.koreanName, tickers?.get(t.symbol)?.koreanName,
@@ -164,6 +168,7 @@ const PaperTradeHistoryList: React.FC<Props> = ({
)} )}
</section> </section>
</div> </div>
); );
};
export default PaperTradeHistoryList; export default PaperTradeHistoryList;
+57
View File
@@ -0,0 +1,57 @@
import type { PaperTradeDto } from './backendApi';
import type { TradeNotificationItem } from '../contexts/TradeNotificationContext';
import type { TradeNotificationListSort } from './tradeNotificationListLayout';
/** 종목(마켓) 코드 비교 — 한글 정렬 */
export function compareMarketSymbol(a: string, b: string): number {
return a.localeCompare(b, 'ko');
}
/** epoch ms·ISO 문자열·초 단위 타임스탬프 → 오름차순(이전 → 최근) */
export function compareTimeAsc(
a: number | string | null | undefined,
b: number | string | null | undefined,
): number {
return toTimeMs(a) - toTimeMs(b);
}
function toTimeMs(t: number | string | null | undefined): number {
if (t == null) return 0;
if (typeof t === 'number') {
if (!Number.isFinite(t)) return 0;
return t < 1e12 ? t * 1000 : t;
}
const d = new Date(t);
return Number.isNaN(d.getTime()) ? 0 : d.getTime();
}
/** 종목순 → 동일 종목은 체결·수신 시각 오름차순 (매수·매도 흐름) */
export function sortByMarketThenTimeAsc<T>(
items: readonly T[],
getMarket: (item: T) => string,
getTime: (item: T) => number | string | null | undefined,
): T[] {
return [...items].sort((a, b) => {
const byMarket = compareMarketSymbol(getMarket(a), getMarket(b));
if (byMarket !== 0) return byMarket;
return compareTimeAsc(getTime(a), getTime(b));
});
}
export function sortPaperTradesForDisplay(trades: readonly PaperTradeDto[]): PaperTradeDto[] {
return sortByMarketThenTimeAsc(trades, t => t.symbol, t => t.createdAt);
}
export function sortTradeNotifications(
items: TradeNotificationItem[],
sort: TradeNotificationListSort,
): TradeNotificationItem[] {
const arr = [...items];
if (sort === 'market') {
return sortByMarketThenTimeAsc(arr, n => n.market, n => n.receivedAt);
}
if (sort === 'oldest') {
return arr.sort((a, b) => compareTimeAsc(a.receivedAt, b.receivedAt));
}
return arr.sort((a, b) => compareTimeAsc(b.receivedAt, a.receivedAt));
}