From 2fdce8d7a57c792bba0ac36fa554dca9c9cc31e3 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 30 May 2025 03:36:16 +0000
Subject: (김준회) 기술영업 조선 RFQ 오류 수정 및 선종 필터 기능 추가
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/techsales-rfq/table/create-rfq-dialog.tsx | 749 +++++++++++++++++---------
 1 file changed, 487 insertions(+), 262 deletions(-)

(limited to 'lib/techsales-rfq/table/create-rfq-dialog.tsx')

diff --git a/lib/techsales-rfq/table/create-rfq-dialog.tsx b/lib/techsales-rfq/table/create-rfq-dialog.tsx
index 5faa3a0b..bacec02e 100644
--- a/lib/techsales-rfq/table/create-rfq-dialog.tsx
+++ b/lib/techsales-rfq/table/create-rfq-dialog.tsx
@@ -44,12 +44,20 @@ import {
 } from "@/components/ui/dropdown-menu"
 import { cn } from "@/lib/utils"
 import { ScrollArea } from "@/components/ui/scroll-area"
+import {
+  Table,
+  TableBody,
+  TableCell,
+  TableHead,
+  TableHeader,
+  TableRow,
+} from "@/components/ui/table"
 
 // 실제 데이터 서비스 import
 import { 
-  getShipbuildingItemsByWorkType, 
-  searchShipbuildingItems, 
   getWorkTypes,
+  getAllShipbuildingItemsForCache,
+  getShipTypes,
   type ShipbuildingItem,
   type WorkType 
 } from "@/lib/items-tech/service"
@@ -81,7 +89,7 @@ interface CreateRfqDialogProps {
   onCreated?: () => void;
 }
 
