"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 import { getShipbuildingItemsByWorkType, searchShipbuildingItems, getWorkTypes, 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 // 공종 타입 정의 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(null) // 검색 및 필터링 상태 const [itemSearchQuery, setItemSearchQuery] = React.useState("") const [selectedWorkType, setSelectedWorkType] = React.useState(null) const [selectedItems, setSelectedItems] = React.useState([]) const [isSearchingItems, setIsSearchingItems] = React.useState(false) // 데이터 상태 const [workTypes, setWorkTypes] = React.useState([]) const [availableItems, setAvailableItems] = React.useState([]) const [isLoadingItems, setIsLoadingItems] = React.useState(false) // RFQ 생성 폼 const form = useForm({ resolver: zodResolver(createRfqSchema), defaultValues: { biddingProjectId: undefined, materialCodes: [], dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // 14일 후 } }) // 공종 목록 로드 React.useEffect(() => { const loadWorkTypes = async () => { const types = await getWorkTypes() setWorkTypes(types) } 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) } }, [itemSearchQuery, selectedWorkType]) // 아이템 검색 디바운스 React.useEffect(() => { setIsSearchingItems(true) const timer = setTimeout(() => { loadItems() setIsSearchingItems(false) }, 300) return () => clearTimeout(timer) }, [loadItems]) // 프로젝트 선택 처리 const handleProjectSelect = (project: Project) => { setSelectedProject(project) form.setValue("biddingProjectId", project.id) // 선택 초기화 setSelectedItems([]) 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("로그인이 필요합니다") } // 자재코드(item_code) 배열을 materialGroupCodes로 전달 const result = await createTechSalesRfq({ biddingProjectId: data.biddingProjectId, materialGroupCodes: data.materialCodes, // item_code를 자재코드로 사용 createdBy: Number(session.user.id), dueDate: data.dueDate, }) if (result.error) { throw new Error(result.error) } // 성공적으로 생성되면 다이얼로그 닫기 및 메시지 표시 toast.success(`${result.data?.length || 0}개의 RFQ가 성공적으로 생성되었습니다`) setIsDialogOpen(false) form.reset({ biddingProjectId: undefined, materialCodes: [], dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // 14일 후로 재설정 }) setSelectedProject(null) setItemSearchQuery("") setSelectedWorkType(null) setSelectedItems([]) setAvailableItems([]) // 생성 후 콜백 실행 if (onCreated) { onCreated() } } catch (error) { console.error("RFQ 생성 오류:", error) toast.error(`RFQ 생성 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`) } finally { setIsProcessing(false) } } return ( { setIsDialogOpen(open) if (!open) { form.reset({ biddingProjectId: undefined, materialCodes: [], dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // 14일 후로 재설정 }) setSelectedProject(null) setItemSearchQuery("") setSelectedWorkType(null) setSelectedItems([]) setAvailableItems([]) } }} > RFQ 생성
{/* 프로젝트 선택 */} ( 입찰 프로젝트 )} /> {/* 마감일 설정 */} ( 마감일 date < new Date() || date < new Date("1900-01-01") } initialFocus /> 벤더가 견적을 제출해야 하는 마감일입니다. )} /> {!selectedProject ? (
먼저 프로젝트를 선택해주세요
) : (
{/* 아이템 선택 영역 */}
조선 아이템 선택 공종별 아이템을 선택하세요
{/* 아이템 검색 및 필터 */}
setItemSearchQuery(e.target.value)} className="pl-8 pr-8" /> {itemSearchQuery && ( )} {isSearchingItems && ( )}
{/* 공종 필터 */} setSelectedWorkType(null)} > 전체 공종 {workTypes.map(workType => ( setSelectedWorkType(workType.code)} > {workType.name} ))}
{/* 아이템 목록 */}
{isLoadingItems ? (
아이템을 불러오는 중...
) : availableItems.length > 0 ? ( availableItems.map((item) => { const isSelected = selectedItems.some(selected => selected.id === item.id) return (
handleItemToggle(item)} >
{isSelected ? ( ) : ( )}
{item.itemList || item.itemName}
{item.itemCode} • {item.description || '설명 없음'}
공종: {item.workType} • 선종: {item.shipTypes}
) }) ) : (
{itemSearchQuery ? "검색 결과가 없습니다" : "아이템이 없습니다"}
)}
{/* 선택된 아이템 목록 */} ( 선택된 아이템 ({selectedItems.length}개)
{selectedItems.length > 0 ? (
{selectedItems.map((item) => ( {item.itemList || item.itemName} ({item.itemCode}) handleRemoveItem(item.id)} /> ))}
) : (
선택된 아이템이 없습니다
)}
)} />
)} {/* 안내 메시지 */} {selectedProject && (

• 공종별 조선 아이템을 선택하세요.

• 선택된 아이템의 자재코드(item_code)별로 개별 RFQ가 생성됩니다.

• 아이템 코드가 자재 그룹 코드로 사용됩니다.

• 마감일은 벤더가 견적을 제출해야 하는 날짜입니다.

)}
) }