import { useState } from 'react' import { toast } from 'sonner' import { useMeQuery, useSaveExtraInfoMutation, getApiErrorMessage } from '@/apis' import type { SessionField } from '@/apis/auth/auth.type' import { useChatStore } from '@/features/chat/stores/useChatStore' import { useChatInitStore } from '@/features/chat/stores/useChatInitStore' // 타결 요약의 '동의' 단계 하단 액션바 = 부가정보 입력 + 동의를 한 자리에서. // 다른 입력(가격·배송)과 같은 자리에서 받는다. proceedText = 동의 문구(누르면 저장 후 그 텍스트로 협상 종료). // 필드 정의(session_fields)가 없으면 폼 없이 동의 버튼만 둔다. 저장 후에도 세션 목록에서 수정할 수 있다. export function ExtraInfoBar({ proceedText }: { proceedText: string }) { const sessionId = useChatStore((s) => s.sessionId) const sendMessage = useChatStore((s) => s.sendMessage) const { data: user } = useMeQuery() const fields: SessionField[] = user?.sessionFields ?? [] const save = useSaveExtraInfoMutation() const existing = useChatInitStore((s) => s.custom) // 기존 입력값(재진입 프리필) const setInitData = useChatInitStore((s) => s.setInitData) // 의견은 session_fields 와 무관한 공통 필드 — 타결·결렬 모두 항상 받는다. custom.opinion 에 저장. const [opinion, setOpinion] = useState(null) const opinionValue = opinion ?? String(existing?.opinion ?? '') // 사용자가 건드린 값만 state 로 두고, 나머지는 기존값/기본값에서 렌더마다 파생한다(초기화 effect 불필요). const [overrides, setOverrides] = useState>({}) const values: Record = {} for (const f of fields) values[f.key] = overrides[f.key] ?? existing?.[f.key] ?? (f.type === 'boolean' ? false : '') const proceed = () => sendMessage(proceedText) const set = (key: string, value: unknown) => setOverrides((v) => ({ ...v, [key]: value })) // 저장 성공 후에만 동의(협상 종료)로 넘어간다 — 저장 실패 시 화면 유지. const handleSaveAndProceed = () => { if (save.isPending) return const custom: Record = {} for (const f of fields) { const v = values[f.key] if (f.type === 'boolean') custom[f.key] = !!v else if (v !== '' && v != null) custom[f.key] = f.type === 'number' ? Number(v) : v } if (opinionValue.trim()) custom.opinion = opinionValue.trim() if (Object.keys(custom).length === 0) { proceed(); return } // 입력 없음 → 저장 생략하고 종료 save.mutate( { sessionId, request: { custom } }, { onSuccess: () => { setInitData({ custom: { ...existing, ...custom } }) // 요약 카드가 방금 입력값을 즉시 반영하도록 store 갱신 proceed() }, onError: (error) => toast.error(getApiErrorMessage(error, '부가정보 저장에 실패했습니다.')), }, ) } return (

아래 정보를 입력하고 협상을 마무리해 주세요.

{/* overflow-y-auto 는 overflow-x 도 auto 로 만들어(스펙상) 인풋 focus ring 을 좌우로 잘라낸다. p-1 로 링 여백을 주고 -m-1 로 원래 정렬(버튼과 좌우폭)을 유지한다. */}
{fields.map((f) => (
{f.type === 'boolean' ? ( ) : f.type === 'select' ? ( ) : ( set(f.key, e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !e.nativeEvent.isComposing && handleSaveAndProceed()} className="h-10 w-full rounded-xl border border-border bg-white px-3 text-sm outline-none focus:border-brand-600 focus:ring-1 focus:ring-brand-600" placeholder={f.label} /> )}
))} {/* 의견 — 타결·결렬 공통 필드(IMK #18). custom.opinion 저장. */}