diff --git a/src/pages/stock/Stock.css b/src/pages/stock/Stock.css index 03112d2..83fb872 100644 --- a/src/pages/stock/Stock.css +++ b/src/pages/stock/Stock.css @@ -2943,3 +2943,41 @@ justify-content: flex-end; } } + +@media (max-width: 768px) { + /* 필터 가로 스크롤 */ + .stock-filter-row { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + flex-wrap: nowrap; + } + + .stock-filter-row > * { + flex-shrink: 0; + } + + /* 지표 카드 가로 스크롤 캐러셀 */ + .stock-snapshot { + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + gap: 12px; + padding-bottom: 8px; + scroll-snap-type: x mandatory; + } + + .stock-snapshot > * { + flex: 0 0 200px; + scroll-snap-align: start; + } + + /* 뉴스 1컬럼 */ + .stock-news-grid { + grid-template-columns: 1fr; + } + + /* 매크로 지표 1컬럼 */ + .stock-macro-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/stock/Stock.jsx b/src/pages/stock/Stock.jsx index 53e4f20..1dc7170 100644 --- a/src/pages/stock/Stock.jsx +++ b/src/pages/stock/Stock.jsx @@ -1,8 +1,11 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { getStockIndices, getStockNews, getFearAndGreed, getVix, getTreasury10Y, getWTI, getBrent } from '../../api'; import Loading from '../../components/Loading'; import FearGreedGauge from '../../components/FearGreedGauge'; +import { useIsMobile } from '../../hooks/useIsMobile'; +import PullToRefresh from '../../components/PullToRefresh'; +import FAB from '../../components/FAB'; import './Stock.css'; const formatDate = (value) => { @@ -109,6 +112,7 @@ const getVixLevel = (score) => { }; const Stock = () => { + const isMobile = useIsMobile(); const [newsDomestic, setNewsDomestic] = useState([]); const [newsOverseas, setNewsOverseas] = useState([]); const [newsCategory, setNewsCategory] = useState('domestic'); @@ -146,6 +150,10 @@ const Stock = () => { } }; + const handleRefresh = useCallback(async () => { + await loadNews(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + const loadIndices = async () => { setIndicesLoading(true); setIndicesError(''); @@ -217,6 +225,7 @@ const Stock = () => { newsCategory === 'domestic' ? newsDomestic : newsOverseas; return ( +
@@ -559,6 +568,13 @@ const Stock = () => { )}
+ + + + + } /> + ); }; diff --git a/src/pages/stock/StockTrade.jsx b/src/pages/stock/StockTrade.jsx index da805d3..c9f117a 100644 --- a/src/pages/stock/StockTrade.jsx +++ b/src/pages/stock/StockTrade.jsx @@ -1,6 +1,8 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { Link } from 'react-router-dom'; import './Stock.css'; +import { useIsMobile } from '../../hooks/useIsMobile'; +import SwipeableView from '../../components/SwipeableView'; import { formatNumber, formatPercent, toNumeric, profitColorClass, @@ -28,6 +30,12 @@ import SellHistoryDrawer from './components/SellHistoryDrawer'; const StockTrade = () => { const [activeTab, setActiveTab] = React.useState(TAB_REPORT); + const isMobile = useIsMobile(); + + const TAB_ORDER = [TAB_PORTFOLIO, TAB_AI, TAB_REPORT, TAB_ADVISOR]; + const tabLabels = ['포트폴리오', 'AI 트레이드', '리포트', '어드바이저']; + const tabIndex = TAB_ORDER.indexOf(activeTab); + const handleTabChange = useCallback((idx) => setActiveTab(TAB_ORDER[idx]), []); // eslint-disable-line react-hooks/exhaustive-deps /* ── hooks ────────────────────────────────────────────────────── */ const pf = usePortfolio(); @@ -166,35 +174,54 @@ const StockTrade = () => {
- {/* Tab bar */} -
- {[ - { id: TAB_PORTFOLIO, icon: '💼', label: '쟁승토리 계좌', badge: pf.portfolioHoldings.length || null }, - { id: TAB_AI, icon: '🤖', label: 'AI 투자', sub: '모의투자' }, - { id: TAB_REPORT, icon: '📊', label: '리포트', sub: '분석·AI코치' }, - { id: TAB_ADVISOR, icon: '🧠', label: 'AI 어드바이저', sub: 'Gemini Pro', className: 'stock-main-tab--advisor' }, - ].map(({ id, icon, label, sub, badge, className: cls }) => ( - - ))} -
+ {/* Tab bar + Tab content */} + {isMobile ? ( + ({ + key: tabId, + label: tabLabels[i], + content: tabId === TAB_PORTFOLIO + ? + : tabId === TAB_AI + ? + : tabId === TAB_REPORT + ? + : , + }))} + activeIndex={tabIndex} + onTabChange={handleTabChange} + /> + ) : ( + <> +
+ {[ + { id: TAB_PORTFOLIO, icon: '💼', label: '쟁승토리 계좌', badge: pf.portfolioHoldings.length || null }, + { id: TAB_AI, icon: '🤖', label: 'AI 투자', sub: '모의투자' }, + { id: TAB_REPORT, icon: '📊', label: '리포트', sub: '분석·AI코치' }, + { id: TAB_ADVISOR, icon: '🧠', label: 'AI 어드바이저', sub: 'Gemini Pro', className: 'stock-main-tab--advisor' }, + ].map(({ id, icon, label, sub, badge, className: cls }) => ( + + ))} +
- {/* Tab content */} - {activeTab === TAB_PORTFOLIO && ( - + {activeTab === TAB_PORTFOLIO && ( + + )} + {activeTab === TAB_AI && } + {activeTab === TAB_REPORT && } + {activeTab === TAB_ADVISOR && } + )} - {activeTab === TAB_AI && } - {activeTab === TAB_REPORT && } - {activeTab === TAB_ADVISOR && } {/* Sell history drawer (always mounted) */}