From ff902243a658067fae858a615c0629aa2e0a4837 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 11 Jun 2025 12:18:38 +0000
Subject: (대표님) 20250611 21시 15분 OCR 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/b-rfq/summary-table/add-new-rfq-dialog.tsx | 523 +++++++++++++++++++++++++
 1 file changed, 523 insertions(+)
 create mode 100644 lib/b-rfq/summary-table/add-new-rfq-dialog.tsx

(limited to 'lib/b-rfq/summary-table/add-new-rfq-dialog.tsx')

diff --git a/lib/b-rfq/summary-table/add-new-rfq-dialog.tsx b/lib/b-rfq/summary-table/add-new-rfq-dialog.tsx
new file mode 100644
index 00000000..2333d9cf
--- /dev/null
+++ b/lib/b-rfq/summary-table/add-new-rfq-dialog.tsx
@@ -0,0 +1,523 @@
+"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<typeof createRfqSchema>
+
+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<string>("")
+  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<CreateRfqFormValues>({
+    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 (
+    <Dialog open={open} onOpenChange={handleOpenChange}>
+      <DialogTrigger asChild>
+        <Button size="sm" variant="outline">
+          <Plus className="mr-2 h-4 w-4" />
+          새 RFQ
+        </Button>
+      </DialogTrigger>
+      <DialogContent className="max-w-3xl h-[90vh] flex flex-col">
+        {/* 고정된 헤더 */}
+        <DialogHeader className="flex-shrink-0">
+          <DialogTitle>새 RFQ 생성</DialogTitle>
+          <DialogDescription>
+            새로운 RFQ를 생성합니다. 필수 정보를 입력해주세요.
+          </DialogDescription>
+        </DialogHeader>
+
+        {/* 스크롤 가능한 컨텐츠 영역 */}
+        <div className="flex-1 overflow-y-auto px-1">
+          <Form {...form}>
+            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 py-2">
+              
+              {/* 프로젝트 선택 (필수) */}
+              <FormField
+                control={form.control}
+                name="projectId"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>
+                      프로젝트 <span className="text-red-500">*</span>
+                    </FormLabel>
+                    <FormControl>
+                      <ProjectSelector
+                        selectedProjectId={field.value}
+                        onProjectSelect={handleProjectSelect}
+                        placeholder="프로젝트 선택..."
+                      />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              {/* 마감일 (필수) */}
+              <FormField
+                control={form.control}
+                name="dueDate"
+                render={({ field }) => (
+                  <FormItem className="flex flex-col">
+                    <FormLabel>
+                      마감일 <span className="text-red-500">*</span>
+                    </FormLabel>
+                    <Popover>
+                      <PopoverTrigger asChild>
+                        <FormControl>
+                          <Button
+                            variant="outline"
+                            className={cn(
+                              "w-full pl-3 text-left font-normal",
+                              !field.value && "text-muted-foreground"
+                            )}
+                          >
+                            {field.value ? (
+                              format(field.value, "yyyy-MM-dd")
+                            ) : (
+                              <span>마감일을 선택하세요</span>
+                            )}
+                            <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
+                          </Button>
+                        </FormControl>
+                      </PopoverTrigger>
+                      <PopoverContent className="w-auto p-0" align="start">
+                        <Calendar
+                          mode="single"
+                          selected={field.value}
+                          onSelect={field.onChange}
+                          disabled={(date) =>
+                            date < new Date() || date < new Date("1900-01-01")
+                          }
+                          initialFocus
+                        />
+                      </PopoverContent>
+                    </Popover>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              {/* 구매 담당자 코드 (필수) + 미리보기 */}
+              <FormField
+                control={form.control}
+                name="picCode"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>
+                      구매 담당자 코드 <span className="text-red-500">*</span>
+                    </FormLabel>
+                    <FormControl>
+                      <div className="space-y-2">
+                        <Input
+                          placeholder="예: P001, P002, MGR01 등"
+                          {...field}
+                        />
+                        {/* RFQ 코드 미리보기 */}
+                        {previewCode && (
+                          <div className="flex items-center gap-2 p-2 bg-muted rounded-md">
+                            <Eye className="h-4 w-4 text-muted-foreground" />
+                            <span className="text-sm text-muted-foreground">
+                              생성될 RFQ 코드:
+                            </span>
+                            <Badge variant="outline" className="font-mono">
+                              {isLoadingPreview ? "생성 중..." : previewCode}
+                            </Badge>
+                          </div>
+                        )}
+                      </div>
+                    </FormControl>
+                    <FormDescription>
+                      RFQ 코드는 N + 담당자코드 + 시리얼번호(5자리) 형식으로 자동 생성됩니다
+                    </FormDescription>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              {/* 담당자 정보 (두 개 나란히) */}
+              <div className="space-y-3">
+                <h4 className="text-sm font-medium">담당자 정보</h4>
+                <div className="grid grid-cols-2 gap-4">
+                  {/* 구매 담당자 */}
+                  <FormField
+                    control={form.control}
+                    name="picName"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>구매 담당자명</FormLabel>
+                        <FormControl>
+                          <Input
+                            placeholder="구매 담당자명"
+                            {...field}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  {/* 설계 담당자 */}
+                  <FormField
+                    control={form.control}
+                    name="engPicName"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>설계 담당자명</FormLabel>
+                        <FormControl>
+                          <Input
+                            placeholder="설계 담당자명"
+                            {...field}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                </div>
+              </div>
+
+              {/* 패키지 정보 (두 개 나란히) - 필수 */}
+              <div className="space-y-3">
+                <h4 className="text-sm font-medium">패키지 정보</h4>
+                <div className="grid grid-cols-2 gap-4">
+                  {/* 패키지 번호 (필수) */}
+                  <FormField
+                    control={form.control}
+                    name="packageNo"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>
+                          패키지 번호 <span className="text-red-500">*</span>
+                        </FormLabel>
+                        <FormControl>
+                          <Input
+                            placeholder="패키지 번호"
+                            {...field}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  {/* 패키지명 (필수) */}
+                  <FormField
+                    control={form.control}
+                    name="packageName"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>
+                          패키지명 <span className="text-red-500">*</span>
+                        </FormLabel>
+                        <FormControl>
+                          <Input
+                            placeholder="패키지명"
+                            {...field}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                </div>
+              </div>
+
+              {/* 프로젝트 상세 정보 */}
+              <div className="space-y-3">
+                <h4 className="text-sm font-medium">프로젝트 상세 정보</h4>
+                <div className="grid grid-cols-1 gap-3">
+                  <FormField
+                    control={form.control}
+                    name="projectCompany"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>프로젝트 회사</FormLabel>
+                        <FormControl>
+                          <Input
+                            placeholder="프로젝트 회사명"
+                            {...field}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  <div className="grid grid-cols-2 gap-3">
+                    <FormField
+                      control={form.control}
+                      name="projectFlag"
+                      render={({ field }) => (
+                        <FormItem>
+                          <FormLabel>프로젝트 플래그</FormLabel>
+                          <FormControl>
+                            <Input
+                              placeholder="프로젝트 플래그"
+                              {...field}
+                            />
+                          </FormControl>
+                          <FormMessage />
+                        </FormItem>
+                      )}
+                    />
+
+                    <FormField
+                      control={form.control}
+                      name="projectSite"
+                      render={({ field }) => (
+                        <FormItem>
+                          <FormLabel>프로젝트 사이트</FormLabel>
+                          <FormControl>
+                            <Input
+                              placeholder="프로젝트 사이트"
+                              {...field}
+                            />
+                          </FormControl>
+                          <FormMessage />
+                        </FormItem>
+                      )}
+                    />
+                  </div>
+                </div>
+              </div>
+
+              {/* 비고 */}
+              <FormField
+                control={form.control}
+                name="remark"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>비고</FormLabel>
+                    <FormControl>
+                      <Textarea
+                        placeholder="추가 비고사항을 입력하세요"
+                        className="resize-none"
+                        rows={3}
+                        {...field}
+                      />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+            </form>
+          </Form>
+        </div>
+
+        {/* 고정된 푸터 */}
+        <DialogFooter className="flex-shrink-0">
+          <Button
+            type="button"
+            variant="outline"
+            onClick={handleCancel}
+            disabled={isLoading}
+          >
+            취소
+          </Button>
+          <Button
+            type="submit"
+            onClick={form.handleSubmit(onSubmit)}
+            disabled={isLoading}
+          >
+            {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+            {isLoading ? "생성 중..." : "RFQ 생성"}
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

