"use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { format } from "date-fns" import { CalendarIcon, Plus, Loader2, Eye } from "lucide-react" import { useRouter } from "next/navigation" import { useSession } from "next-auth/react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Calendar } from "@/components/ui/calendar" import { Badge } from "@/components/ui/badge" import { cn } from "@/lib/utils" import { toast } from "sonner" import { ProjectSelector } from "@/components/ProjectSelector" import { createRfqAction, previewNextRfqCode } from "../service" export type Project = { id: number; projectCode: string; projectName: string; } // 클라이언트 폼 스키마 (projectId 필수로 변경) const createRfqSchema = z.object({ projectId: z.number().min(1, "프로젝트를 선택해주세요"), // 필수로 변경 dueDate: z.date({ required_error: "마감일을 선택해주세요", }), picCode: z.string().min(1, "구매 담당자 코드를 입력해주세요"), picName: z.string().optional(), engPicName: z.string().optional(), packageNo: z.string().min(1, "패키지 번호를 입력해주세요"), packageName: z.string().min(1, "패키지명을 입력해주세요"), remark: z.string().optional(), projectCompany: z.string().optional(), projectFlag: z.string().optional(), projectSite: z.string().optional(), }) type CreateRfqFormValues = z.infer interface CreateRfqDialogProps { onSuccess?: () => void; } export function CreateRfqDialog({ onSuccess }: CreateRfqDialogProps) { const [open, setOpen] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false) const [previewCode, setPreviewCode] = React.useState("") const [isLoadingPreview, setIsLoadingPreview] = React.useState(false) const router = useRouter() const { data: session } = useSession() const userId = React.useMemo(() => { return session?.user?.id ? Number(session.user.id) : null; }, [session]); const form = useForm({ resolver: zodResolver(createRfqSchema), defaultValues: { projectId: undefined, dueDate: undefined, picCode: "", picName: "", engPicName: "", packageNo: "", packageName: "", remark: "", projectCompany: "", projectFlag: "", projectSite: "", }, }) // picCode 변경 시 미리보기 업데이트 const watchedPicCode = form.watch("picCode") React.useEffect(() => { if (watchedPicCode && watchedPicCode.length > 0) { setIsLoadingPreview(true) const timer = setTimeout(async () => { try { const preview = await previewNextRfqCode(watchedPicCode) setPreviewCode(preview) } catch (error) { console.error("미리보기 오류:", error) setPreviewCode("") } finally { setIsLoadingPreview(false) } }, 500) // 500ms 디바운스 return () => clearTimeout(timer) } else { setPreviewCode("") } }, [watchedPicCode]) // 다이얼로그 열림/닫힘 처리 및 폼 리셋 const handleOpenChange = (newOpen: boolean) => { setOpen(newOpen) // 다이얼로그가 닫힐 때 폼과 상태 초기화 if (!newOpen) { form.reset() setPreviewCode("") setIsLoadingPreview(false) } } const handleCancel = () => { form.reset() setOpen(false) } const onSubmit = async (data: CreateRfqFormValues) => { if (!userId) { toast.error("로그인이 필요합니다") return } setIsLoading(true) try { // 서버 액션 호출 - Date 객체를 직접 전달 const result = await createRfqAction({ projectId: data.projectId, // 이제 항상 값이 있음 dueDate: data.dueDate, // Date 객체 직접 전달 picCode: data.picCode, picName: data.picName || "", engPicName: data.engPicName || "", packageNo: data.packageNo, packageName: data.packageName, remark: data.remark || "", projectCompany: data.projectCompany || "", projectFlag: data.projectFlag || "", projectSite: data.projectSite || "", createdBy: userId, updatedBy: userId, }) if (result.success) { toast.success(result.message, { description: `RFQ 코드: ${result.data?.rfqCode}`, }) // 다이얼로그 닫기 (handleOpenChange에서 리셋 처리됨) setOpen(false) // 성공 콜백 실행 if (onSuccess) { onSuccess() } } else { toast.error(result.error || "RFQ 생성에 실패했습니다") } } catch (error) { console.error('RFQ 생성 오류:', error) toast.error("RFQ 생성에 실패했습니다", { description: "알 수 없는 오류가 발생했습니다", }) } finally { setIsLoading(false) } } const handleProjectSelect = (project: Project | null) => { if (project === null) { form.setValue("projectId", undefined as any); // 타입 에러 방지 return; } form.setValue("projectId", project.id); }; return ( {/* 고정된 헤더 */} 새 RFQ 생성 새로운 RFQ를 생성합니다. 필수 정보를 입력해주세요. {/* 스크롤 가능한 컨텐츠 영역 */}
{/* 프로젝트 선택 (필수) */} ( 프로젝트 * )} /> {/* 마감일 (필수) */} ( 마감일 * date < new Date() || date < new Date("1900-01-01") } initialFocus /> )} /> {/* 구매 담당자 코드 (필수) + 미리보기 */} ( 구매 담당자 코드 *
{/* RFQ 코드 미리보기 */} {previewCode && (
생성될 RFQ 코드: {isLoadingPreview ? "생성 중..." : previewCode}
)}
RFQ 코드는 N + 담당자코드 + 시리얼번호(5자리) 형식으로 자동 생성됩니다
)} /> {/* 담당자 정보 (두 개 나란히) */}

담당자 정보

{/* 구매 담당자 */} ( 구매 담당자명 )} /> {/* 설계 담당자 */} ( 설계 담당자명 )} />
{/* 패키지 정보 (두 개 나란히) - 필수 */}

패키지 정보

{/* 패키지 번호 (필수) */} ( 패키지 번호 * )} /> {/* 패키지명 (필수) */} ( 패키지명 * )} />
{/* 프로젝트 상세 정보 */}

프로젝트 상세 정보

( 프로젝트 회사 )} />
( 프로젝트 플래그 )} /> ( 프로젝트 사이트 )} />
{/* 비고 */} ( 비고