refactor(returns): extract shared ReturnBucket from Intraday/PeriodReturns
IntradayReturns(1D 10분봉 모드)와 PeriodReturns(일봉 이상)는 페이지의 같은 슬롯에서 모드 전환으로 갈아끼우는 한 쌍이다. 두 컴포넌트의 Bucket 서브컴포넌트는 className 문자열까지 완전히 동일했지만 각 파일에 별도 정의돼 있었다 — 한쪽 톤만 바꾸면 모드 전환 시 시각적 점프가 생기는 드리프트 리스크. 신규 web/components/ReturnBucket.tsx 로 단일 source of truth 분리: - 색 규칙(rose/sky/zinc), 카드 톤(rounded-md border-zinc-800 bg-zinc-900/30 px-2 py-2 text-center), 라벨/값 타이포 일괄 캡슐화. - 두 패널은 각자 horizon 라벨/계산 로직만 유지하고 렌더는 ReturnBucket 으로 위임. 기존 외부 동작/시각 결과 동일 — 동일한 className 을 한 곳으로 묶은 순수 리팩토링. 검증: - npx tsc --noEmit clean - npx next lint "✔ No ESLint warnings or errors"
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
// 추가 백엔드 호출 없음 — chart.ohlcv 슬라이싱만.
|
||||
|
||||
import type { OhlcvPoint } from "../lib/api";
|
||||
import { ReturnBucket } from "./ReturnBucket";
|
||||
|
||||
const BUCKETS: { label: string; bars: number }[] = [
|
||||
{ label: "10분", bars: 1 },
|
||||
@@ -55,27 +56,9 @@ export function IntradayReturns({ ohlcv }: { ohlcv: OhlcvPoint[] }) {
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-5">
|
||||
{buckets.map((b) => (
|
||||
<Bucket key={b.label} label={b.label} pct={b.pct} />
|
||||
<ReturnBucket key={b.label} label={b.label} pct={b.pct} />
|
||||
))}
|
||||
<Bucket label="시가대비" pct={todayPct} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Bucket({ label, pct }: { label: string; pct: number | null }) {
|
||||
// 한국식 컬러 — 상승=rose, 하락=sky, 0/null=zinc.
|
||||
const tone =
|
||||
pct == null || pct === 0
|
||||
? "text-zinc-400"
|
||||
: pct > 0
|
||||
? "text-rose-400"
|
||||
: "text-sky-400";
|
||||
return (
|
||||
<div className="rounded-md border border-zinc-800 bg-zinc-900/30 px-2 py-2 text-center">
|
||||
<div className="text-[11px] text-zinc-500">{label}</div>
|
||||
<div className={`mt-1 text-sm font-medium tabular-nums ${tone}`}>
|
||||
{pct == null ? "—" : `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`}
|
||||
<ReturnBucket label="시가대비" pct={todayPct} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// 거래일 기준 근사: 1주≈5, 1개월≈21, 3개월≈63, 6개월≈126, 1년≈252.
|
||||
|
||||
import type { OhlcvPoint } from "../lib/api";
|
||||
import { ReturnBucket } from "./ReturnBucket";
|
||||
|
||||
const BUCKETS: { label: string; days: number }[] = [
|
||||
{ label: "1주", days: 5 },
|
||||
@@ -45,28 +46,9 @@ export function PeriodReturns({ ohlcv }: { ohlcv: OhlcvPoint[] }) {
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
|
||||
{buckets.map((b) => (
|
||||
<Bucket key={b.label} label={b.label} pct={b.pct} />
|
||||
<ReturnBucket key={b.label} label={b.label} pct={b.pct} />
|
||||
))}
|
||||
<Bucket label="전체" pct={all} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Bucket({ label, pct }: { label: string; pct: number | null }) {
|
||||
const tone =
|
||||
pct == null || pct === 0
|
||||
? "text-zinc-400"
|
||||
: pct > 0
|
||||
? "text-rose-400"
|
||||
: "text-sky-400";
|
||||
return (
|
||||
<div className="rounded-md border border-zinc-800 bg-zinc-900/30 px-2 py-2 text-center">
|
||||
<div className="text-[11px] text-zinc-500">{label}</div>
|
||||
<div className={`mt-1 text-sm font-medium tabular-nums ${tone}`}>
|
||||
{pct == null
|
||||
? "—"
|
||||
: `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`}
|
||||
<ReturnBucket label="전체" pct={all} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
28
web/components/ReturnBucket.tsx
Normal file
28
web/components/ReturnBucket.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
// 수익률 미니 셀 — IntradayReturns(1D 10분봉 모드) 와 PeriodReturns(일봉 이상) 가 같은
|
||||
// 슬롯에서 모드 전환으로 갈아끼우므로 두 패널의 Bucket 톤/색 규칙은 반드시 동일해야 한다.
|
||||
// 예전엔 각 파일에 동일한 Bucket 이 중복 정의돼 있어 한쪽만 톤을 바꾸면 모드 전환 시
|
||||
// 시각적 점프가 생길 위험이 있었다. 한 곳으로 묶어 단일 source of truth 로 둔다.
|
||||
//
|
||||
// 한국식 컬러 규칙: 상승 = rose, 하락 = sky, 0/null = zinc (Toss 톤).
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
pct: number | null;
|
||||
};
|
||||
|
||||
export function ReturnBucket({ label, pct }: Props) {
|
||||
const tone =
|
||||
pct == null || pct === 0
|
||||
? "text-zinc-400"
|
||||
: pct > 0
|
||||
? "text-rose-400"
|
||||
: "text-sky-400";
|
||||
return (
|
||||
<div className="rounded-md border border-zinc-800 bg-zinc-900/30 px-2 py-2 text-center">
|
||||
<div className="text-[11px] text-zinc-500">{label}</div>
|
||||
<div className={`mt-1 text-sm font-medium tabular-nums ${tone}`}>
|
||||
{pct == null ? "—" : `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user