feat(stock): 거래 데스크에 관심종목 탭 등재 + API 문서 갱신
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHXzpsZQxKG9hQmNRfZjRS
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
| `/blog` | `Blog` | 마크다운 기반 블로그 |
|
| `/blog` | `Blog` | 마크다운 기반 블로그 |
|
||||||
| `/lotto` | `Lotto` | 로또 추천/통계 |
|
| `/lotto` | `Lotto` | 로또 추천/통계 |
|
||||||
| `/stock` | `Stock` | 주식 뉴스/지수 |
|
| `/stock` | `Stock` | 주식 뉴스/지수 |
|
||||||
| `/stock/trade` | `StockTrade` | 주식 트레이딩 |
|
| `/stock/trade` | `StockTrade` | 주식 트레이딩 (포트폴리오·리포트·어드바이저·보유종목 인텔·관심종목 5탭) |
|
||||||
| `/stock/screener` | `Screener` | 노드 기반 강세주 스크리너 (폼 ↔ n8n 스타일 캔버스 모드 토글, 점수 노드 7 + 위생 게이트 + ATR 포지션 사이저) |
|
| `/stock/screener` | `Screener` | 노드 기반 강세주 스크리너 (폼 ↔ n8n 스타일 캔버스 모드 토글, 점수 노드 7 + 위생 게이트 + ATR 포지션 사이저) |
|
||||||
| `/realestate` | `Subscription` | 청약 자격·일정 관리<br>• **프로필 탭**: 자치구 5티어 분류(드래그&드롭, PC 전용 / 모바일 read-only), 매칭 임계값 슬라이더, 텔레그램 알림 토글<br>• **카드/매칭 결과**: district 뱃지 + 5티어(S/A/B/C/D) 뱃지 표시<br>• **상세 모달**: 매칭 분석 섹션 (점수 + 사유 + 신청 자격) |
|
| `/realestate` | `Subscription` | 청약 자격·일정 관리<br>• **프로필 탭**: 자치구 5티어 분류(드래그&드롭, PC 전용 / 모바일 read-only), 매칭 임계값 슬라이더, 텔레그램 알림 토글<br>• **카드/매칭 결과**: district 뱃지 + 5티어(S/A/B/C/D) 뱃지 표시<br>• **상세 모달**: 매칭 분석 섹션 (점수 + 사유 + 신청 자격) |
|
||||||
| `/realestate/property` | `RealEstate` | 관심 단지 정보 |
|
| `/realestate/property` | `RealEstate` | 관심 단지 정보 |
|
||||||
@@ -102,6 +102,10 @@ proxy: {
|
|||||||
| 스크리너 | POST | `/api/stock/screener/snapshot/refresh` |
|
| 스크리너 | POST | `/api/stock/screener/snapshot/refresh` |
|
||||||
| 스크리너 | GET | `/api/stock/screener/runs?limit=N` |
|
| 스크리너 | GET | `/api/stock/screener/runs?limit=N` |
|
||||||
| 스크리너 | GET | `/api/stock/screener/runs/:id` |
|
| 스크리너 | GET | `/api/stock/screener/runs/:id` |
|
||||||
|
| 관심종목 | GET | `/api/stock/watchlist` — { watchlist: [{ ticker, name, note, params, added_at }] } |
|
||||||
|
| 관심종목 | POST | `/api/stock/watchlist` — body: { ticker, name?, note? } |
|
||||||
|
| 관심종목 | DELETE | `/api/stock/watchlist/:ticker` |
|
||||||
|
| 매매 시그널 | GET | `/api/stock/trade-alerts?days=N` — { alerts: [{ id, ticker, name, kind, condition, price, detail, fired_at }] } |
|
||||||
| 포트폴리오 | GET/POST | `/api/portfolio` |
|
| 포트폴리오 | GET/POST | `/api/portfolio` |
|
||||||
| 포트폴리오 | PUT/DELETE | `/api/portfolio/:id` |
|
| 포트폴리오 | PUT/DELETE | `/api/portfolio/:id` |
|
||||||
| 예수금 | PUT | `/api/portfolio/cash` — body: `{ broker, cash }` |
|
| 예수금 | PUT | `/api/portfolio/cash` — body: `{ broker, cash }` |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import SwipeableView from '../../components/SwipeableView';
|
|||||||
import {
|
import {
|
||||||
formatNumber, formatPercent,
|
formatNumber, formatPercent,
|
||||||
toNumeric, profitColorClass,
|
toNumeric, profitColorClass,
|
||||||
TAB_PORTFOLIO, TAB_REPORT, TAB_ADVISOR, TAB_HOLDINGS_INTEL,
|
TAB_PORTFOLIO, TAB_REPORT, TAB_ADVISOR, TAB_HOLDINGS_INTEL, TAB_WATCHLIST,
|
||||||
} from './stockUtils';
|
} from './stockUtils';
|
||||||
|
|
||||||
/* ── hooks ──────────────────────────────────────────────────────── */
|
/* ── hooks ──────────────────────────────────────────────────────── */
|
||||||
@@ -17,12 +17,14 @@ import useAssetHistory from './hooks/useAssetHistory';
|
|||||||
import useMarketContext from './hooks/useMarketContext';
|
import useMarketContext from './hooks/useMarketContext';
|
||||||
import useReportData from './hooks/useReportData';
|
import useReportData from './hooks/useReportData';
|
||||||
import useAdvisor from './hooks/useAdvisor';
|
import useAdvisor from './hooks/useAdvisor';
|
||||||
|
import useWatchlist from './hooks/useWatchlist';
|
||||||
|
|
||||||
/* ── tab components ─────────────────────────────────────────────── */
|
/* ── tab components ─────────────────────────────────────────────── */
|
||||||
import PortfolioTab from './components/PortfolioTab';
|
import PortfolioTab from './components/PortfolioTab';
|
||||||
import ReportTab from './components/ReportTab';
|
import ReportTab from './components/ReportTab';
|
||||||
import AdvisorTab from './components/AdvisorTab';
|
import AdvisorTab from './components/AdvisorTab';
|
||||||
import HoldingsIntelTab from './components/HoldingsIntelTab';
|
import HoldingsIntelTab from './components/HoldingsIntelTab';
|
||||||
|
import WatchlistTab from './components/WatchlistTab';
|
||||||
import SellHistoryDrawer from './components/SellHistoryDrawer';
|
import SellHistoryDrawer from './components/SellHistoryDrawer';
|
||||||
|
|
||||||
/* ── component ───────────────────────────────────────────────────── */
|
/* ── component ───────────────────────────────────────────────────── */
|
||||||
@@ -31,8 +33,8 @@ const StockTrade = () => {
|
|||||||
const [activeTab, setActiveTab] = React.useState(TAB_REPORT);
|
const [activeTab, setActiveTab] = React.useState(TAB_REPORT);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const TAB_ORDER = [TAB_PORTFOLIO, TAB_REPORT, TAB_ADVISOR, TAB_HOLDINGS_INTEL];
|
const TAB_ORDER = [TAB_PORTFOLIO, TAB_REPORT, TAB_ADVISOR, TAB_HOLDINGS_INTEL, TAB_WATCHLIST];
|
||||||
const tabLabels = ['포트폴리오', '리포트', '어드바이저', '보유종목 인텔'];
|
const tabLabels = ['포트폴리오', '리포트', '어드바이저', '보유종목 인텔', '관심종목'];
|
||||||
const tabIndex = TAB_ORDER.indexOf(activeTab);
|
const tabIndex = TAB_ORDER.indexOf(activeTab);
|
||||||
const handleTabChange = useCallback((idx) => setActiveTab(TAB_ORDER[idx]), []); // eslint-disable-line react-hooks/exhaustive-deps
|
const handleTabChange = useCallback((idx) => setActiveTab(TAB_ORDER[idx]), []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ const StockTrade = () => {
|
|||||||
totalAssets: pf.totalAssets,
|
totalAssets: pf.totalAssets,
|
||||||
marketCtx,
|
marketCtx,
|
||||||
});
|
});
|
||||||
|
const wl = useWatchlist();
|
||||||
|
|
||||||
/* ── sell history filter derived ─────────────────────────────── */
|
/* ── sell history filter derived ─────────────────────────────── */
|
||||||
const sellHistoryBrokers = useMemo(() => {
|
const sellHistoryBrokers = useMemo(() => {
|
||||||
@@ -169,7 +172,9 @@ const StockTrade = () => {
|
|||||||
? <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />
|
? <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />
|
||||||
: tabId === TAB_ADVISOR
|
: tabId === TAB_ADVISOR
|
||||||
? <AdvisorTab pf={pf} advisor={advisor} />
|
? <AdvisorTab pf={pf} advisor={advisor} />
|
||||||
: <HoldingsIntelTab />,
|
: tabId === TAB_HOLDINGS_INTEL
|
||||||
|
? <HoldingsIntelTab />
|
||||||
|
: <WatchlistTab wl={wl} />,
|
||||||
}))}
|
}))}
|
||||||
activeIndex={tabIndex}
|
activeIndex={tabIndex}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
@@ -182,6 +187,7 @@ const StockTrade = () => {
|
|||||||
{ id: TAB_REPORT, icon: '📊', label: '리포트', sub: '분석·AI코치' },
|
{ id: TAB_REPORT, icon: '📊', label: '리포트', sub: '분석·AI코치' },
|
||||||
{ id: TAB_ADVISOR, icon: '🧠', label: 'AI 어드바이저', sub: 'Gemini Pro', className: 'stock-main-tab--advisor' },
|
{ id: TAB_ADVISOR, icon: '🧠', label: 'AI 어드바이저', sub: 'Gemini Pro', className: 'stock-main-tab--advisor' },
|
||||||
{ id: TAB_HOLDINGS_INTEL, icon: '🔍', label: '보유종목 인텔', sub: '신호·이슈', className: 'stock-main-tab--holdings-intel' },
|
{ id: TAB_HOLDINGS_INTEL, icon: '🔍', label: '보유종목 인텔', sub: '신호·이슈', className: 'stock-main-tab--holdings-intel' },
|
||||||
|
{ id: TAB_WATCHLIST, icon: '⭐', label: '관심종목', sub: '관리·시그널', badge: wl.items.length || null },
|
||||||
].map(({ id, icon, label, sub, badge, className: cls }) => (
|
].map(({ id, icon, label, sub, badge, className: cls }) => (
|
||||||
<button
|
<button
|
||||||
key={id}
|
key={id}
|
||||||
@@ -203,6 +209,7 @@ const StockTrade = () => {
|
|||||||
{activeTab === TAB_REPORT && <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />}
|
{activeTab === TAB_REPORT && <ReportTab pf={pf} report={report} ai={ai} marketCtx={marketCtx} />}
|
||||||
{activeTab === TAB_ADVISOR && <AdvisorTab pf={pf} advisor={advisor} />}
|
{activeTab === TAB_ADVISOR && <AdvisorTab pf={pf} advisor={advisor} />}
|
||||||
{activeTab === TAB_HOLDINGS_INTEL && <HoldingsIntelTab />}
|
{activeTab === TAB_HOLDINGS_INTEL && <HoldingsIntelTab />}
|
||||||
|
{activeTab === TAB_WATCHLIST && <WatchlistTab wl={wl} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -150,3 +150,4 @@ export const TAB_PORTFOLIO = 'portfolio';
|
|||||||
export const TAB_REPORT = 'report';
|
export const TAB_REPORT = 'report';
|
||||||
export const TAB_ADVISOR = 'advisor';
|
export const TAB_ADVISOR = 'advisor';
|
||||||
export const TAB_HOLDINGS_INTEL = 'holdings_intel';
|
export const TAB_HOLDINGS_INTEL = 'holdings_intel';
|
||||||
|
export const TAB_WATCHLIST = 'watchlist';
|
||||||
|
|||||||
Reference in New Issue
Block a user