트레이딩창 UI 배치 및 UX 수정 및 기획서 추가

This commit is contained in:
2026-02-24 15:43:56 +09:00
parent 19ebb1c6ea
commit a16af8ad7d
16 changed files with 1615 additions and 479 deletions

View File

@@ -37,6 +37,7 @@ import {
const UP_COLOR = "#ef4444";
const MINUTE_SYNC_INTERVAL_MS = 30000;
const REALTIME_STALE_THRESHOLD_MS = 12000;
const CHART_MIN_HEIGHT = 220;
interface ChartPalette {
backgroundColor: string;
@@ -60,7 +61,10 @@ const DEFAULT_CHART_PALETTE: ChartPalette = {
function readCssVar(name: string, fallback: string) {
if (typeof window === "undefined") return fallback;
const value = window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();
const value = window
.getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
return value || fallback;
}
@@ -69,16 +73,28 @@ function getChartPaletteFromCssVars(themeMode: "light" | "dark"): ChartPalette {
const backgroundVar = isDark
? "--brand-chart-background-dark"
: "--brand-chart-background-light";
const textVar = isDark ? "--brand-chart-text-dark" : "--brand-chart-text-light";
const borderVar = isDark ? "--brand-chart-border-dark" : "--brand-chart-border-light";
const gridVar = isDark ? "--brand-chart-grid-dark" : "--brand-chart-grid-light";
const textVar = isDark
? "--brand-chart-text-dark"
: "--brand-chart-text-light";
const borderVar = isDark
? "--brand-chart-border-dark"
: "--brand-chart-border-light";
const gridVar = isDark
? "--brand-chart-grid-dark"
: "--brand-chart-grid-light";
const crosshairVar = isDark
? "--brand-chart-crosshair-dark"
: "--brand-chart-crosshair-light";
return {
backgroundColor: readCssVar(backgroundVar, DEFAULT_CHART_PALETTE.backgroundColor),
downColor: readCssVar("--brand-chart-down", DEFAULT_CHART_PALETTE.downColor),
backgroundColor: readCssVar(
backgroundVar,
DEFAULT_CHART_PALETTE.backgroundColor,
),
downColor: readCssVar(
"--brand-chart-down",
DEFAULT_CHART_PALETTE.downColor,
),
volumeDownColor: readCssVar(
"--brand-chart-volume-down",
DEFAULT_CHART_PALETTE.volumeDownColor,
@@ -237,7 +253,8 @@ export function StockLineChart({
* @see lib/kis/domestic.ts getDomesticChart cursor
*/
const handleLoadMore = useCallback(async () => {
if (!symbol || !credentials || !nextCursor || loadingMoreRef.current) return;
if (!symbol || !credentials || !nextCursor || loadingMoreRef.current)
return;
loadingMoreRef.current = true;
setIsLoadingMore(true);
@@ -284,7 +301,7 @@ export function StockLineChart({
const chart = createChart(container, {
width: Math.max(container.clientWidth, 320),
height: Math.max(container.clientHeight, 340),
height: Math.max(container.clientHeight, CHART_MIN_HEIGHT),
layout: {
background: { type: ColorType.Solid, color: palette.backgroundColor },
textColor: palette.textColor,
@@ -298,7 +315,7 @@ export function StockLineChart({
borderColor: palette.borderColor,
scaleMargins: {
top: 0.08,
bottom: 0.24,
bottom: 0.2,
},
},
grid: {
@@ -372,7 +389,7 @@ export function StockLineChart({
const resizeObserver = new ResizeObserver(() => {
chart.resize(
Math.max(container.clientWidth, 320),
Math.max(container.clientHeight, 340),
Math.max(container.clientHeight, CHART_MIN_HEIGHT),
);
});
resizeObserver.observe(container);
@@ -380,7 +397,7 @@ export function StockLineChart({
const rafId = window.requestAnimationFrame(() => {
chart.resize(
Math.max(container.clientWidth, 320),
Math.max(container.clientHeight, 340),
Math.max(container.clientHeight, CHART_MIN_HEIGHT),
);
});
@@ -452,7 +469,9 @@ export function StockLineChart({
if (disposed) return;
let mergedBars = normalizeCandles(firstPage.candles, timeframe);
let resolvedNextCursor = firstPage.hasMore ? firstPage.nextCursor : null;
let resolvedNextCursor = firstPage.hasMore
? firstPage.nextCursor
: null;
// 분봉은 기본 3페이지까지 순차 조회해 오전 구간 누락을 줄입니다.
if (
@@ -474,7 +493,9 @@ export function StockLineChart({
const olderBars = normalizeCandles(olderPage.candles, timeframe);
mergedBars = mergeBars(olderBars, mergedBars);
resolvedNextCursor = olderPage.hasMore ? olderPage.nextCursor : null;
resolvedNextCursor = olderPage.hasMore
? olderPage.nextCursor
: null;
minuteCursor = olderPage.hasMore ? olderPage.nextCursor : null;
extraPageCount += 1;
} catch {
@@ -522,11 +543,11 @@ export function StockLineChart({
}
}, [isChartReady, renderableBars, setSeriesData]);
/**
* @description WebSocket 체결 틱을 현재 timeframe 캔들에 반영합니다.
* @see features/trade/hooks/useKisTradeWebSocket.ts latestTick
* @see features/trade/components/chart/chart-utils.ts toRealtimeTickBar
*/
/**
* @description WebSocket 체결 틱을 현재 timeframe 캔들에 반영합니다.
* @see features/trade/hooks/useKisTradeWebSocket.ts latestTick
* @see features/trade/components/chart/chart-utils.ts toRealtimeTickBar
*/
useEffect(() => {
if (!latestTick) return;
if (bars.length === 0) return;
@@ -600,7 +621,7 @@ export function StockLineChart({
})();
return (
<div className="flex h-full min-h-[340px] flex-col bg-white dark:bg-brand-900/10">
<div className="flex h-full min-h-[280px] flex-col bg-white dark:bg-brand-900/10 xl:min-h-0">
{/* ========== CHART TOOLBAR ========== */}
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-brand-100 bg-muted/20 px-2 py-2 sm:px-3 dark:border-brand-800/45 dark:bg-brand-900/35">
<div className="flex flex-wrap items-center gap-1 text-xs sm:text-sm">
@@ -668,14 +689,15 @@ export function StockLineChart({
</div>
<div className="text-[11px] text-muted-foreground dark:text-brand-100/85 sm:text-xs">
O {formatPrice(latest?.open ?? 0)} H {formatPrice(latest?.high ?? 0)} L{" "}
{formatPrice(latest?.low ?? 0)} C{" "}
O {formatPrice(latest?.open ?? 0)} H {formatPrice(latest?.high ?? 0)}{" "}
L {formatPrice(latest?.low ?? 0)} C{" "}
<span
className={cn(
change >= 0 ? "text-red-600" : "text-blue-600 dark:text-blue-400",
)}
>
{formatPrice(latest?.close ?? 0)} ({formatSignedPercent(changeRate)})
{formatPrice(latest?.close ?? 0)} ({formatSignedPercent(changeRate)}
)
</span>
</div>
</div>