"use client" import * as React from "react" import { useState } from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { toast } from "sonner" import { Check, ChevronsUpDown, File, Upload, X } from "lucide-react" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { ProcurementRfqsView } from "@/db/schema" import { addVendorToRfq } from "@/lib/procurement-rfqs/services" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { cn } from "@/lib/utils" import { ScrollArea } from "@/components/ui/scroll-area" // 필수 필드를 위한 커스텀 레이블 컴포넌트 const RequiredLabel = ({ children }: { children: React.ReactNode }) => ( {children} * ); // 폼 유효성 검증 스키마 const vendorFormSchema = z.object({ vendorId: z.string().min(1, "벤더를 선택해주세요"), currency: z.string().min(1, "통화를 선택해주세요"), paymentTermsCode: z.string().min(1, "지불 조건을 선택해주세요"), incotermsCode: z.string().min(1, "인코텀즈를 선택해주세요"), incotermsDetail: z.string().optional(), deliveryDate: z.string().optional(), taxCode: z.string().optional(), placeOfShipping: z.string().optional(), placeOfDestination: z.string().optional(), materialPriceRelatedYn: z.boolean().default(false), }) type VendorFormValues = z.infer interface AddVendorDialogProps { open: boolean onOpenChange: (open: boolean) => void selectedRfq: ProcurementRfqsView | null // 벤더 및 기타 옵션 데이터를 prop으로 받음 vendors?: { id: number; vendorName: string; vendorCode: string }[] currencies?: { code: string; name: string }[] paymentTerms?: { code: string; description: string }[] incoterms?: { code: string; description: string }[] onSuccess?: () => void existingVendorIds?: number[] } export function AddVendorDialog({ open, onOpenChange, selectedRfq, vendors = [], currencies = [], paymentTerms = [], incoterms = [], onSuccess, existingVendorIds = [], // 기본값 빈 배열 }: AddVendorDialogProps) { const availableVendors = React.useMemo(() => { return vendors.filter(vendor => !existingVendorIds.includes(vendor.id)); }, [vendors, existingVendorIds]); // 파일 업로드 상태 관리 const [attachments, setAttachments] = useState([]) const [isSubmitting, setIsSubmitting] = useState(false) // 벤더 선택을 위한 팝오버 상태 const [vendorOpen, setVendorOpen] = useState(false) const form = useForm({ resolver: zodResolver(vendorFormSchema), defaultValues: { vendorId: "", currency: "", paymentTermsCode: "", incotermsCode: "", incotermsDetail: "", deliveryDate: "", taxCode: "", placeOfShipping: "", placeOfDestination: "", materialPriceRelatedYn: false, }, }) // 폼 제출 핸들러 async function onSubmit(values: VendorFormValues) { if (!selectedRfq) { toast.error("선택된 RFQ가 없습니다") return } try { setIsSubmitting(true) // FormData 생성 const formData = new FormData() formData.append("rfqId", selectedRfq.id.toString()) // 폼 데이터 추가 Object.entries(values).forEach(([key, value]) => { formData.append(key, value.toString()) }) // 첨부파일 추가 attachments.forEach((file, index) => { formData.append(`attachment-${index}`, file) }) // 서버 액션 호출 const result = await addVendorToRfq(formData) if (result.success) { toast.success("벤더가 성공적으로 추가되었습니다") onOpenChange(false) form.reset() setAttachments([]) onSuccess?.() } else { toast.error(result.message || "벤더 추가 중 오류가 발생했습니다") } } catch (error) { console.error("벤더 추가 오류:", error) toast.error("벤더 추가 중 오류가 발생했습니다") } finally { setIsSubmitting(false) } } // 파일 업로드 핸들러 const handleFileUpload = (event: React.ChangeEvent) => { if (event.target.files && event.target.files.length > 0) { const newFiles = Array.from(event.target.files) setAttachments((prev) => [...prev, ...newFiles]) } } // 파일 삭제 핸들러 const handleRemoveFile = (index: number) => { setAttachments((prev) => prev.filter((_, i) => i !== index)) } return ( {/* 커스텀 DialogContent - 고정 헤더, 스크롤 가능한 콘텐츠, 고정 푸터 */} {/* 고정 헤더 */} 벤더 추가 {selectedRfq ? ( <> {selectedRfq.rfqCode} RFQ에 벤더를 추가합니다. > ) : ( "RFQ에 벤더를 추가합니다." )} {/* 스크롤 가능한 콘텐츠 영역 */} {/* 검색 가능한 벤더 선택 필드 */} ( 벤더 {field.value ? vendors.find((vendor) => String(vendor.id) === field.value) ? `${vendors.find((vendor) => String(vendor.id) === field.value)?.vendorName} (${vendors.find((vendor) => String(vendor.id) === field.value)?.vendorCode})` : "벤더를 선택하세요" : "벤더를 선택하세요"} 검색 결과가 없습니다 {availableVendors.length > 0 ? ( availableVendors.map((vendor) => ( { form.setValue("vendorId", String(vendor.id), { shouldValidate: true, }) setVendorOpen(false) }} > {vendor.vendorName} ({vendor.vendorCode}) )) ) : ( 추가 가능한 벤더가 없습니다 )} )} /> ( 통화 {currencies.map((currency) => ( {currency.name} ({currency.code}) ))} )} /> ( 지불 조건 {paymentTerms.map((term) => ( {term.description} ))} )} /> ( 인코텀즈 {incoterms.map((incoterm) => ( {incoterm.description} ))} )} /> {/* 나머지 필드들은 동일하게 유지 */} ( 인코텀즈 세부사항 )} /> ( 납품 예정일 )} /> ( 세금 코드 )} /> ( 선적지 )} /> ( 도착지 )} /> ( 하도급대금 연동제 여부 )} /> {/* 파일 업로드 섹션 */} 첨부 파일 클릭하여 파일 업로드 또는 파일을 끌어 놓으세요 PDF, DOCX, XLSX, JPG, PNG (최대 10MB) {/* 업로드된 파일 목록 */} {attachments.length > 0 && ( 업로드된 파일 {attachments.map((file, index) => ( {file.name} ({(file.size / 1024).toFixed(1)} KB) handleRemoveFile(index)} > ))} )} {/* 고정 푸터 */} onOpenChange(false)} disabled={isSubmitting} > 취소 {isSubmitting ? "처리 중..." : "벤더 추가"} ) }
클릭하여 파일 업로드 또는 파일을 끌어 놓으세요
PDF, DOCX, XLSX, JPG, PNG (최대 10MB)