125 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
}
|