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

125 lines
4.4 KiB
TypeScript

import { useState } from 'react'
import { X } from 'lucide-react'
import { Modal } from '@/components'
import { cn } from '@/lib'
const REASONS = ['단종', '품절', '기타'] as const
export interface RejectPopupProps {
onClose: () => void
/** 최종 거부 사유 (프리셋 라벨 또는 기타 입력 텍스트) */
onSubmit: (reason: string) => void
}
// 거부 사유 입력 팝업 (단종/품절/기타).
export function RejectPopup({ onClose, onSubmit }: RejectPopupProps) {
const [selectedReason, setSelectedReason] = useState<string | null>(null)
const [customReason, setCustomReason] = useState('')
const [showError, setShowError] = useState(false)
const isEtcOpen = selectedReason === '기타'
const isSubmitDisabled = !selectedReason || (isEtcOpen && !customReason.trim())
const handleReasonClick = (reason: string) => {
setShowError(false)
if (selectedReason === reason) {
setSelectedReason(null)
setCustomReason('')
} else {
setSelectedReason(reason)
if (reason !== '기타') setCustomReason('')
}
}
const handleSubmit = () => {
if (isEtcOpen && !customReason.trim()) {
setShowError(true)
return
}
if (isSubmitDisabled || !selectedReason) return
onSubmit(isEtcOpen ? customReason.trim() : selectedReason)
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">
<h3 className="text-base font-bold text-neutral-90">거부 사유를 입력해주세요</h3>
<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">
<div className="grid grid-cols-3 gap-2">
{REASONS.map((reason) => (
<button
key={reason}
type="button"
onClick={() => handleReasonClick(reason)}
className={cn(
'h-11 rounded-xl border text-sm font-bold transition-all active:scale-[0.98]',
selectedReason === reason
? 'border-brand-600 bg-brand-light text-brand-600'
: 'border-border bg-white text-neutral-70 hover:bg-neutral-10',
)}
>
{reason}
</button>
))}
</div>
{isEtcOpen && (
<div className="animate-fade-in">
<textarea
placeholder="사유를 입력하여 주십시오"
value={customReason}
onChange={(e) => {
setCustomReason(e.target.value)
setShowError(false)
}}
rows={3}
className={cn(
'w-full resize-none rounded-xl border bg-white p-3 text-sm text-neutral-90 outline-none transition-all',
'placeholder:text-neutral-50 focus:ring-1',
showError
? 'border-destructive focus:border-destructive focus:ring-destructive'
: 'border-border focus:border-brand-600 focus:ring-brand-600',
)}
/>
{showError && <p className="mt-1.5 text-xs font-medium text-destructive">기타 사유를 입력해주세요</p>}
</div>
)}
</div>
{/* 푸터 */}
<div className="flex gap-2 border-t border-border p-4">
<button
type="button"
onClick={onClose}
className="h-11 flex-1 rounded-xl border border-border bg-white text-sm font-bold text-neutral-70 transition-all hover:bg-neutral-10 active:scale-[0.98]"
>
취소
</button>
<button
type="button"
onClick={handleSubmit}
disabled={isSubmitDisabled}
className="h-11 flex-1 rounded-xl bg-brand-600 text-sm font-bold text-white shadow-sm transition-all hover:bg-brand-700 active:scale-[0.98] disabled:opacity-40"
>
거부 처리
</button>
</div>
</div>
</Modal>
)
}