대시보드 중간 커밋
This commit is contained in:
118
features/dashboard/hooks/useStockOverview.ts
Normal file
118
features/dashboard/hooks/useStockOverview.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
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<DashboardStockItem | null>(
|
||||
null,
|
||||
);
|
||||
const [meta, setMeta] = useState<OverviewMeta | null>(null);
|
||||
const [error, setError] = useState<string | null>(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);
|
||||
}
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 실시간 체결 데이터 수신 시 헤더/차트 기준 가격을 갱신합니다.
|
||||
const updateRealtimeTradeTick = useCallback(
|
||||
(tick: DashboardRealtimeTradeTick) => {
|
||||
setSelectedStock((prev) => {
|
||||
if (!prev) return prev;
|
||||
const { price, accumulatedVolume, change, changeRate, tickTime } = tick;
|
||||
const pointTime =
|
||||
tickTime && tickTime.length === 6
|
||||
? `${tickTime.slice(0, 2)}:${tickTime.slice(2, 4)}`
|
||||
: "실시간";
|
||||
|
||||
const nextChange = change;
|
||||
const nextChangeRate = Number.isFinite(changeRate)
|
||||
? changeRate
|
||||
: prev.prevClose > 0
|
||||
? (nextChange / prev.prevClose) * 100
|
||||
: prev.changeRate;
|
||||
const nextHigh = prev.high > 0 ? Math.max(prev.high, price) : price;
|
||||
const nextLow = prev.low > 0 ? Math.min(prev.low, price) : price;
|
||||
const nextCandles =
|
||||
prev.candles.length > 0 &&
|
||||
prev.candles[prev.candles.length - 1]?.time === pointTime
|
||||
? [
|
||||
...prev.candles.slice(0, -1),
|
||||
{
|
||||
...prev.candles[prev.candles.length - 1],
|
||||
time: pointTime,
|
||||
price,
|
||||
},
|
||||
]
|
||||
: [...prev.candles, { time: pointTime, price }].slice(-80);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
currentPrice: price,
|
||||
change: nextChange,
|
||||
changeRate: nextChangeRate,
|
||||
high: nextHigh,
|
||||
low: nextLow,
|
||||
volume: accumulatedVolume > 0 ? accumulatedVolume : prev.volume,
|
||||
candles: nextCandles,
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
selectedStock,
|
||||
setSelectedStock,
|
||||
meta,
|
||||
setMeta,
|
||||
error,
|
||||
setError,
|
||||
isLoading,
|
||||
loadOverview,
|
||||
updateRealtimeTradeTick,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user