From 25b916d040a512cd5248dff319d727ae144d0652 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 15 Sep 2025 03:24:12 +0000 Subject: (최겸) 구매 PCR 개발(po -> pcr, ecc pcr-confirm test 필) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pcr/table/create-pcr-dialog.tsx | 642 ++++++++++++++++++++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 lib/pcr/table/create-pcr-dialog.tsx (limited to 'lib/pcr/table/create-pcr-dialog.tsx') diff --git a/lib/pcr/table/create-pcr-dialog.tsx b/lib/pcr/table/create-pcr-dialog.tsx new file mode 100644 index 00000000..cddb20d3 --- /dev/null +++ b/lib/pcr/table/create-pcr-dialog.tsx @@ -0,0 +1,642 @@ +"use client" + +import * as React from "react" +import { toast } from "sonner" +import { CalendarIcon, Loader2, Plus, Check, ChevronsUpDown } from "lucide-react" +import { Calendar } from "@/components/ui/calendar" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { format } from "date-fns" +import { ko } from "date-fns/locale" + +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" +import { createPcrPo, getVendorsForPcr } from "@/lib/pcr/service" +import { PCR_CHANGE_TYPES } from "@/lib/pcr/types" +import { cn } from "@/lib/utils" + +// PCR 생성 스키마 +const createPcrSchema = z.object({ + changeType: z.string().optional(), + details: z.string().optional(), + project: z.string().optional(), + pcrRequestDate: z.date({ + required_error: "PCR 요청일자를 선택해주세요.", + }), + poContractNumber: z.string().min(1, "PO/계약번호를 입력해주세요."), + revItemNumber: z.string().optional(), + purchaseContractManager: z.string().optional(), + pcrCreator: z.string().optional(), + poContractAmountBefore: z.number().optional(), + poContractAmountAfter: z.number().optional(), + contractCurrency: z.string().optional(), + pcrReason: z.string().optional(), + detailsReason: z.string().optional(), + rejectionReason: z.string().optional(), + pcrResponseDate: z.date().optional(), + vendorId: z.number({ + required_error: "협력업체를 선택해주세요.", + }), +}) + +type CreatePcrFormValues = z.infer + +interface CreatePcrDialogProps { + open?: boolean + onOpenChange?: (open: boolean) => void + isEvcpPage?: boolean + onSuccess?: () => void + currentVendorId?: number // Partners 페이지에서 현재 사용자의 vendorId +} + +interface Vendor { + id: number + vendorName: string + vendorCode: string +} + +export function CreatePcrDialog({ + open, + onOpenChange, + isEvcpPage = false, + onSuccess, + currentVendorId, +}: CreatePcrDialogProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [internalOpen, setInternalOpen] = React.useState(false) + const [vendors, setVendors] = React.useState([]) + const [vendorsLoading, setVendorsLoading] = React.useState(false) + const [vendorSearchOpen, setVendorSearchOpen] = React.useState(false) + + const dialogOpen = open !== undefined ? open : internalOpen + const setDialogOpen = onOpenChange || setInternalOpen + + const form = useForm({ + resolver: zodResolver(createPcrSchema), + defaultValues: { + changeType: "OTHER", + contractCurrency: "KRW", + pcrRequestDate: new Date(), + }, + }) + + const loadVendors = React.useCallback(async () => { + try { + setVendorsLoading(true) + + let result + if (!isEvcpPage && currentVendorId) { + // Partners 페이지: 특정 vendorId만 조회 + result = await getVendorsForPcr({ + vendorId: currentVendorId, + limit: 1 + }) + } else { + // EvcP 페이지: 모든 협력업체 조회 + result = await getVendorsForPcr({ + limit: 1000 + }) + } + + if (result.success) { + setVendors(result.data.map(vendor => ({ + id: vendor.id, + vendorName: vendor.vendorName, + vendorCode: vendor.vendorCode || "", + }))) + + // Partners 페이지에서는 자동으로 해당 협력업체 선택 + if (!isEvcpPage && currentVendorId && result.data.length > 0) { + form.setValue("vendorId", currentVendorId) + } + } else { + toast.error(result.error || "협력업체 목록을 불러오는 중 오류가 발생했습니다.") + } + } catch (error) { + console.error("협력업체 로드 오류:", error) + toast.error("협력업체 목록을 불러오는 중 오류가 발생했습니다.") + } finally { + setVendorsLoading(false) + } + }, [isEvcpPage, currentVendorId, form]) + + // 협력업체 데이터 로드 + React.useEffect(() => { + if (dialogOpen) { + loadVendors() + } + }, [dialogOpen, loadVendors]) + + async function onSubmit(data: CreatePcrFormValues) { + try { + setIsLoading(true) + + const result = await createPcrPo({ + ...data, + pcrApprovalStatus: "PENDING", + contractCurrency: data.contractCurrency || "KRW", + changeType: data.changeType || "OTHER", + }) + + if (result.success) { + toast.success("PCR이 성공적으로 생성되었습니다.") + form.reset() + setDialogOpen(false) + onSuccess?.() + } else { + toast.error(result.error || "PCR 생성에 실패했습니다.") + } + } catch (error) { + console.error("PCR 생성 오류:", error) + toast.error("PCR 생성 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + } + } + + return ( + + + + + + + PCR 생성 + + +
+ + {/* 기본 정보 섹션 */} +
+

기본 정보

+
+ ( + + 변경 구분 + + + + )} + /> + + ( + + PCR 요청일자 * + + + + + + + + + date > new Date() || date < new Date("1900-01-01") + } + initialFocus + /> + + + + + )} + /> +
+ + {/* 협력업체 선택 */} + ( + + 협력업체 * + + + + + + + + + + + {vendorsLoading ? "로딩 중..." : "협력업체를 찾을 수 없습니다."} + + + + {vendors.map((vendor) => ( + { + form.setValue("vendorId", vendor.id) + setVendorSearchOpen(false) + }} + > + +
+ {vendor.vendorName} + {vendor.vendorCode && ( + + 코드: {vendor.vendorCode} + + )} +
+
+ ))} +
+
+
+
+
+ +
+ )} + /> + +
+ ( + + PO/계약번호 * + + + + + + )} + /> + + ( + + Rev./품번 + + + + + + )} + /> +
+ + ( + + 프로젝트 + + + + + + )} + /> + + ( + + 상세 + +