import { useCallback, useState, useTransition } from "react"; import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store"; import type { DashboardMarketPhase, DashboardPriceSource, DashboardRealtimeTradeTick, DashboardStockSearchItem, DashboardStockItem, } from "@/features/dashboard/types/dashboard.types"; import { fetchStockOverview } from "@/features/dashboard/apis/kis-stock.api"; interface OverviewMeta { priceSource: DashboardPriceSource; marketPhase: DashboardMarketPhase; fetchedAt: string; } export function useStockOverview() { const [selectedStock, setSelectedStock] = useState( null, ); const [meta, setMeta] = useState(null); const [error, setError] = useState(null); const [isLoading, startTransition] = useTransition(); const loadOverview = useCallback( ( symbol: string, credentials: KisRuntimeCredentials | null, marketHint?: DashboardStockSearchItem["market"], ) => { if (!credentials) return; startTransition(async () => { try { setError(null); const data = await fetchStockOverview(symbol, credentials); setSelectedStock({ ...data.stock, market: marketHint ?? data.stock.market, }); setMeta({ priceSource: data.priceSource, marketPhase: data.marketPhase, fetchedAt: data.fetchedAt, }); } catch (err) { const message = err instanceof Error ? err.message : "종목 조회 중 오류가 발생했습니다."; setError(message); setMeta(null); } }); }, [], ); /** * 실시간 체결 수신 시 헤더/주요 시세 상태만 갱신합니다. * 차트 캔들은 StockLineChart 내부 API 응답을 기준으로 유지합니다. * @see features/dashboard/components/DashboardContainer.tsx useKisTradeWebSocket onTick 전달 * @see features/dashboard/components/chart/StockLineChart.tsx 차트 데이터 fetchStockChart 기준 렌더링 */ const updateRealtimeTradeTick = useCallback( (tick: DashboardRealtimeTradeTick) => { setSelectedStock((prev) => { if (!prev) return prev; const { price, accumulatedVolume, change, changeRate } = tick; const nextChange = change; const nextChangeRate = Number.isFinite(changeRate) ? changeRate : prev.prevClose > 0 ? (nextChange / prev.prevClose) * 100 : prev.changeRate; return { ...prev, currentPrice: price, change: nextChange, changeRate: nextChangeRate, high: prev.high > 0 ? Math.max(prev.high, price) : price, low: prev.low > 0 ? Math.min(prev.low, price) : price, volume: accumulatedVolume > 0 ? accumulatedVolume : prev.volume, }; }); }, [], ); return { selectedStock, setSelectedStock, meta, setMeta, error, setError, isLoading, loadOverview, updateRealtimeTradeTick, }; }