o2o-negosium-original/frontend/src/features/list/components/ExtraInfoPopup.tsx

137 lines
5.7 KiB
TypeScript

import { useState } from 'react'
import { X } from 'lucide-react'
import { Modal } from '@/components'
import { useMeQuery } from '@/apis'
import type { SessionField } from '@/apis/auth/auth.type'
import type { ListItem } from '../types'
export interface ExtraInfoPopupProps {
target: ListItem
onClose: () => void
/** 부가정보 저장 (custom = {key: value}) */
onSubmit: (custom: Record<string, unknown>) => void
}
// 협상완료 부가정보 입력 팝업. 필드 정의(session_fields)는 회사 설정(/me)에서 오고, 기존 입력값(target.custom)으로 프리필.
export function ExtraInfoPopup({ target, onClose, onSubmit }: ExtraInfoPopupProps) {
const { data: user } = useMeQuery()
const fields: SessionField[] = user?.sessionFields ?? []
const [values, setValues] = useState<Record<string, unknown>>(() => {
const init: Record<string, unknown> = {}
for (const f of fields) init[f.key] = target.custom?.[f.key] ?? (f.type === 'boolean' ? false : '')
return init
})
const [opinion, setOpinion] = useState<string>(() => String(target.custom?.opinion ?? ''))
const set = (key: string, value: unknown) => setValues((v) => ({ ...v, [key]: value }))
const handleSubmit = () => {
// 빈 값은 제외하고 저장(부분 입력 허용)
const out: Record<string, unknown> = {}
for (const f of fields) {
const v = values[f.key]
if (f.type === 'boolean') out[f.key] = !!v
else if (v !== '' && v != null) out[f.key] = f.type === 'number' ? Number(v) : v
}
if (opinion.trim()) out.opinion = opinion.trim()
onSubmit(out)
onClose()
}
return (
<Modal onClose={onClose}>
<div className="w-full max-w-md overflow-hidden rounded-2xl border border-border bg-white shadow-xl animate-scale-in">
<div className="flex items-center justify-between border-b border-border p-5">
<div>
<h3 className="text-base font-bold text-neutral-90">협상완료 부가정보</h3>
<p className="mt-0.5 text-xs text-neutral-60">{target.qt_number} · {target.item_name}</p>
</div>
<button
type="button"
onClick={onClose}
aria-label="닫기"
className="flex size-8 items-center justify-center rounded-full text-neutral-60 hover:bg-neutral-10"
>
<X className="size-4" />
</button>
</div>
<div className="space-y-4 p-5">
{fields.map((f) => (
<div key={f.key} className="space-y-1.5">
<label className="block text-sm font-semibold text-neutral-80">{f.label}</label>
{f.type === 'boolean' ? (
<button
type="button"
role="switch"
aria-checked={!!values[f.key]}
onClick={() => set(f.key, !values[f.key])}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
values[f.key] ? 'bg-brand-600' : 'bg-neutral-30'
}`}
>
<span
className={`inline-block size-5 transform rounded-full bg-white shadow transition-transform ${
values[f.key] ? 'translate-x-5' : 'translate-x-0.5'
}`}
/>
</button>
) : f.type === 'select' ? (
<select
value={String(values[f.key] ?? '')}
onChange={(e) => set(f.key, e.target.value)}
className="h-11 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"
>
<option value="">선택</option>
{(f.options ?? []).map((o) => (
<option key={o} value={o}>{o}</option>
))}
</select>
) : (
<input
type={f.type === 'number' ? 'number' : 'text'}
value={String(values[f.key] ?? '')}
onChange={(e) => set(f.key, e.target.value)}
className="h-11 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}
/>
)}
</div>
))}
<div className="space-y-1.5">
<label className="block text-sm font-semibold text-neutral-80">
의견 <span className="font-normal text-neutral-50">(선택)</span>
</label>
<textarea
value={opinion}
onChange={(e) => setOpinion(e.target.value)}
rows={2}
maxLength={255}
placeholder="추가로 남길 의견"
className="w-full resize-none rounded-xl border border-border bg-white px-3 py-2 text-sm outline-none focus:border-brand-600 focus:ring-1 focus:ring-brand-600"
/>
</div>
</div>
<div className="flex gap-2 border-t border-border p-5">
<button
type="button"
onClick={onClose}
className="h-11 flex-1 rounded-xl border border-border text-sm font-bold text-neutral-70 hover:bg-neutral-10"
>
취소
</button>
<button
type="button"
onClick={handleSubmit}
className="h-11 flex-1 rounded-xl bg-brand-600 text-sm font-bold text-white hover:bg-brand-700 disabled:opacity-40"
>
저장
</button>
</div>
</div>
</Modal>
)
}