// add-initial-rfq-dialog.tsx "use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { Plus, Check, ChevronsUpDown, Search, Building, CalendarIcon } from "lucide-react" import { toast } from "sonner" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Checkbox } from "@/components/ui/checkbox" import { cn, formatDate } from "@/lib/utils" import { addInitialRfqRecord, getIncotermsForSelection, getVendorsForSelection } from "../service" import { Calendar } from "@/components/ui/calendar" // Initial RFQ 추가 폼 스키마 const addInitialRfqSchema = z.object({ vendorId: z.number({ required_error: "벤더를 선택해주세요.", }), initialRfqStatus: z.enum(["DRAFT", "Init. RFQ Sent", "S/L Decline", "Init. RFQ Answered"], { required_error: "초기 RFQ 상태를 선택해주세요.", }).default("DRAFT"), dueDate: z.date({ required_error: "마감일을 선택해주세요.", }), validDate: z.date().optional(), incotermsCode: z.string().optional(), gtc: z.string().optional(), gtcValidDate: z.string().optional(), classification: z.string().optional(), sparepart: z.string().optional(), shortList: z.boolean().default(false), returnYn: z.boolean().default(false), cpRequestYn: z.boolean().default(false), prjectGtcYn: z.boolean().default(false), returnRevision: z.number().default(0), }) type AddInitialRfqFormData = z.infer interface Vendor { id: number vendorName: string vendorCode: string country: string status: string } interface AddInitialRfqDialogProps { rfqId: number onSuccess?: () => void } export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogProps) { const [open, setOpen] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) const [vendors, setVendors] = React.useState([]) const [vendorsLoading, setVendorsLoading] = React.useState(false) const [vendorSearchOpen, setVendorSearchOpen] = React.useState(false) const [incoterms, setIncoterms] = React.useState([]) const [incotermsLoading, setIncotermsLoading] = React.useState(false) const [incotermsSearchOpen, setIncotermsSearchOpen] = React.useState(false) const form = useForm({ resolver: zodResolver(addInitialRfqSchema), defaultValues: { initialRfqStatus: "DRAFT", shortList: false, returnYn: false, cpRequestYn: false, prjectGtcYn: false, returnRevision: 0, }, }) // 벤더 목록 로드 const loadVendors = React.useCallback(async () => { setVendorsLoading(true) try { const vendorList = await getVendorsForSelection() setVendors(vendorList) } catch (error) { console.error("Failed to load vendors:", error) toast.error("벤더 목록을 불러오는데 실패했습니다.") } finally { setVendorsLoading(false) } }, []) // Incoterms 목록 로드 const loadIncoterms = React.useCallback(async () => { setIncotermsLoading(true) try { const incotermsList = await getIncotermsForSelection() setIncoterms(incotermsList) } catch (error) { console.error("Failed to load incoterms:", error) toast.error("Incoterms 목록을 불러오는데 실패했습니다.") } finally { setIncotermsLoading(false) } }, []) // 다이얼로그 열릴 때 벤더 목록 로드 React.useEffect(() => { if (open) { if (vendors.length === 0) { loadVendors() } if (incoterms.length === 0) { loadIncoterms() } } }, [open, vendors.length, incoterms.length, loadVendors, loadIncoterms]) // 다이얼로그 닫기 핸들러 const handleOpenChange = (newOpen: boolean) => { if (!newOpen && !isSubmitting) { form.reset() } setOpen(newOpen) } // 폼 제출 const onSubmit = async (data: AddInitialRfqFormData) => { setIsSubmitting(true) try { const result = await addInitialRfqRecord({ ...data, rfqId, }) if (result.success) { toast.success(result.message || "초기 RFQ가 성공적으로 추가되었습니다.") form.reset() handleOpenChange(false) onSuccess?.() } else { toast.error(result.message || "초기 RFQ 추가에 실패했습니다.") } } catch (error) { console.error("Submit error:", error) toast.error("초기 RFQ 추가 중 오류가 발생했습니다.") } finally { setIsSubmitting(false) } } // 선택된 벤더 정보 const selectedVendor = vendors.find(vendor => vendor.id === form.watch("vendorId")) const selectedIncoterm = incoterms.find(incoterm => incoterm.code === form.watch("incotermsCode")) return ( 초기 RFQ 추가 새로운 벤더를 대상으로 하는 초기 RFQ를 추가합니다.
{/* 벤더 선택 */} ( 벤더 선택 * 검색 결과가 없습니다. {vendors.map((vendor) => ( { field.onChange(vendor.id) setVendorSearchOpen(false) }} >
{vendor.vendorName}
{vendor.vendorCode} • {vendor.country}
))}
)} /> {/* 날짜 필드들 */}
( 견적 마감일 date < new Date() || date < new Date("1900-01-01") } initialFocus /> )} /> ( 견적 유효일 date < new Date() || date < new Date("1900-01-01") } initialFocus /> )} />
{/* Incoterms 및 GTC */}
( Incoterms * 검색 결과가 없습니다. {incoterms.map((incoterm) => ( { field.onChange(vendor.id) setVendorSearchOpen(false) }} >
{incoterm.code} {incoterm.description}
))}
)} />
{/* GTC 정보 */}
( GTC )} /> ( GTC 유효일 )} />
{/* 분류 정보 */}
( 선급 )} /> ( Spare part )} />
) }