"use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { toast } from "sonner" import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" import { useSession } from "next-auth/react" import { createRfqSchema, type CreateRfqSchema, RfqType } from "../validations" import { createRfq, generateNextRfqCode, getBudgetaryRfqs } from "../service" import { ProjectSelector } from "@/components/ProjectSelector" import { type Project } from "../service" import { ParentRfqSelector } from "./ParentRfqSelector" import { EstimateProjectSelector } from "@/components/BidProjectSelector" // 부모 RFQ 정보 타입 정의 interface ParentRfq { id: number; rfqCode: string; description: string | null; rfqType: RfqType; projectId: number | null; projectCode: string | null; projectName: string | null; } interface AddRfqDialogProps { rfqType?: RfqType; } export function AddRfqDialog({ rfqType = RfqType.PURCHASE }: AddRfqDialogProps) { const [open, setOpen] = React.useState(false) const { data: session, status } = useSession() const [parentRfqs, setParentRfqs] = React.useState([]) const [isLoadingParents, setIsLoadingParents] = React.useState(false) const [selectedParentRfq, setSelectedParentRfq] = React.useState(null) const [isLoadingRfqCode, setIsLoadingRfqCode] = React.useState(false) // Get the user ID safely, ensuring it's a valid number const userId = React.useMemo(() => { const id = session?.user?.id ? Number(session.user.id) : null; return id; }, [session, status]); // RfqType에 따른 타이틀 생성 const getTitle = () => { switch (rfqType) { case RfqType.PURCHASE: return "Purchase RFQ"; case RfqType.BUDGETARY: return "Budgetary RFQ"; case RfqType.PURCHASE_BUDGETARY: return "Purchase Budgetary RFQ"; default: return "RFQ"; } }; // RfqType 설명 가져오기 const getTypeDescription = () => { switch (rfqType) { case RfqType.PURCHASE: return "실제 구매 발주 전에 가격을 요청"; case RfqType.BUDGETARY: return "기술영업 단계에서 입찰가 산정을 위한 견적 요청"; case RfqType.PURCHASE_BUDGETARY: return "프로젝트 수주 후, 공식 입찰 전 예산 책정을 위한 가격 요청"; default: return ""; } }; // RHF + Zod const form = useForm({ resolver: zodResolver(createRfqSchema), defaultValues: { rfqCode: "", description: "", projectId: undefined, parentRfqId: undefined, dueDate: new Date(), status: "DRAFT", rfqType: rfqType, // Don't set createdBy yet - we'll set it when the form is submitted createdBy: undefined, }, }); // Update form values when session loads React.useEffect(() => { if (status === "authenticated" && userId) { form.setValue("createdBy", userId); } }, [status, userId, form]); // 다이얼로그가 열릴 때 자동으로 RFQ 코드 생성 React.useEffect(() => { if (open) { const generateRfqCode = async () => { setIsLoadingRfqCode(true); try { // 서버 액션 호출 const result = await generateNextRfqCode(rfqType); if (result.error) { toast.error(`RFQ 코드 생성 실패: ${result.error}`); return; } // 생성된 코드를 폼에 설정 form.setValue("rfqCode", result.code); } catch (error) { console.error("RFQ 코드 생성 오류:", error); toast.error("RFQ 코드 생성에 실패했습니다"); } finally { setIsLoadingRfqCode(false); } }; generateRfqCode(); } }, [open, rfqType, form]); // 현재 RFQ 타입에 따라 선택 가능한 부모 RFQ 타입들 결정 const getParentRfqTypes = (): RfqType[] => { switch (rfqType) { case RfqType.PURCHASE: // PURCHASE는 BUDGETARY와 PURCHASE_BUDGETARY를 부모로 가질 수 있음 return [RfqType.BUDGETARY, RfqType.PURCHASE_BUDGETARY]; case RfqType.PURCHASE_BUDGETARY: // PURCHASE_BUDGETARY는 BUDGETARY만 부모로 가질 수 있음 return [RfqType.BUDGETARY]; default: return []; } }; // 선택 가능한 부모 RFQ 목록 로드 React.useEffect(() => { if ((rfqType === RfqType.PURCHASE || rfqType === RfqType.PURCHASE_BUDGETARY) && open) { const loadParentRfqs = async () => { setIsLoadingParents(true); try { // 현재 RFQ 타입에 따라 선택 가능한, 부모가 될 수 있는 RFQ 타입들 가져오기 const parentTypes = getParentRfqTypes(); // 부모 RFQ 타입이 있을 때만 API 호출 if (parentTypes.length > 0) { const result = await getBudgetaryRfqs({ rfqTypes: parentTypes // 서비스에 rfqTypes 파라미터 추가 필요 }); if ('rfqs' in result) { setParentRfqs(result.rfqs as unknown as ParentRfq[]); } else if ('error' in result) { console.error("부모 RFQ 로드 오류:", result.error); } } } catch (error) { console.error("부모 RFQ 로드 오류:", error); } finally { setIsLoadingParents(false); } }; loadParentRfqs(); } }, [rfqType, open]); // 프로젝트 선택 처리 const handleProjectSelect = (project: Project | null) => { if (project === null) { return; } form.setValue("projectId", project.id); }; const handleBidProjectSelect = (project: Project | null) => { if (project === null) { return; } form.setValue("bidProjectId", project.id); }; // 부모 RFQ 선택 처리 const handleParentRfqSelect = (rfq: ParentRfq | null) => { setSelectedParentRfq(rfq); form.setValue("parentRfqId", rfq?.id); }; async function onSubmit(data: CreateRfqSchema) { // Check if user is authenticated before submitting if (status !== "authenticated" || !userId) { toast.error("사용자 인증이 필요합니다. 다시 로그인해주세요."); return; } // Make sure createdBy is set with the current user ID const submitData = { ...data, createdBy: userId }; console.log("Submitting form data:", submitData); const result = await createRfq(submitData); if (result.error) { toast.error(`에러: ${result.error}`); return; } toast.success("RFQ가 성공적으로 생성되었습니다."); form.reset(); setSelectedParentRfq(null); setOpen(false); } function handleDialogOpenChange(nextOpen: boolean) { if (!nextOpen) { form.reset(); setSelectedParentRfq(null); } setOpen(nextOpen); } // Return a message or disabled state if user is not authenticated if (status === "loading") { return ; } // 타입에 따라 부모 RFQ 선택 필드를 보여줄지 결정 const shouldShowParentRfqSelector = rfqType === RfqType.PURCHASE || rfqType === RfqType.PURCHASE_BUDGETARY; const shouldShowEstimateSelector = rfqType === RfqType.BUDGETARY; // 부모 RFQ 선택기 레이블 및 설명 가져오기 const getParentRfqSelectorLabel = () => { if (rfqType === RfqType.PURCHASE) { return "부모 RFQ (BUDGETARY/PURCHASE_BUDGETARY)"; } else if (rfqType === RfqType.PURCHASE_BUDGETARY) { return "부모 RFQ (BUDGETARY)"; } return "부모 RFQ"; }; const getParentRfqDescription = () => { if (rfqType === RfqType.PURCHASE) { return "BUDGETARY 또는 PURCHASE_BUDGETARY 타입의 RFQ를 부모로 선택할 수 있습니다."; } else if (rfqType === RfqType.PURCHASE_BUDGETARY) { return "BUDGETARY 타입의 RFQ만 부모로 선택할 수 있습니다."; } return ""; }; return ( {/* 모달을 열기 위한 버튼 */} Create New {getTitle()} 새 {getTitle()} 정보를 입력하고 Create 버튼을 누르세요.
{getTypeDescription()}
{/* rfqType - hidden field */} ( )} /> {/* Project Selector */} ( Project {shouldShowEstimateSelector ? : } )} /> {/* Parent RFQ Selector - PURCHASE 또는 PURCHASE_BUDGETARY 타입일 때만 표시 */} {shouldShowParentRfqSelector && ( ( {getParentRfqSelectorLabel()}
{getParentRfqDescription()}
)} /> )} {/* rfqCode - 자동 생성되고 읽기 전용 */} ( RFQ Code
{isLoadingRfqCode && (
)}
RFQ 타입과 현재 날짜를 기준으로 자동 생성됩니다
)} /> {/* description */} ( RFQ Description )} /> {/* dueDate */} ( Due Date { const val = e.target.value if (val) { const date = new Date(val); // 날짜 1일씩 밀리는 문제로 우선 KTC로 입력 // 추후 아래와 같이 수정 // 1. 해당 유저 타임존 값으로 입력 // 2. DB에는 UTC 타임존 값으로 저장 // 3. 출력시 유저별 타임존 값으로 변환해 출력 // 4. 어떤 타임존으로 나오는지도 함께 렌더링 // field.onChange(new Date(val + "T00:00:00")) field.onChange(date); } }} /> )} /> {/* status (Read-only) */} ( Status { }} // Prevent changes /> )} />
) }