- 반응형: 최소 폭 1350→1024px, max-[1350px]/max-[1180px] 2단계 축소 - 사이드바 350→310→280, 우측 메뉴 360→320→280, 채팅 거터 140/126→80/72→48/40 (헤더 패딩 동기) - 가격·할인율 입력 min-w 480→420→320, 가운데 버튼 패딩·최소폭 축소, 상품목록 버튼 1180px 미만 아이콘만 - 상품 이미지 220→200, 사이드바 정보 행 값 영역 고정 150px → flex-1 - 리드타임: formatLeadTime 헬퍼 신설 — 좌측 상품정보·협상 결과 요약 카드에 "일" 접미 표시 - 인디케이터: init 의 session_status 를 스토어에 적재, 협상중(IN_PROGRESS) 세션에서만 표시 - 사이드바 정보 영역 의미 없는 가로 스크롤 제거 (shrink 금지 컬럼 + overflow-x 자동 계산이 원인) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
118 lines
4.8 KiB
TypeScript
118 lines
4.8 KiB
TypeScript
import { numberToKorean } from '@/features/chat/lib/koreanNumber'
|
|
import { formatLeadTime } from '@/features/chat/lib/format'
|
|
import type { ChatSummary } from '@/features/chat/types'
|
|
|
|
// 협상 결과 요약 카드
|
|
export function Summary({ data }: { data: ChatSummary }) {
|
|
return (
|
|
<div className="flex flex-col w-full p-10 bg-neutral-00 rounded-[28px] gap-4">
|
|
<h1 className="headline-3 text-neutral-90">협상 결과 요약</h1>
|
|
<div className="body-1-read-r">
|
|
협상이 완료되어 아래와 같이 결과를 요약하오니 다시 한 번 최종 확인 부탁 드립니다. 최종 확인 후 협상 결과에 대한
|
|
수정 변경은 불가함을 안내 드립니다.
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<div className="body-1-read-r">협상 개시 시간 : {formatKoreanDateTime(data.nego_start_date)}</div>
|
|
<div className="body-1-read-r">협상 종료 시간 : {formatKoreanDateTime(data.nego_end_date)}</div>
|
|
<div className="body-1-read-r">
|
|
우선협상 대상자 : {data.supplier_name || '-'} ({data.supplier_manager_phone || '-'}){' '}
|
|
{data.supplier_manager_email || '-'}
|
|
</div>
|
|
<div className="body-1-read-r">협상 상세 내역</div>
|
|
</div>
|
|
<div className="flex flex-col gap-4">
|
|
<DetailText title="상품 코드" value={data.item_code || '-'} />
|
|
<DetailText title="상품 명" value={data.item_name || '-'} />
|
|
<DetailText title="모델 명" value={data.item_model || '-'} />
|
|
<DetailText title="제품 규격" value={data.item_spec || '-'} />
|
|
<DetailText title="최소 주문" value={data.item_moq || '-'} />
|
|
<DetailText title="배송 형태" value={data.item_delivery_type || '-'} />
|
|
<DetailText title="배송 리드타임" value={formatLeadTime(data.item_lead_time) || '-'} />
|
|
<PriceText price={data.final_price} isVAT={data.item_isVAT} />
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<div className="body-1-read-r">
|
|
공급 계약 기간: 협상 완료일로부터 1년 ({addOneYear(data.nego_end_date)})까지
|
|
</div>
|
|
<div className="body-1-read-r">
|
|
담당 MD: {data.md_name || '-'} ({data.md_phone_number || '-'}) {data.md_email || '-'}
|
|
</div>
|
|
</div>
|
|
<div className="body-1-read-b">상기 내용에 이상이 없으며 최종 협상 결과에 동의하여 이를 승인합니다.</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ISO 시각 → 한국시(KST) 기준 부분값. 백엔드는 UTC(ISO)로 내려주므로 표시 시 KST 로 변환한다.
|
|
function kstParts(iso: string): Record<string, string> | null {
|
|
if (!iso) return null
|
|
const d = new Date(iso)
|
|
if (isNaN(d.getTime())) return null
|
|
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
timeZone: 'Asia/Seoul',
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false,
|
|
}).formatToParts(d)
|
|
const out: Record<string, string> = {}
|
|
for (const p of parts) out[p.type] = p.value
|
|
return out
|
|
}
|
|
|
|
// "YYYY년 MM월 dd일 HH시 MM분 SS초" (KST)
|
|
function formatKoreanDateTime(iso: string): string {
|
|
const p = kstParts(iso)
|
|
if (!p) return '-'
|
|
return `${p.year}년 ${p.month}월 ${p.day}일 ${p.hour}시 ${p.minute}분 ${p.second}초`
|
|
}
|
|
|
|
// 협상 종료 시각 + 1년 → "YYYY년 MM월 dd일" (공급 계약 만료일)
|
|
function addOneYear(iso: string): string {
|
|
const p = kstParts(iso)
|
|
if (!p) return '-'
|
|
return `${parseInt(p.year) + 1}년 ${p.month}월 ${p.day}일`
|
|
}
|
|
|
|
function DetailText({ title, value }: { title: string; value: string }) {
|
|
return (
|
|
<div className="flex items-start gap-2">
|
|
<Dot />
|
|
<div className="flex-1 whitespace-pre-wrap">
|
|
<span className="body-1-read-b">{title} : </span>
|
|
<span className="body-1-read-r">{value}</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PriceText({ price, isVAT }: { price: number; isVAT: boolean }) {
|
|
const numberString = price.toLocaleString()
|
|
return (
|
|
<div className="flex items-start gap-2">
|
|
<Dot />
|
|
<div className="flex flex-1">
|
|
<div className="body-1-read-b flex flex-shrink-0">최종 협의 가격 :</div>
|
|
<div className="flex flex-1 flex-wrap">
|
|
<span className="body-1-read-b text-negative break-keep"> {numberString}원</span>
|
|
<span className="body-1-read-b text-neutral-90 break-keep">
|
|
({numberToKorean(parseInt(numberString.replace(/,/g, '')))}원)
|
|
</span>
|
|
<span className="body-1-read-b text-neutral-90 break-keep"> {isVAT ? 'VAT포함' : 'VAT별도'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Dot() {
|
|
return (
|
|
<div className="flex justify-end items-center w-[26px] h-[30px]">
|
|
<p className="body-1-read-r">•</p>
|
|
</div>
|
|
)
|
|
}
|