매매 시그널 알림 수정
This commit is contained in:
+8
-10
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user