From c5002d77087b256599b174ada611621657fcc523 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Sun, 15 Jun 2025 04:40:22 +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 | 917 --------------------------
 1 file changed, 917 deletions(-)
 delete mode 100644 lib/techsales-rfq/table/create-rfq-dialog.tsx

(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
deleted file mode 100644
index 81c85649..00000000
--- a/lib/techsales-rfq/table/create-rfq-dialog.tsx
+++ /dev/null
@@ -1,917 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { toast } from "sonner"
-import { ArrowUpDown, CheckSquare, Plus, Search, Square, X, Loader2 } from "lucide-react"
-import { Input } from "@/components/ui/input"
-import { Calendar } from "@/components/ui/calendar"
-import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
-import { CalendarIcon } from "lucide-react"
-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,
-  FormDescription,
-} from "@/components/ui/form"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useForm } from "react-hook-form"
-import * as z from "zod"
-import { EstimateProjectSelector } from "@/components/BidProjectSelector"
-import { type Project } from "@/lib/rfqs/service"
-import { createTechSalesRfq } from "@/lib/techsales-rfq/service"
-import { useSession } from "next-auth/react"
-import { Separator } from "@/components/ui/separator"
-import { Badge } from "@/components/ui/badge"
-import {
-  DropdownMenu,
-  DropdownMenuCheckboxItem,
-  DropdownMenuContent,
-  DropdownMenuTrigger,
-} 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 { 
-  getWorkTypes,
-  getAllShipbuildingItemsForCache,
-  getShipTypes,
-  type ShipbuildingItem,
-  type WorkType 
-} from "@/lib/items-tech/service"
-
-// 유효성 검증 스키마 - 자재코드(item_code) 배열로 변경
-const createRfqSchema = z.object({
-  biddingProjectId: z.number({
-    required_error: "프로젝트를 선택해주세요.",
-  }),
-  materialCodes: z.array(z.string()).min(1, {
-    message: "적어도 하나의 자재코드를 선택해야 합니다.",
-  }),
-  dueDate: z.date({
-    required_error: "마감일을 선택해주세요.",
-  }),
-})
-
-// 폼 데이터 타입
-type CreateRfqFormValues = z.infer<typeof createRfqSchema>
-
-// 공종 타입 정의
-interface WorkTypeOption {
-  code: WorkType
-  name: string
-  description: string
-}
-
-interface CreateRfqDialogProps {
-  onCreated?: () => void;
-}
-
-export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) { 
-  const { data: session } = useSession()
-  const [isProcessing, setIsProcessing] = React.useState(false)
-  const [isDialogOpen, setIsDialogOpen] = React.useState(false)
-  const [selectedProject, setSelectedProject] = React.useState<Project | null>(null)
-  
-  // 검색 및 필터링 상태
-  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 [workTypes, setWorkTypes] = React.useState<WorkTypeOption[]>([])
-  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>({
-    resolver: zodResolver(createRfqSchema),
-    defaultValues: {
-      biddingProjectId: undefined,
-      materialCodes: [],
-      dueDate: undefined, // 기본값 제거
-    }
-  })
-
-  // 필터링된 아이템 목록 가져오기
-  const availableItems = React.useMemo(() => {
-    let filtered = [...allItems]
-
-    // 선종 필터
-    if (selectedShipType) {
-      filtered = filtered.filter(item => item.shipTypes === selectedShipType)
-    }
-
-    // 공종 필터  
-    if (selectedWorkType) {
-      filtered = filtered.filter(item => item.workType === selectedWorkType)
-    }
-
-    // 검색어 필터
-    if (itemSearchQuery && itemSearchQuery.trim()) {
-      const query = itemSearchQuery.toLowerCase().trim()
-      filtered = filtered.filter(item => 
-        item.itemCode.toLowerCase().includes(query) ||
-        (item.itemList && item.itemList.toLowerCase().includes(query))
-      )
-    }
-
-    return filtered
-  }, [allItems, itemSearchQuery, selectedWorkType, selectedShipType])
-
-  // 사용 가능한 선종 목록 가져오기
-  const availableShipTypes = React.useMemo(() => {
-    return shipTypes
-  }, [shipTypes])
-
-  // 프로젝트 선택 처리
-  const handleProjectSelect = (project: Project) => {
-    setSelectedProject(project)
-    form.setValue("biddingProjectId", project.id)
-    // 선택 초기화
-    setSelectedItems([])
-    setSelectedShipType(null)
-    setSelectedWorkType(null)
-    setItemSearchQuery("")
-    form.setValue("materialCodes", [])
-  }
-
-  // 아이템 선택/해제 처리
-  const handleItemToggle = (item: ShipbuildingItem) => {
-    const isSelected = selectedItems.some(selected => selected.id === item.id)
-    
-    if (isSelected) {
-      // 아이템 선택 해제
-      const newSelectedItems = selectedItems.filter(selected => selected.id !== item.id)
-      setSelectedItems(newSelectedItems)
-      form.setValue("materialCodes", newSelectedItems.map(item => item.itemCode))
-    } else {
-      // 아이템 선택 추가
-      const newSelectedItems = [...selectedItems, item]
-      setSelectedItems(newSelectedItems)
-      form.setValue("materialCodes", newSelectedItems.map(item => item.itemCode))
-    }
-  }
-
-  // 아이템 제거 처리
-  const handleRemoveItem = (itemId: number) => {
-    const newSelectedItems = selectedItems.filter(item => item.id !== itemId)
-    setSelectedItems(newSelectedItems)
-    form.setValue("materialCodes", newSelectedItems.map(item => item.itemCode))
-  }
-
-  // RFQ 생성 함수
-  const handleCreateRfq = async (data: CreateRfqFormValues) => {
-    try {
-      setIsProcessing(true)
-      
-      // 사용자 인증 확인
-      if (!session?.user?.id) {
-        throw new Error("로그인이 필요합니다")
-      }
-
-      // 선택된 아이템들을 아이템명(itemList)으로 그룹핑
-      const groupedItems = selectedItems.reduce((groups, item) => {
-        const actualItemName = item.itemList // 실제 조선 아이템명
-        if (!actualItemName) {
-          throw new Error(`아이템 "${item.itemCode}"의 아이템명(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
-        }
-      })
-
-      // 255자 초과 그룹 확인
-      const overLimitGroups = rfqGroups.filter(group => group.isOverLimit)
-      if (overLimitGroups.length > 0) {
-        const groupNames = overLimitGroups.map(g => `"${g.actualItemName}" (${g.codeLength}자)`).join(', ')
-        throw new Error(`다음 아이템 그룹의 자재코드가 255자를 초과합니다: ${groupNames}`)
-      }
-
-      // 각 그룹별로 RFQ 생성
-      const createPromises = rfqGroups.map(group => 
-        createTechSalesRfq({
-          biddingProjectId: data.biddingProjectId,
-          itemShipbuildingId: group.items[0].id, // 그룹의 첫 번째 아이템의 shipbuilding ID 사용
-          materialGroupCodes: group.itemCodes, // 해당 그룹의 자재코드들
-          createdBy: Number(session.user.id),
-          dueDate: data.dueDate,
-        })
-      )
-
-      const results = await Promise.all(createPromises)
-      
-      // 오류 확인
-      const errors = results.filter(result => result.error)
-      if (errors.length > 0) {
-        throw new Error(errors.map(e => e.error).join(', '))
-      }
-      
-      // 성공적으로 생성되면 다이얼로그 닫기 및 메시지 표시
-      const totalRfqs = results.reduce((sum, result) => sum + (result.data?.length || 0), 0)
-      toast.success(`${rfqGroups.length}개 아이템 그룹으로 총 ${totalRfqs}개의 RFQ가 성공적으로 생성되었습니다`)
-      setIsDialogOpen(false)
-      form.reset({
-        biddingProjectId: undefined,
-        materialCodes: [],
-        dueDate: undefined, // 기본값 제거
-      })
-      setSelectedProject(null)
-      setItemSearchQuery("")
-      setSelectedWorkType(null)
-      setSelectedShipType(null)
-      setSelectedItems([])
-      // 에러 상태 및 재시도 카운터 초기화
-      setDataLoadError(null)
-      setRetryCount(0)
-      
-      // 생성 후 콜백 실행
-      if (onCreated) {
-        onCreated()
-      }
-      
-    } catch (error) {
-      console.error("RFQ 생성 오류:", error)
-      toast.error(`RFQ 생성 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`)
-    } finally {
-      setIsProcessing(false)
-    }
-  }
-
-  return (
-    <Dialog
-      open={isDialogOpen}
-      onOpenChange={(open) => {
-        setIsDialogOpen(open)
-        if (!open) {
-          form.reset({
-            biddingProjectId: undefined,
-            materialCodes: [],
-            dueDate: undefined, // 기본값 제거
-          })
-          setSelectedProject(null)
-          setItemSearchQuery("")
-          setSelectedWorkType(null)
-          setSelectedShipType(null)
-          setSelectedItems([])
-          // 에러 상태 및 재시도 카운터 초기화
-          setDataLoadError(null)
-          setRetryCount(0)
-        }
-      }}
-    >
-      <DialogTrigger asChild>
-        <Button
-          variant="default"
-          size="sm"
-          className="gap-2"
-          disabled={isProcessing}
-        >
-          <Plus className="size-4" aria-hidden="true" />
-          <span className="hidden sm:inline">RFQ 생성</span>
-        </Button>
-      </DialogTrigger>
-      <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="space-y-6 p-1 overflow-y-auto">
-          <Form {...form}>
-            <form onSubmit={form.handleSubmit(handleCreateRfq)} className="space-y-6">
-              {/* 프로젝트 선택 */}
-              <div className="space-y-4">
-                <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" />
-                
-                {/* 선종 선택 */}
-                <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>
-                    
-                    {/* 아이템 검색 및 필터 */}
-                    <div className="space-y-2">
-                      <div className="flex space-x-2">
-                        <div className="relative flex-1">
-                          <Search className="absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
-                          <Input
-                            placeholder="아이템 검색..."
-                            value={itemSearchQuery}
-                            onChange={(e) => setItemSearchQuery(e.target.value)}
-                            className="pl-8 pr-8"
-                            disabled={!selectedShipType || isLoadingItems || dataLoadError !== null}
-                          />
-                          {itemSearchQuery && (
-                            <Button
-                              variant="ghost"
-                              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>
-                          )}
-                        </div>
-                        
-                        {/* 공종 필터 */}
-                        <DropdownMenu>
-                          <DropdownMenuTrigger asChild>
-                            <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>
-                          </DropdownMenuTrigger>
-                          <DropdownMenuContent align="end">
-                            <DropdownMenuCheckboxItem
-                              checked={selectedWorkType === null}
-                              onCheckedChange={() => setSelectedWorkType(null)}
-                            >
-                              전체 공종
-                            </DropdownMenuCheckboxItem>
-                            {workTypes.map(workType => (
-                              <DropdownMenuCheckboxItem
-                                key={workType.code}
-                                checked={selectedWorkType === workType.code}
-                                onCheckedChange={() => setSelectedWorkType(workType.code)}
-                              >
-                                {workType.name}
-                              </DropdownMenuCheckboxItem>
-                            ))}
-                          </DropdownMenuContent>
-                        </DropdownMenu>
-                      </div>
-                    </div>
-                      
-                    {/* 아이템 목록 */}
-                    <div className="border rounded-md">
-                      <ScrollArea className="h-[300px]">
-                        <div className="p-2 space-y-1">
-                          {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]
-                              .sort((a, b) => {
-                                // itemList 기준으로 정렬 (없는 경우 itemName 사용, 둘 다 없으면 맨 뒤로)
-                                const aName = a.itemList || 'zzz'
-                                const bName = b.itemList || 'zzz'
-                                return aName.localeCompare(bName, 'ko', { numeric: true })
-                              })
-                              .map((item) => {
-                                const isSelected = selectedItems.some(selected => selected.id === item.id)
-                                return (
-                                  <div
-                                    key={item.id}
-                                    className={cn(
-                                      "flex items-center space-x-2 p-2 rounded-md cursor-pointer hover:bg-muted",
-                                      isSelected && "bg-muted"
-                                    )}
-                                    onClick={() => handleItemToggle(item)}
-                                  >
-                                    <div className="flex items-center space-x-2 flex-1">
-                                      {isSelected ? (
-                                        <CheckSquare className="h-4 w-4" />
-                                      ) : (
-                                        <Square className="h-4 w-4" />
-                                      )}
-                                      <div className="flex-1">
-                                        <div className="font-medium">
-                                          {item.itemList || '아이템명 없음'}
-                                        </div>
-                                        <div className="text-sm text-muted-foreground">
-                                          {item.itemCode || '자재그룹코드 없음'}
-                                        </div>
-                                        <div className="text-xs text-muted-foreground">
-                                          공종: {item.workType} • 선종: {item.shipTypes}
-                                        </div>
-                                      </div>
-                                    </div>
-                                  </div>
-                                )
-                              })
-                          ) : (
-                            <div className="text-center py-8 text-muted-foreground">
-                              {itemSearchQuery ? "검색 결과가 없습니다" : "아이템이 없습니다"}
-                            </div>
-                          )}
-                        </div>
-                      </ScrollArea>
-                    </div>
-                    
-                    {/* 선택된 아이템 목록 */}
-                    <FormField
-                      control={form.control}
-                      name="materialCodes"
-                      render={() => (
-                        <FormItem>
-                          <FormLabel>선택된 아이템 ({selectedItems.length}개)</FormLabel>
-                          <div className="min-h-[80px] p-3 border rounded-md bg-muted/50">
-                            {selectedItems.length > 0 ? (
-                              <div className="flex flex-wrap gap-2">
-                                {selectedItems.map((item) => (
-                                  <Badge
-                                    key={item.id}
-                                    variant="secondary"
-                                    className="flex items-center gap-1"
-                                  >
-                                    {item.itemList || '아이템명 없음'} ({item.itemCode})
-                                    <X
-                                      className="h-3 w-3 cursor-pointer hover:text-destructive"
-                                      onClick={() => handleRemoveItem(item.id)}
-                                    />
-                                  </Badge>
-                                ))}
-                              </div>
-                            ) : (
-                              <div className="flex items-center justify-center h-full text-sm text-muted-foreground">
-                                선택된 아이템이 없습니다
-                              </div>
-                            )}
-                          </div>
-                          <FormMessage />
-                        </FormItem>
-                      )}
-                    />
-
-                    {/* RFQ 그룹핑 미리보기 */}
-                    <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
-                            }
-                          })
-
-                          return (
-                            <div className="space-y-3">
-                              <div className="text-sm text-muted-foreground p-3 border-b">
-                                총 {rfqGroups.length}개의 RFQ가 생성됩니다 (아이템명별로 그룹핑)
-                              </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>
-              </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>
-  )
-} 
\ No newline at end of file
-- 
cgit v1.2.3

