"use client" import * as React from "react" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" import { Download, FileText } from "lucide-react" import { toast } from "sonner" import { VendorPO, VendorPOItem } from "./types" import { getVendorPOItemsByContractNo } from "./service" import { formatNumber } from "@/lib/utils" import { getDownloadUrlByMaterialCode, checkPosFileExists } from "@/lib/pos" import { PosFileSelectionDialog } from "@/lib/pos/components/pos-file-selection-dialog" interface VendorPOItemsDialogProps { open: boolean onOpenChange: (open: boolean) => void po: VendorPO | null /** * 뷰어 타입 * - 'evcp': EVCP 사용자 (암호화된 파일 직접 다운로드) * - 'partners': 협력사 사용자 (복호화된 파일 다운로드) */ viewerType?: 'evcp' | 'partners' } export function VendorPOItemsDialog({ open, onOpenChange, po, viewerType = 'partners' }: VendorPOItemsDialogProps) { const [items, setItems] = React.useState([]) const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState(null) // 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) // 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) } // 상세품목 데이터 로드 React.useEffect(() => { if (!open || !po) { setItems([]) setError(null) return } const loadItems = async () => { setLoading(true) setError(null) try { const vendorPOItems = await getVendorPOItemsByContractNo(po.contractNo) setItems(vendorPOItems) } catch (err) { console.error("Failed to load vendor PO items:", err) setError("상세품목을 불러오는 중 오류가 발생했습니다.") } finally { setLoading(false) } } loadItems() }, [open, po]) if (!po) return null return ( 상세품목 현황 - {po.contractNo} {po.contractName} ({items.length}개 품목)
{loading ? (
상세품목을 불러오는 중...
{Array.from({ length: 3 }).map((_, i) => ( ))}
) : error ? (
{error}
) : items.length === 0 ? (
등록된 상세품목이 없습니다.
) : (
PO/계약번호 품번 P/R번호 PR 품번 자재그룹(명) {/* 단가기준 */} 자재번호 품목/자재내역 {/* 자재내역사양 설계자재번호 Fitting No. Cert. 재질 규격 */} 수량 수량단위 중량 중량단위 총중량 단가(NETPR) 단가단위(BPRME) 가격단위값(PEINH) 참조단가 발주단가 기본총액(BRTWR) 조정금액(ZPDT_EXDS_AMT) 최종정가(NETWR) POS 예정시작일자 납기일자 예정종료일자 PR납기일 구매접수일 세금코드 플랜트 저장위치 RFQ번호 RFQ품번 LOT No. 볼륨 볼륨단위 시리즈구분 비고 {/* 철의장 SPEC */} {/* P/R 담당자 */} {items.map((item, index) => ( {item.contractNo || '-'} {item.itemNo || '-'} {item.prNo || '-'} {item.prItemNo || '-'} {item.materialGroup || '-'} {/* {item.priceStandard || '-'} */} {item.materialNo || '-'}
{item.itemDescription || '-'}
{/* 자재내역사양~규격 까지 받은 정보 없음 */} {/*
{item.materialSpec || '-'}
{item.designMaterialNo || '-'} {item.fittingNo || '-'} {item.cert || '-'} {item.material || '-'} {item.specification || '-'} */} {item.quantity !== undefined && item.quantity !== null ? item.quantity.toLocaleString() : '-'} {item.quantityUnit || '-'} {item.weight ? item.weight.toLocaleString() : '-'} {item.weightUnit || '-'} {item.totalWeight ? item.totalWeight.toLocaleString() : '-'} {(() => { const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 return formatNumber(item.unitPrice, decimals) })()} {item.BPRME || '-'} {item.PEINH ?? '-'} {(() => { const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 return item.ZREF_NETPR ? formatNumber(item.ZREF_NETPR, decimals) : '-' })()} {(() => { const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 return item.ZNETPR ? formatNumber(item.ZNETPR, decimals) : '-' })()} {(() => { const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 return item.BRTWR ? formatNumber(item.BRTWR, decimals) : '-' })()} {(() => { const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 return item.ZPDT_EXDS_AMT ? formatNumber(item.ZPDT_EXDS_AMT, decimals) : '-' })()} {(() => { const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 return item.NETWR ? formatNumber(item.NETWR, decimals) : '-' })()} {item.materialNo ? ( ) : ( - )} {item.ZPLN_ST_DT || '-'} {item.ZPO_DLV_DT || item.deliveryDate || '-'} {item.ZPLN_ED_DT || '-'} {item.LFDAT || '-'} {item.ZRCV_DT || '-'} {item.vatType || '-'} {item.WERKS || '-'} {item.LGORT || '-'} {item.ANFNR || '-'} {item.ANFPS || '-'}
{item.ZPO_LOT_NO || '-'}
{item.VOLUM ? item.VOLUM.toLocaleString() : '-'} {item.VOLEH || '-'} {item.ZCON_IND || '-'}
{item.remark || '-'}
{/* {item.steelSpec || '-'} */} {/* {item.prManager || '-'} */}
))}
)}
{items.length > 0 && (
총 {items.length}개 품목
{(() => { // NETWR(최종정가) 합계 계산 const totalNETWR = items.reduce((sum, item) => sum + (item.NETWR || item.contractAmount || 0), 0) // 통화별 소수점 자리수 결정 (KRW, JPY: 0자리, 나머지: 2자리) const currency = po?.currency || '' const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2 const formattedAmount = formatNumber(totalNETWR, decimals) return `총 계약금액(NETWR): ${currency} ${formattedAmount}` })()}
)} {/* POS 파일 선택 다이얼로그 */}
) }