feat(stock): 모바일 반응형 — 캐러셀 지표 + 스와이프 탭 + FAB
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 = () => {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="stock-main-tabs">
|
||||
{[
|
||||
{ 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 }) => (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
className={`stock-main-tab ${cls ?? ''} ${activeTab === id ? 'is-active' : ''}`}
|
||||
onClick={() => setActiveTab(id)}
|
||||
>
|
||||
<span className="stock-main-tab__icon">{icon}</span>
|
||||
<span className="stock-main-tab__label">{label}</span>
|
||||
{sub && <span className="stock-main-tab__sub">{sub}</span>}
|
||||
{badge > 0 && <span className="stock-main-tab__badge">{badge}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* Tab bar + Tab content */}
|
||||
{isMobile ? (
|
||||
<SwipeableView
|
||||
tabs={TAB_ORDER.map((tabId, i) => ({
|
||||
key: tabId,
|
||||
label: tabLabels[i],
|
||||
content: tabId === TAB_PORTFOLIO
|
||||
? <PortfolioTab pf={pf} asset={asset} handleSell={handleSell} handleSaveSnapshot={handleSaveSnapshot} />
|
||||
: tabId === TAB_AI
|
||||
? <AiTradeTab aib={aib} />
|
||||
: tabId === TAB_REPORT
|
||||
? <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />
|
||||
: <AdvisorTab pf={pf} advisor={advisor} />,
|
||||
}))}
|
||||
activeIndex={tabIndex}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="stock-main-tabs">
|
||||
{[
|
||||
{ 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 }) => (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
className={`stock-main-tab ${cls ?? ''} ${activeTab === id ? 'is-active' : ''}`}
|
||||
onClick={() => setActiveTab(id)}
|
||||
>
|
||||
<span className="stock-main-tab__icon">{icon}</span>
|
||||
<span className="stock-main-tab__label">{label}</span>
|
||||
{sub && <span className="stock-main-tab__sub">{sub}</span>}
|
||||
{badge > 0 && <span className="stock-main-tab__badge">{badge}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
{activeTab === TAB_PORTFOLIO && (
|
||||
<PortfolioTab pf={pf} asset={asset} handleSell={handleSell} handleSaveSnapshot={handleSaveSnapshot} />
|
||||
{activeTab === TAB_PORTFOLIO && (
|
||||
<PortfolioTab pf={pf} asset={asset} handleSell={handleSell} handleSaveSnapshot={handleSaveSnapshot} />
|
||||
)}
|
||||
{activeTab === TAB_AI && <AiTradeTab aib={aib} />}
|
||||
{activeTab === TAB_REPORT && <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />}
|
||||
{activeTab === TAB_ADVISOR && <AdvisorTab pf={pf} advisor={advisor} />}
|
||||
</>
|
||||
)}
|
||||
{activeTab === TAB_AI && <AiTradeTab aib={aib} />}
|
||||
{activeTab === TAB_REPORT && <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />}
|
||||
{activeTab === TAB_ADVISOR && <AdvisorTab pf={pf} advisor={advisor} />}
|
||||
|
||||
{/* Sell history drawer (always mounted) */}
|
||||
<SellHistoryDrawer
|
||||
|
||||
Reference in New Issue
Block a user