-export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
+export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) { 
   const { data: session } = useSession()
   const [isProcessing, setIsProcessing] = React.useState(false)
   const [isDialogOpen, setIsDialogOpen] = React.useState(false)
@@ -90,13 +98,115 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
   // 검색 및 필터링 상태
   const [itemSearchQuery, setItemSearchQuery] = React.useState("")
   const [selectedWorkType, setSelectedWorkType] = React.useState<WorkType | null>(null)
+  const [selectedShipType, setSelectedShipType] = React.useState<string | null>(null)
   const [selectedItems, setSelectedItems] = React.useState<ShipbuildingItem[]>([])
-  const [isSearchingItems, setIsSearchingItems] = React.useState(false)
   
   // 데이터 상태
   const [workTypes, setWorkTypes] = React.useState<WorkTypeOption[]>([])
-  const [availableItems, setAvailableItems] = React.useState<ShipbuildingItem[]>([])
+  const [allItems, setAllItems] = React.useState<ShipbuildingItem[]>([])
+  const [shipTypes, setShipTypes] = React.useState<string[]>([])
   const [isLoadingItems, setIsLoadingItems] = React.useState(false)
+  const [dataLoadError, setDataLoadError] = React.useState<string | null>(null)
+  const [retryCount, setRetryCount] = React.useState(0)
+
+  // 데이터 로딩 함수를 useCallback으로 메모이제이션
+  const loadData = React.useCallback(async (isRetry = false) => {
+    try {
+      if (!isRetry) {
+        setIsLoadingItems(true)
+        setDataLoadError(null)
+      }
+      
+      console.log(`데이터 로딩 시작... ${isRetry ? `(재시도 ${retryCount + 1}회)` : ''}`)
+      
+      const [workTypesResult, itemsResult, shipTypesResult] = await Promise.all([
+        getWorkTypes(),
+        getAllShipbuildingItemsForCache(),
+        getShipTypes()
+      ])
+      
+      console.log("WorkTypes 결과:", workTypesResult)
+      console.log("Items 결과:", itemsResult)
+      console.log("ShipTypes 결과:", shipTypesResult)
+      
+      // WorkTypes 설정
+      if (Array.isArray(workTypesResult)) {
+        setWorkTypes(workTypesResult)
+      } else {
+        console.error("WorkTypes 데이터 형식 오류:", workTypesResult)
+        throw new Error("공종 데이터를 불러올 수 없습니다.")
+      }
+      
+      // Items 설정
+      if (!itemsResult.error && itemsResult.data && Array.isArray(itemsResult.data)) {
+        setAllItems(itemsResult.data)
+        console.log("아이템 설정 완료:", itemsResult.data.length, "개")
+      } else {
+        console.error("아이템 로딩 실패:", itemsResult.error)
+        throw new Error(itemsResult.error || "아이템 데이터를 불러올 수 없습니다.")
+      }
+      
+      // ShipTypes 설정
+      if (!shipTypesResult.error && shipTypesResult.data && Array.isArray(shipTypesResult.data)) {
+        setShipTypes(shipTypesResult.data)
+        console.log("선종 설정 완료:", shipTypesResult.data)
+      } else {
+        console.error("선종 로딩 실패:", shipTypesResult.error)
+        throw new Error(shipTypesResult.error || "선종 데이터를 불러올 수 없습니다.")
+      }
+      
+      // 성공 시 재시도 카운터 리셋
+      setRetryCount(0)
+      setDataLoadError(null)
+      console.log("데이터 로딩 완료")
+      
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.'
+      console.error("데이터 로딩 오류:", errorMessage)
+      
+      setDataLoadError(errorMessage)
+      
+      // 3회까지 자동 재시도 (500ms 간격)
+      if (retryCount < 2) {
+        console.log(`${500 * (retryCount + 1)}ms 후 재시도...`)
+        setTimeout(() => {
+          setRetryCount(prev => prev + 1)
+          loadData(true)
+        }, 500 * (retryCount + 1))
+      } else {
+        // 재시도 실패 시 사용자에게 알림
+        toast.error(`데이터 로딩에 실패했습니다: ${errorMessage}`)
+      }
+    } finally {
+      if (!isRetry) {
+        setIsLoadingItems(false)
+      }
+    }
+  }, [retryCount])
+
+  // 다이얼로그가 열릴 때마다 데이터 로딩
+  React.useEffect(() => {
+    if (isDialogOpen) {
+      // 다이얼로그가 열릴 때마다 데이터 상태 초기화 및 로딩
+      setDataLoadError(null)
+      setRetryCount(0)
+      
+      // 이미 데이터가 있고 에러가 없다면 로딩하지 않음 (성능 최적화)
+      if (allItems.length > 0 && workTypes.length > 0 && shipTypes.length > 0 && !dataLoadError) {
+        console.log("기존 데이터 사용 (캐시)")
+        return
+      }
+      
+      loadData()
+    }
+  }, [isDialogOpen, loadData, allItems.length, workTypes.length, shipTypes.length, dataLoadError])
+
+  // 수동 새로고침 함수
+  const handleRefreshData = React.useCallback(() => {
+    setDataLoadError(null)
+    setRetryCount(0)
+    loadData()
+  }, [loadData])
 
   // RFQ 생성 폼
   const form = useForm<CreateRfqFormValues>({
@@ -104,55 +214,42 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
     defaultValues: {
       biddingProjectId: undefined,
       materialCodes: [],
-      dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // 14일 후
+      dueDate: undefined, // 기본값 제거
     }
   })
 
-  // 공종 목록 로드
-  React.useEffect(() => {
-    const loadWorkTypes = async () => {
-      const types = await getWorkTypes()
-      setWorkTypes(types)
+  // 필터링된 아이템 목록 가져오기
+  const availableItems = React.useMemo(() => {
+    let filtered = [...allItems]
+
+    // 선종 필터
+    if (selectedShipType) {
+      filtered = filtered.filter(item => item.shipTypes === selectedShipType)
     }
-    loadWorkTypes()
-  }, [])
 
-  // 아이템 데이터 로드
-  const loadItems = React.useCallback(async () => {
-    setIsLoadingItems(true)
-    try {
-      let result
-      if (itemSearchQuery.trim()) {
-        result = await searchShipbuildingItems(itemSearchQuery, selectedWorkType || undefined)
-      } else {
-        result = await getShipbuildingItemsByWorkType(selectedWorkType || undefined)
-      }
-      
-      if (result.error) {
-        toast.error(`아이템 로드 오류: ${result.error}`)
-        setAvailableItems([])
-      } else {
-        setAvailableItems(result.data || [])
-      }
-    } catch (error) {
-      console.error("아이템 로드 오류:", error)
-      toast.error("아이템을 불러오는 중 오류가 발생했습니다")
-      setAvailableItems([])
-    } finally {
-      setIsLoadingItems(false)
+    // 공종 필터  
+    if (selectedWorkType) {
+      filtered = filtered.filter(item => item.workType === selectedWorkType)
     }
-  }, [itemSearchQuery, selectedWorkType])
 
-  // 아이템 검색 디바운스
-  React.useEffect(() => {
-    setIsSearchingItems(true)
-    const timer = setTimeout(() => {
-      loadItems()
-      setIsSearchingItems(false)
-    }, 300)
+    // 검색어 필터
+    if (itemSearchQuery && itemSearchQuery.trim()) {
+      const query = itemSearchQuery.toLowerCase().trim()
+      filtered = filtered.filter(item => 
+        item.itemCode.toLowerCase().includes(query) ||
+        item.itemName.toLowerCase().includes(query) ||
+        (item.description && item.description.toLowerCase().includes(query)) ||
+        (item.itemList && item.itemList.toLowerCase().includes(query))
+      )
+    }
+
+    return filtered
+  }, [allItems, itemSearchQuery, selectedWorkType, selectedShipType])
 
-    return () => clearTimeout(timer)
-  }, [loadItems])
+  // 사용 가능한 선종 목록 가져오기
+  const availableShipTypes = React.useMemo(() => {
+    return shipTypes
+  }, [shipTypes])
 
   // 프로젝트 선택 처리
   const handleProjectSelect = (project: Project) => {
@@ -160,6 +257,9 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
     form.setValue("biddingProjectId", project.id)
     // 선택 초기화
     setSelectedItems([])
+    setSelectedShipType(null)
+    setSelectedWorkType(null)
+    setItemSearchQuery("")
     form.setValue("materialCodes", [])
   }
 
@@ -255,13 +355,16 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
       form.reset({
         biddingProjectId: undefined,
         materialCodes: [],
-        dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // 14일 후로 재설정
+        dueDate: undefined, // 기본값 제거
       })
       setSelectedProject(null)
       setItemSearchQuery("")
       setSelectedWorkType(null)
+      setSelectedShipType(null)
       setSelectedItems([])
-      setAvailableItems([])
+      // 에러 상태 및 재시도 카운터 초기화
+      setDataLoadError(null)
+      setRetryCount(0)
       
       // 생성 후 콜백 실행
       if (onCreated) {
@@ -277,7 +380,7 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
   }
 
   return (
-    <Dialog 
+    <Dialog
       open={isDialogOpen}
       onOpenChange={(open) => {
         setIsDialogOpen(open)
@@ -285,13 +388,16 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
           form.reset({
             biddingProjectId: undefined,
             materialCodes: [],
-            dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // 14일 후로 재설정
+            dueDate: undefined, // 기본값 제거
           })
           setSelectedProject(null)
           setItemSearchQuery("")
           setSelectedWorkType(null)
+          setSelectedShipType(null)
           setSelectedItems([])
-          setAvailableItems([])
+          // 에러 상태 및 재시도 카운터 초기화
+          setDataLoadError(null)
+          setRetryCount(0)
         }
       }}
     >
@@ -306,95 +412,177 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
           <span className="hidden sm:inline">RFQ 생성</span>
         </Button>
       </DialogTrigger>
-      <DialogContent className="max-w-4xl w-[90vw] h-[90vh] overflow-hidden flex flex-col">
-        <DialogHeader>
+      <DialogContent 
+        className="max-w-none h-[90vh] overflow-y-auto flex flex-col"
+        style={{ width: '1200px' }}
+      >
+        <DialogHeader className="border-b pb-4">
           <DialogTitle>RFQ 생성</DialogTitle>
         </DialogHeader>
         
-        <div className="flex-1 overflow-y-auto">
+        <div className="space-y-6 p-1 overflow-y-auto">
           <Form {...form}>
-            <form onSubmit={form.handleSubmit(handleCreateRfq)} className="space-y-4">
+            <form onSubmit={form.handleSubmit(handleCreateRfq)} className="space-y-6">
               {/* 프로젝트 선택 */}
-              <FormField
-                control={form.control}
-                name="biddingProjectId"
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>입찰 프로젝트</FormLabel>
-                    <FormControl>
-                      <EstimateProjectSelector
-                        selectedProjectId={field.value}
-                        onProjectSelect={handleProjectSelect}
-                        placeholder="입찰 프로젝트를 선택하세요"
-                      />
-                    </FormControl>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-              
-              <Separator className="my-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 ? (
-                              format(field.value, "PPP", { locale: ko })
-                            ) : (
-                              <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
+              <div className="space-y-4">
+                <FormField
+                  control={form.control}
+                  name="biddingProjectId"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>입찰 프로젝트</FormLabel>
+                      <FormControl>
+                        <EstimateProjectSelector
+                          selectedProjectId={field.value}
+                          onProjectSelect={handleProjectSelect}
+                          placeholder="입찰 프로젝트를 선택하세요"
                         />
-                      </PopoverContent>
-                    </Popover>
-                    <FormDescription>
-                      벤더가 견적을 제출해야 하는 마감일입니다.
-                    </FormDescription>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-              
-              <Separator className="my-4" />
-              
-              {!selectedProject ? (
-                <div className="text-sm text-muted-foreground italic text-center py-8">
-                  먼저 프로젝트를 선택해주세요
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+                
+                <Separator className="my-4" />
+                
+                {/* 선종 선택 */}
+                <div className="space-y-4">
+                  <div>
+                    <FormLabel>선종 선택</FormLabel>
+                  </div>
+                  
+                  {/* 데이터 로딩 에러 표시 */}
+                  {dataLoadError && (
+                    <div className="p-3 bg-destructive/10 border border-destructive/20 rounded-md">
+                      <div className="flex items-center justify-between">
+                        <div className="flex items-center gap-2">
+                          <X className="h-4 w-4 text-destructive" />
+                          <span className="text-sm text-destructive">{dataLoadError}</span>
+                        </div>
+                        <Button
+                          variant="outline"
+                          size="sm"
+                          onClick={handleRefreshData}
+                          disabled={isLoadingItems}
+                          className="h-8 text-xs"
+                        >
+                          {isLoadingItems ? (
+                            <>
+                              <Loader2 className="h-3 w-3 animate-spin mr-1" />
+                              재시도 중...
+                            </>
+                          ) : (
+                            "다시 시도"
+                          )}
+                        </Button>
+                      </div>
+                    </div>
+                  )}
+                  
+                  <DropdownMenu>
+                    <DropdownMenuTrigger asChild>
+                      <Button 
+                        variant="outline" 
+                        className="w-full justify-between"
+                        disabled={!selectedProject || isLoadingItems || dataLoadError !== null}
+                      >
+                        {isLoadingItems ? (
+                          <>
+                            <Loader2 className="h-4 w-4 animate-spin mr-2" />
+                            데이터 로딩 중...
+                          </>
+                        ) : dataLoadError ? (
+                          "데이터 로딩 실패"
+                        ) : selectedShipType ? (
+                          selectedShipType
+                        ) : (
+                          "미선택 (전체)"
+                        )}
+                        <ArrowUpDown className="ml-2 h-4 w-4 opacity-50" />
+                      </Button>
+                    </DropdownMenuTrigger>
+                    <DropdownMenuContent className="w-full max-h-60 overflow-y-auto">
+                      <DropdownMenuCheckboxItem
+                        checked={selectedShipType === null}
+                        onCheckedChange={() => {
+                          setSelectedShipType(null)
+                          setSelectedItems([])
+                          form.setValue("materialCodes", [])
+                        }}
+                      >
+                        전체 선종
+                      </DropdownMenuCheckboxItem>
+                      {availableShipTypes.map(shipType => (
+                        <DropdownMenuCheckboxItem
+                          key={shipType}
+                          checked={selectedShipType === shipType}
+                          onCheckedChange={() => {
+                            setSelectedShipType(shipType)
+                            setSelectedItems([])
+                            form.setValue("materialCodes", [])
+                          }}
+                        >
+                          {shipType}
+                        </DropdownMenuCheckboxItem>
+                      ))}
+                    </DropdownMenuContent>
+                  </DropdownMenu>
                 </div>
-              ) : (
+                
+                <Separator className="my-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 ? (
+                                format(field.value, "PPP", { locale: ko })
+                              ) : (
+                                <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>
+                  )}
+                />
+                
+                <Separator className="my-4" />
+                
                 <div className="space-y-6">
                   {/* 아이템 선택 영역 */}
                   <div className="space-y-4">
                     <div>
                       <FormLabel>조선 아이템 선택</FormLabel>
                       <FormDescription>
-                        공종별 아이템을 선택하세요
+                        {selectedShipType ? `선종 ${selectedShipType}의 공종별 아이템을 선택하세요` : "먼저 선종을 선택해주세요"}
                       </FormDescription>
                     </div>
                     
@@ -408,6 +596,7 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                             value={itemSearchQuery}
                             onChange={(e) => setItemSearchQuery(e.target.value)}
                             className="pl-8 pr-8"
+                            disabled={!selectedShipType || isLoadingItems || dataLoadError !== null}
                           />
                           {itemSearchQuery && (
                             <Button
@@ -415,19 +604,21 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                               size="sm"
                               className="absolute right-0 top-0 h-full px-3"
                               onClick={() => setItemSearchQuery("")}
+                              disabled={!selectedShipType || isLoadingItems || dataLoadError !== null}
                             >
                               <X className="h-4 w-4" />
                             </Button>
                           )}
-                          {isSearchingItems && (
-                            <Loader2 className="absolute right-8 top-2.5 h-4 w-4 animate-spin text-muted-foreground" />
-                          )}
                         </div>
                         
                         {/* 공종 필터 */}
                         <DropdownMenu>
                           <DropdownMenuTrigger asChild>
-                            <Button variant="outline" className="gap-1">
+                            <Button 
+                              variant="outline" 
+                              className="gap-1"
+                              disabled={!selectedShipType || isLoadingItems || dataLoadError !== null}
+                            >
                               {selectedWorkType ? workTypes.find(wt => wt.code === selectedWorkType)?.name : "전체 공종"}
                               <ArrowUpDown className="ml-2 h-4 w-4 opacity-50" />
                             </Button>
@@ -457,10 +648,41 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                     <div className="border rounded-md">
                       <ScrollArea className="h-[300px]">
                         <div className="p-2 space-y-1">
-                          {isLoadingItems ? (
+                          {dataLoadError ? (
+                            <div className="text-center py-8">
+                              <div className="p-4 bg-destructive/10 border border-destructive/20 rounded-md mx-4">
+                                <div className="flex flex-col items-center gap-3">
+                                  <X className="h-8 w-8 text-destructive" />
+                                  <div className="text-center">
+                                    <p className="text-sm text-destructive font-medium">데이터 로딩에 실패했습니다</p>
+                                    <p className="text-xs text-muted-foreground mt-1">{dataLoadError}</p>
+                                  </div>
+                                  <Button
+                                    variant="outline"
+                                    size="sm"
+                                    onClick={handleRefreshData}
+                                    disabled={isLoadingItems}
+                                    className="h-8"
+                                  >
+                                    {isLoadingItems ? (
+                                      <>
+                                        <Loader2 className="h-3 w-3 animate-spin mr-1" />
+                                        재시도 중...
+                                      </>
+                                    ) : (
+                                      "다시 시도"
+                                    )}
+                                  </Button>
+                                </div>
+                              </div>
+                            </div>
+                          ) : isLoadingItems ? (
                             <div className="text-center py-8 text-muted-foreground">
                               <Loader2 className="h-6 w-6 animate-spin mx-auto mb-2" />
                               아이템을 불러오는 중...
+                              {retryCount > 0 && (
+                                <p className="text-xs mt-1">재시도 {retryCount}회</p>
+                              )}
                             </div>
                           ) : availableItems.length > 0 ? (
                             [...availableItems]
@@ -547,146 +769,149 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                     />
 
                     {/* RFQ 그룹핑 미리보기 */}
-                    {selectedItems.length > 0 && (
-                      <div className="space-y-3">
-                        <FormLabel>생성될 RFQ 그룹 미리보기</FormLabel>
-                        <div className="border rounded-md p-3 bg-background">
-                          {(() => {
-                            // 아이템명(itemList)으로 그룹핑
-                            const groupedItems = selectedItems.reduce((groups, item) => {
-                              const actualItemName = item.itemList // 실제 조선 아이템명
-                              if (!actualItemName) {
-                                return groups // itemList가 없는 경우 제외
-                              }
-                              if (!groups[actualItemName]) {
-                                groups[actualItemName] = []
-                              }
-                              groups[actualItemName].push(item)
-                              return groups
-                            }, {} as Record<string, typeof selectedItems>)
+                    <div className="space-y-3">
+                      <FormLabel>생성될 RFQ 그룹 미리보기</FormLabel>
+                      <div className="border rounded-md bg-background">
+                        {(() => {
+                          // 아이템명(itemList)으로 그룹핑
+                          const groupedItems = selectedItems.reduce((groups, item) => {
+                            const actualItemName = item.itemList // 실제 조선 아이템명
+                            if (!actualItemName) {
+                              return groups // itemList가 없는 경우 제외
+                            }
+                            if (!groups[actualItemName]) {
+                              groups[actualItemName] = []
+                            }
+                            groups[actualItemName].push(item)
+                            return groups
+                          }, {} as Record<string, typeof selectedItems>)
 
-                            const rfqGroups = Object.entries(groupedItems).map(([actualItemName, items]) => {
-                              const itemCodes = items.map(item => item.itemCode) // 자재그룹코드들
-                              const joinedItemCodes = itemCodes.join(',')
-                              return {
-                                actualItemName,
-                                items,
-                                itemCodes,
-                                joinedItemCodes,
-                                codeLength: joinedItemCodes.length,
-                                isOverLimit: joinedItemCodes.length > 255
-                              }
-                            })
+                          const rfqGroups = Object.entries(groupedItems).map(([actualItemName, items]) => {
+                            const itemCodes = items.map(item => item.itemCode) // 자재그룹코드들
+                            const joinedItemCodes = itemCodes.join(',')
+                            return {
+                              actualItemName,
+                              items,
+                              itemCodes,
+                              joinedItemCodes,
+                              codeLength: joinedItemCodes.length,
+                              isOverLimit: joinedItemCodes.length > 255
+                            }
+                          })
 
-                            return (
-                              <div className="space-y-3">
-                                <div className="text-sm text-muted-foreground">
-                                  총 {rfqGroups.length}개의 RFQ가 생성됩니다 (아이템명별로 그룹핑)
-                                </div>
-                                {rfqGroups.map((group, index) => (
-                                  <div 
-                                    key={group.actualItemName}
-                                    className={cn(
-                                      "p-3 border rounded-md space-y-2",
-                                      group.isOverLimit && "border-destructive bg-destructive/5"
-                                    )}
-                                  >
-                                    <div className="flex items-center justify-between">
-                                      <div className="font-medium">
-                                        RFQ #{index + 1}: {group.actualItemName}
-                                      </div>
-                                      <Badge variant={group.isOverLimit ? "destructive" : "secondary"}>
-                                        {group.itemCodes.length}개 자재코드 ({group.codeLength}/255자)
-                                      </Badge>
-                                    </div>
-                                    <div className="text-sm text-muted-foreground">
-                                      자재코드: {group.joinedItemCodes}
-                                    </div>
-                                    {group.isOverLimit && (
-                                      <div className="text-sm text-destructive">
-                                        ⚠️ 자재코드 길이가 255자를 초과합니다. 일부 아이템을 제거해주세요.
-                                      </div>
-                                    )}
-                                    <div className="text-xs text-muted-foreground">
-                                      포함된 아이템: {group.items.map(item => `${item.itemCode}`).join(', ')}
-                                    </div>
-                                  </div>
-                                ))}
+                          return (
+                            <div className="space-y-3">
+                              <div className="text-sm text-muted-foreground p-3 border-b">
+                                총 {rfqGroups.length}개의 RFQ가 생성됩니다 (아이템명별로 그룹핑)
                               </div>
-                            )
-                          })()}
-                        </div>
+                              <ScrollArea className="h-[200px]">
+                                <Table>
+                                  <TableHeader className="sticky top-0 bg-background">
+                                    <TableRow>
+                                      <TableHead className="w-[80px]">RFQ #</TableHead>
+                                      <TableHead>아이템명</TableHead>
+                                      <TableHead className="w-[120px]">자재그룹코드 개수</TableHead>
+                                      <TableHead className="w-[100px]">길이</TableHead>
+                                      <TableHead className="w-[80px]">상태</TableHead>
+                                    </TableRow>
+                                  </TableHeader>
+                                  <TableBody>
+                                    {rfqGroups.map((group, index) => (
+                                      <TableRow 
+                                        key={group.actualItemName}
+                                        className={group.isOverLimit ? "bg-destructive/5" : ""}
+                                      >
+                                        <TableCell className="font-medium">#{index + 1}</TableCell>
+                                        <TableCell>
+                                          <div className="max-w-[200px] truncate" title={group.actualItemName}>
+                                            {group.actualItemName}
+                                          </div>
+                                        </TableCell>
+                                        <TableCell>{group.itemCodes.length}개</TableCell>
+                                        <TableCell>
+                                          <span className={group.isOverLimit ? "text-destructive font-medium" : ""}>
+                                            {group.codeLength}/255자
+                                          </span>
+                                        </TableCell>
+                                        <TableCell>
+                                          {group.isOverLimit ? (
+                                            <Badge variant="destructive" className="text-xs">초과</Badge>
+                                          ) : (
+                                            <Badge variant="secondary" className="text-xs">정상</Badge>
+                                          )}
+                                        </TableCell>
+                                      </TableRow>
+                                    ))}
+                                  </TableBody>
+                                </Table>
+                              </ScrollArea>
+                            </div>
+                          )
+                        })()}
                       </div>
-                    )}
+                    </div>
                   </div>
                 </div>
-              )}
-              
-              {/* 안내 메시지 */}
-              {/* {selectedProject && (
-                <div className="text-sm text-muted-foreground bg-muted p-3 rounded-md">
-                  <p>• 공종별 조선 아이템을 선택하세요.</p>
-                  <p>• <strong>같은 아이템명의 다른 자재코드들은 하나의 RFQ로 그룹핑됩니다.</strong></p>
-                  <p>• 그룹핑된 자재코드들은 comma로 구분되어 저장됩니다.</p>
-                  <p>• 자재코드 길이는 최대 255자까지 가능합니다.</p>
-                  <p>• 마감일은 벤더가 견적을 제출해야 하는 날짜입니다.</p>
-                </div>
-              )} */}
-               
-              <div className="flex justify-end space-x-2 pt-4">
-                <Button
-                  type="button"
-                  variant="outline"
-                  onClick={() => setIsDialogOpen(false)}
-                  disabled={isProcessing}
-                >
-                  취소
-                </Button>
-                <Button 
-                  type="submit" 
-                  disabled={
-                    isProcessing || 
-                    !selectedProject || 
-                    selectedItems.length === 0 ||
-                    // 255자 초과 그룹이 있는지 확인
-                    (() => {
-                      const groupedItems = selectedItems.reduce((groups, item) => {
-                        const actualItemName = item.itemList // 실제 조선 아이템명
-                        if (!actualItemName) {
-                          return groups // itemList가 없는 경우 제외
-                        }
-                        if (!groups[actualItemName]) {
-                          groups[actualItemName] = []
-                        }
-                        groups[actualItemName].push(item.itemCode)
-                        return groups
-                      }, {} as Record<string, string[]>)
-                      
-                      return Object.values(groupedItems).some(itemCodes => itemCodes.join(',').length > 255)
-                    })()
-                  }
-                >
-                  {isProcessing ? "처리 중..." : (() => {
-                    const groupedItems = selectedItems.reduce((groups, item) => {
-                      const actualItemName = item.itemList // 실제 조선 아이템명
-                      if (!actualItemName) {
-                        return groups // itemList가 없는 경우 제외
-                      }
-                      if (!groups[actualItemName]) {
-                        groups[actualItemName] = []
-                      }
-                      groups[actualItemName].push(item.itemCode)
-                      return groups
-                    }, {} as Record<string, string[]>)
-                    
-                    const groupCount = Object.keys(groupedItems).length
-                    return `${groupCount}개 아이템 그룹으로 생성하기`
-                  })()}
-                </Button>
               </div>
             </form>
           </Form>
         </div>
+        
+        {/* Footer - Sticky 버튼 영역 */}
+        <div className="sticky bottom-0 left-0 z-20 bg-background border-t pt-4 mt-4">
+          <div className="flex justify-end space-x-2">
+            <Button
+              type="button"
+              variant="outline"
+              onClick={() => setIsDialogOpen(false)}
+              disabled={isProcessing}
+            >
+              취소
+            </Button>
+            <Button 
+              type="button"
+              onClick={form.handleSubmit(handleCreateRfq)}
+              disabled={
+                isProcessing || 
+                !selectedProject || 
+                selectedItems.length === 0 ||
+                // 255자 초과 그룹이 있는지 확인
+                (() => {
+                  const groupedItems = selectedItems.reduce((groups, item) => {
+                    const actualItemName = item.itemList // 실제 조선 아이템명
+                    if (!actualItemName) {
+                      return groups // itemList가 없는 경우 제외
+                    }
+                    if (!groups[actualItemName]) {
+                      groups[actualItemName] = []
+                    }
+                    groups[actualItemName].push(item.itemCode)
+                    return groups
+                  }, {} as Record<string, string[]>)
+                  
+                  return Object.values(groupedItems).some(itemCodes => itemCodes.join(',').length > 255)
+                })()
+              }
+            >
+              {isProcessing ? "처리 중..." : (() => {
+                const groupedItems = selectedItems.reduce((groups, item) => {
+                  const actualItemName = item.itemList // 실제 조선 아이템명
+                  if (!actualItemName) {
+                    return groups // itemList가 없는 경우 제외
+                  }
+                  if (!groups[actualItemName]) {
+                    groups[actualItemName] = []
+                  }
+                  groups[actualItemName].push(item.itemCode)
+                  return groups
+                }, {} as Record<string, string[]>)
+                
+                const groupCount = Object.keys(groupedItems).length
+                return `${groupCount}개 아이템 그룹으로 생성하기`
+              })()}
+            </Button>
+          </div>
+        </div>
       </DialogContent>
     </Dialog>
   )
-- 
cgit v1.2.3

