매매 시그널 알림 수정

This commit is contained in:
Macbook
2026-05-30 16:46:59 +09:00
parent d5699a827e
commit ba4ef5e230
5 changed files with 108 additions and 79 deletions
+8 -10
View File
@@ -194,24 +194,23 @@ html.theme-blue {
.tmb-divider { .tmb-divider {
width: 1px; width: 1px;
height: 20px; height: 20px;
background: var(--border); background: color-mix(in srgb, var(--border) 90%, var(--text3));
margin: 0 8px; margin: 0 6px;
flex-shrink: 0; flex-shrink: 0;
align-self: center;
} }
.tmb-sep { .tmb-divider--nav {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 4px; margin: 0 4px;
flex-shrink: 0; height: 18px;
} }
/* 네비게이션 */ /* 네비게이션 */
.tmb-nav { .tmb-nav {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 0;
flex: 1; flex: 1;
min-width: 0;
} }
.tmb-nav-btn { .tmb-nav-btn {
display: inline-flex; display: inline-flex;
@@ -252,8 +251,7 @@ html.theme-blue {
.tmb-right { .tmb-right {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 0;
margin-left: auto;
flex-shrink: 0; flex-shrink: 0;
} }
+17 -3
View File
@@ -207,21 +207,27 @@ export const TopMenuBar = memo(function TopMenuBar({
{/* 내비게이션 메뉴 */} {/* 내비게이션 메뉴 */}
<nav className="tmb-nav"> <nav className="tmb-nav">
{visibleItems.map(({ page, label, icon }) => ( {visibleItems.map(({ page, label, icon }, index) => (
<React.Fragment key={page}>
{index > 0 && <span className="tmb-divider tmb-divider--nav" aria-hidden="true" />}
<button <button
key={page} type="button"
className={`tmb-nav-btn ${activePage === page ? 'tmb-nav-btn--active' : ''}`} className={`tmb-nav-btn ${activePage === page ? 'tmb-nav-btn--active' : ''}`}
onClick={() => onPage(page)} onClick={() => onPage(page)}
> >
<span className="tmb-nav-icon">{icon}</span> <span className="tmb-nav-icon">{icon}</span>
<span className="tmb-nav-label">{label}</span> <span className="tmb-nav-label">{label}</span>
</button> </button>
</React.Fragment>
))} ))}
</nav> </nav>
<span className="tmb-divider" aria-hidden="true" />
{/* 우측 영역 */} {/* 우측 영역 */}
<div className="tmb-right"> <div className="tmb-right">
{onOpenAppDownload && ( {onOpenAppDownload && (
<>
<button <button
type="button" type="button"
className="tmb-app-download-btn" className="tmb-app-download-btn"
@@ -231,8 +237,13 @@ export const TopMenuBar = memo(function TopMenuBar({
> >
<IcAppDownload /> <IcAppDownload />
</button> </button>
{onOpenNotifications && showNotifications && (
<span className="tmb-divider" aria-hidden="true" />
)}
</>
)} )}
{onOpenNotifications && showNotifications && ( {onOpenNotifications && showNotifications && (
<>
<div className="tmb-notify-group"> <div className="tmb-notify-group">
<button <button
type="button" type="button"
@@ -273,10 +284,13 @@ export const TopMenuBar = memo(function TopMenuBar({
</button> </button>
)} )}
</div> </div>
<span className="tmb-divider" aria-hidden="true" />
</>
)} )}
{/* 테마 토글 버튼 */} {/* 테마 토글 버튼 */}
<button <button
type="button"
className={`tmb-theme-btn tmb-theme-btn--${theme}`} className={`tmb-theme-btn tmb-theme-btn--${theme}`}
onClick={onTheme} onClick={onTheme}
title={`테마 전환 (현재: ${tc.label}) → ${THEME_CONFIG[tc.next].label}`} title={`테마 전환 (현재: ${tc.label}) → ${THEME_CONFIG[tc.next].label}`}
@@ -285,7 +299,7 @@ export const TopMenuBar = memo(function TopMenuBar({
<span className="tmb-theme-label">{tc.label}</span> <span className="tmb-theme-label">{tc.label}</span>
</button> </button>
<div className="tmb-sep" /> <span className="tmb-divider" aria-hidden="true" />
{authUser ? ( {authUser ? (
<> <>
@@ -30,9 +30,12 @@ import {
saveTradeNotificationListLayout, saveTradeNotificationListLayout,
saveTradeNotificationListSort, saveTradeNotificationListSort,
TRADE_NOTIFICATION_CANDLE_FILTER_OPTIONS, TRADE_NOTIFICATION_CANDLE_FILTER_OPTIONS,
TRADE_NOTIFICATION_READ_FILTER_OPTIONS,
TRADE_NOTIFICATION_SORT_OPTIONS,
type TradeNotificationCandleFilter, type TradeNotificationCandleFilter,
type TradeNotificationListLayout, type TradeNotificationListLayout,
type TradeNotificationListSort, type TradeNotificationListSort,
type TradeNotificationReadFilter,
} from '../utils/tradeNotificationListLayout'; } from '../utils/tradeNotificationListLayout';
import { normalizeStartCandleType } from '../utils/strategyStartNodes'; import { normalizeStartCandleType } from '../utils/strategyStartNodes';
import { import {
@@ -121,7 +124,7 @@ export const TradeNotificationListPage: React.FC<Props> = ({
refreshHistory, refreshHistory,
} = useTradeNotification(); } = useTradeNotification();
const [filter, setFilter] = useState<'all' | 'unread'>('all'); const [filter, setFilter] = useState<TradeNotificationReadFilter>('all');
const [listSort, setListSort] = useState<TradeNotificationListSort>( const [listSort, setListSort] = useState<TradeNotificationListSort>(
() => loadTradeNotificationListSort(), () => loadTradeNotificationListSort(),
); );
@@ -337,52 +340,31 @@ export const TradeNotificationListPage: React.FC<Props> = ({
{selectedCount > 0 && ( {selectedCount > 0 && (
<span className="tnl-chip tnl-chip--selected"> {selectedCount}</span> <span className="tnl-chip tnl-chip--selected"> {selectedCount}</span>
)} )}
<div className="tnl-filter"> <div className="tnl-toolbar-selects">
<button
type="button"
className={filter === 'all' ? 'tnl-filter-btn--active' : ''}
onClick={() => setFilter('all')}
>
</button>
<button
type="button"
className={filter === 'unread' ? 'tnl-filter-btn--active' : ''}
onClick={() => setFilter('unread')}
>
</button>
</div>
{unreadCount > 0 && (
<button type="button" className="tnl-btn tnl-btn--primary" onClick={markAllAsRead}>
</button>
)}
<div className="tnl-filter tnl-sort-toggle" role="group" aria-label="목록 정렬">
<button
type="button"
className={listSort === 'market' ? 'tnl-filter-btn--active' : ''}
onClick={() => handleListSortChange('market')}
>
</button>
<button
type="button"
className={listSort === 'newest' ? 'tnl-filter-btn--active' : ''}
onClick={() => handleListSortChange('newest')}
>
</button>
<button
type="button"
className={listSort === 'oldest' ? 'tnl-filter-btn--active' : ''}
onClick={() => handleListSortChange('oldest')}
>
</button>
</div>
<select <select
className="tnl-candle-filter-select" className="tnl-toolbar-select"
value={filter}
aria-label="표시 범위"
onChange={e => setFilter(e.target.value as TradeNotificationReadFilter)}
>
{TRADE_NOTIFICATION_READ_FILTER_OPTIONS.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
<span className="header-toolbar-divider" aria-hidden="true" />
<select
className="tnl-toolbar-select"
value={listSort}
aria-label="목록 정렬"
onChange={e => handleListSortChange(e.target.value as TradeNotificationListSort)}
>
{TRADE_NOTIFICATION_SORT_OPTIONS.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
<span className="header-toolbar-divider" aria-hidden="true" />
<select
className="tnl-toolbar-select"
value={candleFilter} value={candleFilter}
aria-label="시간봉 필터" aria-label="시간봉 필터"
onChange={e => handleCandleFilterChange(e.target.value as TradeNotificationCandleFilter)} onChange={e => handleCandleFilterChange(e.target.value as TradeNotificationCandleFilter)}
@@ -392,6 +374,12 @@ export const TradeNotificationListPage: React.FC<Props> = ({
<option key={o.value} value={o.value}>{o.label}</option> <option key={o.value} value={o.value}>{o.label}</option>
))} ))}
</select> </select>
</div>
{unreadCount > 0 && (
<button type="button" className="tnl-btn tnl-btn--primary" onClick={markAllAsRead}>
</button>
)}
<div className="tnl-layout-toggle vtd-view-toggle" role="group" aria-label="목록 표시 방식"> <div className="tnl-layout-toggle vtd-view-toggle" role="group" aria-label="목록 표시 방식">
<button <button
type="button" type="button"
@@ -953,8 +953,17 @@
gap: 10px; gap: 10px;
} }
.tnl-toolbar-selects {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.tnl-toolbar-select,
.tnl-candle-filter-select { .tnl-candle-filter-select {
height: 32px; height: 32px;
min-width: 108px;
padding: 0 10px; padding: 0 10px;
border: 1px solid var(--se-border, var(--border)); border: 1px solid var(--se-border, var(--border));
border-radius: 8px; border-radius: 8px;
@@ -965,6 +974,7 @@
cursor: pointer; cursor: pointer;
} }
.tnl-toolbar-select:focus-visible,
.tnl-candle-filter-select:focus-visible { .tnl-candle-filter-select:focus-visible {
outline: 2px solid color-mix(in srgb, var(--accent, #3f7ef5) 55%, transparent); outline: 2px solid color-mix(in srgb, var(--accent, #3f7ef5) 55%, transparent);
outline-offset: 1px; outline-offset: 1px;
@@ -43,6 +43,25 @@ export function saveTradeNotificationListSort(sort: TradeNotificationListSort):
patchUiPreferences({ tradeNotifications: { listSort: sort } }); patchUiPreferences({ tradeNotifications: { listSort: sort } });
} }
export type TradeNotificationReadFilter = 'all' | 'unread';
export const TRADE_NOTIFICATION_READ_FILTER_OPTIONS: {
value: TradeNotificationReadFilter;
label: string;
}[] = [
{ value: 'all', label: '전체' },
{ value: 'unread', label: '미확인' },
];
export const TRADE_NOTIFICATION_SORT_OPTIONS: {
value: TradeNotificationListSort;
label: string;
}[] = [
{ value: 'market', label: '종목순' },
{ value: 'newest', label: '최신시간순' },
{ value: 'oldest', label: '이전시간순' },
];
/** 알림 목록 시간봉 필터 — '' = 전체 */ /** 알림 목록 시간봉 필터 — '' = 전체 */
export type TradeNotificationCandleFilter = '' | (typeof STRATEGY_CANDLE_TYPE_OPTIONS)[number]['value']; export type TradeNotificationCandleFilter = '' | (typeof STRATEGY_CANDLE_TYPE_OPTIONS)[number]['value'];