"use client" import * as React from "react" import { format } from "date-fns" import { Package, ExternalLink, Download, FileText } from "lucide-react" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Skeleton } from "@/components/ui/skeleton" import { Separator } from "@/components/ui/separator" import { toast } from "sonner" import { RfqsLastView, VendorQuotationView } from "@/db/schema" import { getRfqItemsAction } from "../service" import { getDownloadUrlByMaterialCode, checkPosFileExists } from "@/lib/pos" import { PosFileSelectionDialog } from "@/lib/pos/components/pos-file-selection-dialog" // 품목 타입 interface RfqItem { id: number rfqsLastId: number | null rfqItem: string | null prItem: string | null prNo: string | null materialCode: string | null materialCategory: string | null acc: string | null materialDescription: string | null size: string | null deliveryDate: Date | null quantity: number | null uom: string | null grossWeight: number | null gwUom: string | null specNo: string | null specUrl: string | null trackingNo: string | null majorYn: boolean | null remark: string | null projectDef: string | null projectSc: string | null projectKl: string | null projectLc: string | null projectDl: string | null // RFQ 관련 정보 rfqCode: string | null rfqType: string | null rfqTitle: string | null itemCode: string | null itemName: string | null projectCode: string | null projectName: string | null } interface ItemStatistics { total: number major: number regular: number totalQuantity: number totalWeight: number } interface RfqItemsDialogProps { isOpen: boolean onClose: () => void rfqData: RfqsLastView | VendorQuotationView /** * 뷰어 타입 * - 'evcp': EVCP 사용자 (암호화된 파일 직접 다운로드) * - 'partners': 협력사 사용자 (복호화된 파일 다운로드) */ viewerType?: 'evcp' | 'partners' } export function RfqItemsDialog({ isOpen, onClose, rfqData, viewerType = 'evcp' }: RfqItemsDialogProps) { const [items, setItems] = React.useState([]) const [statistics, setStatistics] = React.useState(null) const [isLoading, setIsLoading] = React.useState(false) // POS 파일 선택 다이얼로그 상태 const [posDialogOpen, setPosDialogOpen] = React.useState(false) const [selectedMaterialCode, setSelectedMaterialCode] = React.useState("") const [posFiles, setPosFiles] = React.useState>([]) const [loadingPosFiles, setLoadingPosFiles] = React.useState(false) const [downloadingFileIndex, setDownloadingFileIndex] = React.useState(null) // 품목 목록 로드 React.useEffect(() => { if (!isOpen || !rfqData.id) return const loadData = async () => { setIsLoading(true) try { // 품목 목록 로드 const itemsResult = await getRfqItemsAction(rfqData.id) if (itemsResult.success) { setItems(itemsResult.data) setStatistics(itemsResult.statistics || null) } else { toast.error(itemsResult.error || "품목을 불러오는데 실패했습니다") setItems([]) setStatistics(null) } } catch (error) { console.error("데이터 로드 오류:", error) toast.error("데이터를 불러오는데 실패했습니다") setItems([]) setStatistics(null) } finally { setIsLoading(false) } } loadData() }, [isOpen, rfqData.id]) // 사양서 링크 열기 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) { // 파일 정보를 상세하게 가져오기 위해 getDownloadUrlByMaterialCode 사용 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}` }) // viewerType에 따라 다른 엔드포인트 사용 const endpoint = viewerType === 'partners' ? `/api/pos/download-on-demand-partners` // 복호화 포함 : `/api/pos/download-on-demand` // 암호화 파일 그대로 const downloadUrl = `${endpoint}?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) } return ( 견적 품목 목록 {rfqData.rfqCode} - {rfqData.rfqTitle || rfqData.itemName || "품목 정보"} {/* 통계 정보 */} {statistics && !isLoading && ( <>
{statistics.total}
전체 품목
{statistics.regular}
일반 품목
{statistics.totalQuantity.toLocaleString()}
총 수량
{statistics.totalWeight.toLocaleString()}
총 중량 (KG)
)} {isLoading ? ( 아이템 자재코드 자재명 수량 수량단위 중량 중량단위 납기일 PR번호 사양/설계문서 비고 {[...Array(3)].map((_, i) => ( ))}
) : items.length === 0 ? (

품목이 없습니다.

) : ( 아이템 자재코드 자재명 수량 수량단위 중량 중량단위 납기일 PR번호 PR 아이템 번호 사양/설계문서 프로젝트 비고 {items.map((item, index) => (
#{index + 1} {item.majorYn && ( 주요 )}
{item.materialCode || "-"} {item.acc && ( ACC: {item.acc} )}
{item.materialDescription || "-"} {item.materialCategory && ( {item.materialCategory} )} {item.size && ( 크기: {item.size} )}
{item.quantity ? item.quantity.toLocaleString() : "-"} {item.uom || "-"} {item.grossWeight ? item.grossWeight.toLocaleString() : "-"} {item.gwUom || "-"} {item.deliveryDate ? format(new Date(item.deliveryDate), "yyyy-MM-dd") : "-"} {item.prNo || "-"} {item.prItem || "-"}
{/* 기존 스펙 정보 */}
{item.specNo && ( {item.specNo} )} {item.specUrl && ( )}
{/* POS 파일 다운로드 */} {item.materialCode && (
)} {/* 트래킹 번호 */} {item.trackingNo && (
TRK: {item.trackingNo}
)}
{[ item.projectDef && `${item.projectDef}`, item.projectSc && `SC: ${item.projectSc}`, item.projectKl && `KL: ${item.projectKl}`, item.projectLc && `LC: ${item.projectLc}`, item.projectDl && `DL: ${item.projectDl}` ].filter(Boolean).join(" | ") || "-"}
{item.remark ? (item.remark.length > 30 ? `${item.remark.slice(0, 30)}...` : item.remark) : "-"}
))}
)}
{/* 하단 통계 정보 */} {statistics && !isLoading && (
총 {statistics.total}개 품목 전체 수량: {statistics.totalQuantity.toLocaleString()} | 전체 중량: {statistics.totalWeight.toLocaleString()} KG
)} {/* POS 파일 선택 다이얼로그 */}
) }