From 0fddf148402fd6b99a1b3800d73679899bcb2ed3 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 13 Jun 2025 07:11:18 +0000
Subject: (대표님) 20250613 16시 10분 global css, b-rfq, document 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/b-rfq/initial/add-initial-rfq-dialog.tsx | 534 +++++++++++++++++++++++++++
 1 file changed, 534 insertions(+)
 create mode 100644 lib/b-rfq/initial/add-initial-rfq-dialog.tsx

(limited to 'lib/b-rfq/initial/add-initial-rfq-dialog.tsx')

diff --git a/lib/b-rfq/initial/add-initial-rfq-dialog.tsx b/lib/b-rfq/initial/add-initial-rfq-dialog.tsx
new file mode 100644
index 00000000..d0924be2
--- /dev/null
+++ b/lib/b-rfq/initial/add-initial-rfq-dialog.tsx
@@ -0,0 +1,534 @@
+// 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<typeof addInitialRfqSchema>
+
+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<Vendor[]>([])
+  const [vendorsLoading, setVendorsLoading] = React.useState(false)
+  const [vendorSearchOpen, setVendorSearchOpen] = React.useState(false)
+  const [incoterms, setIncoterms] = React.useState<Incoterm[]>([])
+  const [incotermsLoading, setIncotermsLoading] = React.useState(false)
+  const [incotermsSearchOpen, setIncotermsSearchOpen] = React.useState(false)
+
+  const form = useForm<AddInitialRfqFormData>({
+    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 (
+    <Dialog open={open} onOpenChange={handleOpenChange}>
+      <DialogTrigger asChild>
+        <Button variant="outline" size="sm" className="gap-2">
+          <Plus className="size-4" aria-hidden="true" />
+          <span className="hidden sm:inline">초기 RFQ 추가</span>
+        </Button>
+      </DialogTrigger>
+      
+      <DialogContent className="sm:max-w-[600px] max-h-[80vh] overflow-y-auto">
+        <DialogHeader>
+          <DialogTitle>초기 RFQ 추가</DialogTitle>
+          <DialogDescription>
+            새로운 벤더를 대상으로 하는 초기 RFQ를 추가합니다.
+          </DialogDescription>
+        </DialogHeader>
+
+        <Form {...form}>
+          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
+            {/* 벤더 선택 */}
+            <FormField
+              control={form.control}
+              name="vendorId"
+              render={({ field }) => (
+                <FormItem className="flex flex-col">
+                  <FormLabel>벤더 선택 *</FormLabel>
+                  <Popover open={vendorSearchOpen} onOpenChange={setVendorSearchOpen}>
+                    <PopoverTrigger asChild>
+                      <FormControl>
+                        <Button
+                          variant="outline"
+                          role="combobox"
+                          aria-expanded={vendorSearchOpen}
+                          className="justify-between"
+                          disabled={vendorsLoading}
+                        >
+                          {selectedVendor ? (
+                            <div className="flex items-center gap-2">
+                              <Building className="h-4 w-4" />
+                              <span className="truncate">
+                                {selectedVendor.vendorName} ({selectedVendor.vendorCode})
+                              </span>
+                            </div>
+                          ) : (
+                            <span className="text-muted-foreground">
+                              {vendorsLoading ? "로딩 중..." : "벤더를 선택하세요"}
+                            </span>
+                          )}
+                          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+                        </Button>
+                      </FormControl>
+                    </PopoverTrigger>
+                    <PopoverContent className="w-full p-0" align="start">
+                      <Command>
+                        <CommandInput 
+                          placeholder="벤더명 또는 코드로 검색..." 
+                          className="h-9"
+                        />
+                        <CommandList>
+                          <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+                          <CommandGroup>
+                            {vendors.map((vendor) => (
+                              <CommandItem
+                                key={vendor.id}
+                                value={`${vendor.vendorName} ${vendor.vendorCode}`}
+                                onSelect={() => {
+                                  field.onChange(vendor.id)
+                                  setVendorSearchOpen(false)
+                                }}
+                              >
+                                <div className="flex items-center gap-2 w-full">
+                                  <Building className="h-4 w-4" />
+                                  <div className="flex-1 min-w-0">
+                                    <div className="font-medium truncate">
+                                      {vendor.vendorName}
+                                    </div>
+                                    <div className="text-sm text-muted-foreground">
+                                      {vendor.vendorCode} • {vendor.country}
+                                    </div>
+                                  </div>
+                                  <Check
+                                    className={cn(
+                                      "ml-auto h-4 w-4",
+                                      vendor.id === field.value ? "opacity-100" : "opacity-0"
+                                    )}
+                                  />
+                                </div>
+                              </CommandItem>
+                            ))}
+                          </CommandGroup>
+                        </CommandList>
+                      </Command>
+                    </PopoverContent>
+                  </Popover>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+
+            {/* 날짜 필드들 */}
+            <div className="grid grid-cols-2 gap-4">
+            <FormField
+                  control={form.control}
+                  name="dueDate"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-col">
+                      <FormLabel>견적 마감일</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 ? (
+                                formatDate(field.value,"KR")
+                              ) : (
+                                <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="validDate"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-col">
+                      <FormLabel>견적 유효일</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 ? (
+                                formatDate(field.value,"KR")
+                              ) : (
+                                <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>
+                  )}
+                />
+            </div>
+
+            {/* Incoterms 및 GTC */}
+            <div className="grid grid-cols-2 gap-4">
+            <FormField
+              control={form.control}
+              name="vendorId"
+              render={({ field }) => (
+                <FormItem className="flex flex-col">
+                  <FormLabel>Incoterms *</FormLabel>
+                  <Popover open={incotermsSearchOpen} onOpenChange={setIncotermsSearchOpen}>
+                    <PopoverTrigger asChild>
+                      <FormControl>
+                        <Button
+                          variant="outline"
+                          role="combobox"
+                          aria-expanded={incotermsSearchOpen}
+                          className="justify-between"
+                          disabled={incotermsLoading}
+                        >
+                          {selectedIncoterm ? (
+                            <div className="flex items-center gap-2">
+                              <Building className="h-4 w-4" />
+                              <span className="truncate">
+                                {selectedIncoterm.code} ({selectedIncoterm.description})
+                              </span>
+                            </div>
+                          ) : (
+                            <span className="text-muted-foreground">
+                              {incotermsLoading ? "로딩 중..." : "인코텀즈를 선택하세요"}
+                            </span>
+                          )}
+                          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+                        </Button>
+                      </FormControl>
+                    </PopoverTrigger>
+                    <PopoverContent className="w-full p-0" align="start">
+                      <Command>
+                        <CommandInput 
+                          placeholder="코드 또는 내용으로 검색..." 
+                          className="h-9"
+                        />
+                        <CommandList>
+                          <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+                          <CommandGroup>
+                            {incoterms.map((incoterm) => (
+                              <CommandItem
+                                key={incoterm.id}
+                                value={`${incoterm.code} ${incoterm.description}`}
+                                onSelect={() => {
+                                  field.onChange(vendor.id)
+                                  setVendorSearchOpen(false)
+                                }}
+                              >
+                                <div className="flex items-center gap-2 w-full">
+                                    <div className="font-medium truncate">
+                                      {incoterm.code}    {incoterm.description}
+                                    </div>
+                                  <Check
+                                    className={cn(
+                                      "ml-auto h-4 w-4",
+                                      incoterm.id === field.value ? "opacity-100" : "opacity-0"
+                                    )}
+                                  />
+                                </div>
+                              </CommandItem>
+                            ))}
+                          </CommandGroup>
+                        </CommandList>
+                      </Command>
+                    </PopoverContent>
+                  </Popover>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+            </div>
+
+            {/* GTC 정보 */}
+            <div className="grid grid-cols-2 gap-4">
+              <FormField
+                control={form.control}
+                name="gtc"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>GTC</FormLabel>
+                    <FormControl>
+                      <Input placeholder="GTC 정보" {...field} />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <FormField
+                control={form.control}
+                name="gtcValidDate"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>GTC 유효일</FormLabel>
+                    <FormControl>
+                      <Input placeholder="GTC 유효일" {...field} />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+            </div>
+
+            {/* 분류 정보 */}
+            <div className="grid grid-cols-2 gap-4">
+              <FormField
+                control={form.control}
+                name="classification"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>선급</FormLabel>
+                    <FormControl>
+                      <Input placeholder="선급" {...field} />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <FormField
+                control={form.control}
+                name="sparepart"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>Spare part</FormLabel>
+                    <FormControl>
+                      <Input placeholder="예비부품 정보" {...field} />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+            </div>
+
+
+
+            <DialogFooter>
+              <Button
+                type="button"
+                variant="outline"
+                onClick={() => handleOpenChange(false)}
+                disabled={isSubmitting}
+              >
+                취소
+              </Button>
+              <Button type="submit" disabled={isSubmitting}>
+                {isSubmitting ? "추가 중..." : "추가"}
+              </Button>
+            </DialogFooter>
+          </form>
+        </Form>
+      </DialogContent>
+    </Dialog>
+  )
+}
+
+
-- 
cgit v1.2.3

