o2o-negosium-original/frontend/src/features/chat/components/templates/RejectCM.tsx

121 lines
5.7 KiB
TypeScript

import { useState, useMemo } from 'react'
import { AlertTriangle } from 'lucide-react'
import { cn } from '@/lib'
import { numberToKorean } from '@/features/chat/lib/koreanNumber'
import { findRestoreScript, extractPart } from '@/features/chat/lib/rejectForm'
import { SelectRadio, SubmitButton } from '@/features/chat/components/templates/rejectControls'
import { useChatStore } from '@/features/chat/stores/useChatStore'
import { useChatInitStore } from '@/features/chat/stores/useChatInitStore'
const MAX_PRICE = 999999999999999
// 재견적 실패 시: 최종 공급 희망 가격 + 배송 형태 입력 폼
export function RejectCM() {
const isLoading = useChatStore((s) => s.isLoading)
const sendMessage = useChatStore((s) => s.sendMessage)
const messages = useChatStore((s) => s.messages)
const item_vat_yn = useChatInitStore((s) => s.item_vat_yn)
const isVAT = item_vat_yn === 'VAT별도'
// 폼 값은 이전 제출 내역에서 1회만 복원 (setter 미사용 = lazy 초기화 전용)
const restored = useState(() => findRestoreScript(messages, 'rejectCM'))[0]
const [price, setPrice] = useState(() => extractPart(restored, '공급희망가격-'))
const [selectedReason, setSelectedReason] = useState(() => extractPart(restored, '배송형태-'))
const [priceErrorMessage, setPriceErrorMessage] = useState('')
const [radioErrorMessage, setRadioErrorMessage] = useState('')
// 제출 완료 여부는 messages 변화에 반응해야 함 (제출 후 갱신 반영)
const isSubmitted = useMemo(() => Boolean(findRestoreScript(messages, 'rejectCM')), [messages])
const isValid = Boolean(price && parseInt(price) > 0) && Boolean(selectedReason)
const handlePriceChange = (value: string) => {
const numeric = value.replace(/\D/g, '')
if (numeric && parseInt(numeric) > MAX_PRICE) return
setPrice(numeric)
if (numeric) setPriceErrorMessage('')
}
const koreanPrice = (() => {
if (!price) return '영'
const n = parseInt(price)
return Number.isNaN(n) || n === 0 ? '영' : numberToKorean(n)
})()
const handleSubmit = () => {
setPriceErrorMessage(!price || parseInt(price) === 0 ? '가격을 입력해주세요.' : '')
setRadioErrorMessage(!selectedReason ? '배송 형태를 선택해주세요.' : '')
if (isValid && !isLoading && !isSubmitted) {
sendMessage(`공급희망가격-${price}, 배송형태-${selectedReason}`, 'text')
}
}
const isDisabled = isLoading || isSubmitted
return (
<div className="flex flex-col w-full p-8 bg-white rounded-2xl border border-border shadow-sm gap-2 transition-all duration-300">
<div className="flex w-full items-center justify-center gap-2 rounded-xl bg-[#FFF1F1] px-4 py-3 text-sm font-bold text-[#E5484D]">
<AlertTriangle className="size-5 shrink-0" />
최종 공급 희망 가격 입력
</div>
<div className="py-2 text-sm leading-relaxed text-neutral-70 break-keep">
최종 공급 희망 가격과 배송 형태를 입력하여 주시기 바랍니다.
<br />
가격 검토를 통해 경쟁력 있는 가격일 경우 다시 협상에 참여할 기회가 주어지오니 신중한 가격 제안 부탁드립니다.
</div>
<div className="flex flex-col w-full gap-1">
<div className="flex w-full items-start gap-3">
<div className="w-[100px] shrink-0 pt-2.5 text-sm font-bold text-neutral-90">공급 희망 가격</div>
<div className="flex w-full flex-wrap items-center gap-2 pt-2">
<div className="flex items-center gap-2">
<input
type="text"
className={cn(
'h-10 max-w-[200px] rounded-xl border px-3 text-right text-sm outline-none transition-all placeholder:text-neutral-50',
isDisabled
? 'cursor-not-allowed border-border bg-neutral-10 text-neutral-50'
: 'border-border bg-white text-neutral-90 focus:border-brand-600 focus:ring-1 focus:ring-brand-600',
)}
value={price ? parseInt(price).toLocaleString() : ''}
onChange={(e) => handlePriceChange(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && !e.nativeEvent.isComposing && handleSubmit()}
placeholder="0"
disabled={isDisabled}
/>
<div className="whitespace-nowrap text-sm text-neutral-70">원{isVAT && '(VAT 별도)'}</div>
</div>
<div className="whitespace-nowrap text-sm text-neutral-50">[{koreanPrice} 원]</div>
</div>
</div>
{priceErrorMessage && <div className="pl-[112px] text-xs font-medium text-negative">{priceErrorMessage}</div>}
</div>
<div className="flex w-full gap-3">
<div className="w-[100px] shrink-0 pt-2 text-sm font-bold text-neutral-90">배송 형태</div>
<div className="flex w-full flex-wrap items-center gap-x-6 gap-y-2">
{['협력사배송', '지정택배배송', '픽업배송'].map((opt) => (
<SelectRadio
key={opt}
text={opt === '협력사배송' ? '협력사 배송' : opt}
value={opt}
name="rejectcm"
selectedValue={selectedReason}
onChange={(v) => {
setSelectedReason(v)
setRadioErrorMessage('')
}}
errorMessage={radioErrorMessage}
disabled={isDisabled}
/>
))}
</div>
</div>
<div className="flex w-full items-center justify-center mt-4">
<SubmitButton isValid={isValid} isLoading={isLoading} isSubmitted={isSubmitted} onSubmit={handleSubmit} />
</div>
</div>
)
}