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 슬라이싱만.
|
// 추가 백엔드 호출 없음 — chart.ohlcv 슬라이싱만.
|
||||||
|
|
||||||
import type { OhlcvPoint } from "../lib/api";
|
import type { OhlcvPoint } from "../lib/api";
|
||||||
|
import { ReturnBucket } from "./ReturnBucket";
|
||||||
|
|
||||||
const BUCKETS: { label: string; bars: number }[] = [
|
const BUCKETS: { label: string; bars: number }[] = [
|
||||||
{ label: "10분", bars: 1 },
|
{ label: "10분", bars: 1 },
|
||||||
@@ -55,27 +56,9 @@ export function IntradayReturns({ ohlcv }: { ohlcv: OhlcvPoint[] }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-5">
|
<div className="grid grid-cols-3 gap-2 sm:grid-cols-5">
|
||||||
{buckets.map((b) => (
|
{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} />
|
<ReturnBucket 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)}%`}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// 거래일 기준 근사: 1주≈5, 1개월≈21, 3개월≈63, 6개월≈126, 1년≈252.
|
// 거래일 기준 근사: 1주≈5, 1개월≈21, 3개월≈63, 6개월≈126, 1년≈252.
|
||||||
|
|
||||||
import type { OhlcvPoint } from "../lib/api";
|
import type { OhlcvPoint } from "../lib/api";
|
||||||
|
import { ReturnBucket } from "./ReturnBucket";
|
||||||
|
|
||||||
const BUCKETS: { label: string; days: number }[] = [
|
const BUCKETS: { label: string; days: number }[] = [
|
||||||
{ label: "1주", days: 5 },
|
{ label: "1주", days: 5 },
|
||||||
@@ -45,28 +46,9 @@ export function PeriodReturns({ ohlcv }: { ohlcv: OhlcvPoint[] }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
|
<div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
|
||||||
{buckets.map((b) => (
|
{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} />
|
<ReturnBucket 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)}%`}
|
|
||||||
</div>
|
</div>
|
||||||
</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