"use client" import { useFormContext, useFieldArray } from "react-hook-form" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Checkbox } from "@/components/ui/checkbox" import { Button } from "@/components/ui/button" import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { Badge } from "@/components/ui/badge" import { Label } from "@/components/ui/label" import { Separator } from "@/components/ui/separator" import { ScrollArea } from "@/components/ui/scroll-area" import { CalendarIcon, Eye, FileText, Download, ExternalLink } from "lucide-react" import { format } from "date-fns" import { cn, formatCurrency } from "@/lib/utils" import { useState, useEffect } from "react" import { toast } from "sonner" import { checkPosFileExists, getDownloadUrlByMaterialCode } from "@/lib/pos" import { PosFileSelectionDialog } from "@/lib/pos/components/pos-file-selection-dialog" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" interface QuotationItemsTableProps { prItems: any[] } export default function QuotationItemsTable({ prItems }: QuotationItemsTableProps) { const { control, register, setValue, watch } = useFormContext() const { fields } = useFieldArray({ control, name: "quotationItems" }) const [selectedItem, setSelectedItem] = useState(null) const [showDetail, setShowDetail] = useState(false) const [showBulkDateDialog, setShowBulkDateDialog] = useState(false) const [bulkDeliveryDate, setBulkDeliveryDate] = useState(undefined) // POS 파일 관련 상태 const [posDialogOpen, setPosDialogOpen] = useState(false) const [selectedMaterialCode, setSelectedMaterialCode] = useState("") const [posFiles, setPosFiles] = useState>([]) const [loadingPosFiles, setLoadingPosFiles] = useState(false) const [downloadingFileIndex, setDownloadingFileIndex] = useState(null) const currency = watch("vendorCurrency") || "USD" const quotationItems = watch("quotationItems") console.log(prItems,"prItems") // PR 아이템 정보를 quotationItems에 초기화 useEffect(() => { if (prItems && prItems.length > 0) { prItems.forEach((prItem, index) => { // PR 아이템 정보를 quotationItem에 포함 setValue(`quotationItems.${index}.prNo`, prItem.prNo) setValue(`quotationItems.${index}.materialCode`, prItem.materialCode) setValue(`quotationItems.${index}.materialDescription`, prItem.materialDescription) setValue(`quotationItems.${index}.quantity`, prItem.quantity) setValue(`quotationItems.${index}.uom`, prItem.uom) setValue(`quotationItems.${index}.rfqPrItemId`, prItem.id) // currency는 vendorCurrency를 따름 setValue(`quotationItems.${index}.currency`, currency) }) } }, [prItems, setValue, currency]) // 단가 * 수량 계산 const calculateTotal = (index: number) => { const item = quotationItems[index] const prItem = prItems[index] if (item && prItem) { const total = (item.unitPrice || 0) * (prItem.quantity || 0) setValue(`quotationItems.${index}.totalPrice`, total) // PR 아이템 정보도 함께 업데이트 (값이 변경되었을 수 있음) setValue(`quotationItems.${index}.prNo`, prItem.prNo) setValue(`quotationItems.${index}.materialCode`, prItem.materialCode) setValue(`quotationItems.${index}.materialDescription`, prItem.materialDescription) setValue(`quotationItems.${index}.quantity`, prItem.quantity) setValue(`quotationItems.${index}.uom`, prItem.uom) } } // 일괄 납기일 적용 const applyBulkDeliveryDate = () => { if (bulkDeliveryDate && fields.length > 0) { fields.forEach((_, index) => { setValue(`quotationItems.${index}.vendorDeliveryDate`, bulkDeliveryDate) }) setShowBulkDateDialog(false) setBulkDeliveryDate(undefined) } } // 납기일 초기화 const clearAllDeliveryDates = () => { fields.forEach((_, index) => { setValue(`quotationItems.${index}.vendorDeliveryDate`, undefined) }) } // 사양서 링크 열기 const handleOpenSpec = (specUrl: string) => { window.open(specUrl, '_blank', 'noopener,noreferrer') } // POS 파일 목록 조회 및 다이얼로그 열기 const handleOpenPosDialog = async (materialCode: string) => { if (!materialCode) { toast.error("자재코드가 없습니다") return } setLoadingPosFiles(true) setSelectedMaterialCode(materialCode) try { toast.loading(`POS 파일 목록 조회 중... (${materialCode})`, { id: `pos-check-${materialCode}` }) const result = await checkPosFileExists(materialCode) if (result.exists && result.files && result.files.length > 0) { const detailResult = await getDownloadUrlByMaterialCode(materialCode) if (detailResult.success && detailResult.availableFiles) { setPosFiles(detailResult.availableFiles) setPosDialogOpen(true) toast.success(`${result.fileCount}개의 POS 파일을 찾았습니다`, { id: `pos-check-${materialCode}` }) } else { toast.error('POS 파일 정보를 가져올 수 없습니다', { id: `pos-check-${materialCode}` }) } } else { toast.error(result.error || 'POS 파일을 찾을 수 없습니다', { id: `pos-check-${materialCode}` }) } } catch (error) { console.error("POS 파일 조회 오류:", error) toast.error("POS 파일 조회에 실패했습니다", { id: `pos-check-${materialCode}` }) } finally { setLoadingPosFiles(false) } } // POS 파일 다운로드 실행 const handleDownloadPosFile = async (fileIndex: number, fileName: string) => { if (!selectedMaterialCode) return setDownloadingFileIndex(fileIndex) try { toast.loading(`POS 파일 다운로드 준비 중...`, { id: `download-${fileIndex}` }) const downloadUrl = `/api/pos/download-on-demand?materialCode=${encodeURIComponent(selectedMaterialCode)}&fileIndex=${fileIndex}` toast.success(`POS 파일 다운로드 시작: ${fileName}`, { id: `download-${fileIndex}` }) window.open(downloadUrl, '_blank', 'noopener,noreferrer') setTimeout(() => { setDownloadingFileIndex(null) }, 1000) } catch (error) { console.error("POS 파일 다운로드 오류:", error) toast.error("POS 파일 다운로드에 실패했습니다", { id: `download-${fileIndex}` }) setDownloadingFileIndex(null) } } // POS 다이얼로그 닫기 const handleClosePosDialog = () => { setPosDialogOpen(false) setSelectedMaterialCode("") setPosFiles([]) setDownloadingFileIndex(null) } const totalAmount = quotationItems?.reduce( (sum: number, item: any) => sum + (item.totalPrice || 0), 0 ) || 0 // 상세 정보 다이얼로그 const ItemDetailDialog = ({ item, prItem, index }: any) => { const [localDeviationReason, setLocalDeviationReason] = useState("") const [localItemRemark, setLocalItemRemark] = useState("") const [localTechnicalCompliance, setLocalTechnicalCompliance] = useState(false) const [localAlternativeProposal, setLocalAlternativeProposal] = useState("") // 다이얼로그가 열릴 때 기존 값으로 초기화 useEffect(() => { if (item) { setLocalDeviationReason(item.deviationReason || "") setLocalItemRemark(item.itemRemark || "") setLocalTechnicalCompliance(item.technicalCompliance || false) setLocalAlternativeProposal(item.alternativeProposal || "") } }, [item]) // 저장 버튼 클릭 핸들러 const handleSaveDetail = () => { setValue(`quotationItems.${index}.deviationReason`, localDeviationReason) setValue(`quotationItems.${index}.itemRemark`, localItemRemark) setValue(`quotationItems.${index}.technicalCompliance`, localTechnicalCompliance) setValue(`quotationItems.${index}.alternativeProposal`, localAlternativeProposal) setShowDetail(false) } // 취소 버튼 클릭 핸들러 const handleCancelDetail = () => { setShowDetail(false) } return ( !open && setShowDetail(false)}> 견적 상세 정보 {prItem.materialCode} - {prItem.materialDescription}
{/* PR 아이템 정보 */} PR 아이템 정보

{prItem.prNo}

{prItem.materialCode}

{prItem.quantity} {prItem.uom}

{prItem.deliveryDate ? format(new Date(prItem.deliveryDate), "yyyy-MM-dd") : '-'}

{prItem.specNo && (

{prItem.specNo}

)} {prItem.trackingNo && (

{prItem.trackingNo}

)}
{/* 제조사 정보 */} {/* 제조사 정보
*/} {/* 기술 준수 및 대안 */} 기술 사양
setLocalTechnicalCompliance(checked === true)} />
{!localTechnicalCompliance && (