import type { ReactNode } from 'react'; import { Typography } from '@/components/ui/typography'; import { cn } from '@/lib/utils'; import type { PriceRailView } from '../../types'; const won = (n?: number | null) => (n != null ? `₩${n.toLocaleString()}` : '-'); /** * 헤더 가격 레일 — 협상을 판정하는 네 가격을 왼쪽에서 오른쪽 흐름 순으로 고정 노출한다. * 접힘·대화 탭에서도 남으므로 협상을 보며 기준가를 확인할 수 있다. * * 1번 칸만 유형에 따라 갈린다: * 1:1 = 앵커링가(협력사가 하나라 견적의 값) * 1:N = 투찰 현황(앵커는 (상품×공급사)마다 달라 대표값이 성립하지 않는다 → 협상현황 표의 협력사별 컬럼으로) * 4번 칸은 상태로 갈린다: 낙찰가 / 최저 투찰가(개찰) / 현재 제시가·최저가(진행 중). */ export function PriceRail({ rail, onShowTarget }: { rail: PriceRailView; onShowTarget?: () => void }) { const savingsText = rail.savings != null ? `목표가 대비 ${rail.savings >= 0 ? '−' : '+'}${won(Math.abs(rail.savings))}` + (rail.savingsRate != null ? ` (${rail.savings >= 0 ? '−' : '+'}${Math.abs(rail.savingsRate * 100).toFixed(1)}%)` : '') : null; return (
{rail.oneToOne ? ( ) : ( )}
); } /** 목표가 대비 하락폭(%) — 앵커가 목표가보다 낮은 정상 케이스만 의미가 있다. */ function pctGap(target: number, anchor: number): string { if (!target) return '0.0%'; return `${(((target - anchor) / target) * 100).toFixed(1)}%`; } function Cell({ label, badge, dot, value, sub, onSub, className, valueClassName, }: { label: string; badge?: string | null; dot: string; value: ReactNode; sub?: string; /** 있으면 보조줄이 버튼(목표가 산정 내역) */ onSub?: () => void; className?: string; valueClassName?: string; }) { return (
{label} {badge && ( {badge} )}
{value} {/* 보조줄은 값이 없어도 자리를 지킨다 — 칸마다 높이가 달라지면 레일 바닥이 들쭉날쭉해진다. */}
{onSub ? ( ) : sub ? ( {sub} ) : null}
); }