"use client" import * as React from "react" import { format } from "date-fns" import { Download, FileText, Eye, Loader2 } 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 { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { ScrollArea } from "@/components/ui/scroll-area" import { Skeleton } from "@/components/ui/skeleton" import { toast } from "sonner" import { RfqsLastView } from "@/db/schema" import { getRfqAttachmentsAction } from "../service" import { downloadFile, quickPreview, formatFileSize, getFileInfo } from "@/lib/file-download" // import { syncRfqPosFiles } from "@/lib/pos" // 주석 처리: ECC 매핑 시 자동 처리로 변경 // import { useSession } from "next-auth/react" // 주석 처리: 동기화 UI 제거로 불필요 // 첨부파일 타입 interface RfqAttachment { attachmentId: number attachmentType: string serialNo: string description: string | null currentRevision: string fileName: string originalFileName: string filePath: string fileSize: number | null fileType: string | null createdByName: string | null createdAt: Date | null updatedAt: Date | null revisionComment?: string | null } interface RfqAttachmentsDialogProps { isOpen: boolean onClose: () => void rfqData: RfqsLastView } export function RfqAttachmentsDialog({ isOpen, onClose, rfqData }: RfqAttachmentsDialogProps) { const [attachments, setAttachments] = React.useState([]) const [isLoading, setIsLoading] = React.useState(false) const [downloadingFiles, setDownloadingFiles] = React.useState>(new Set()) // const [isSyncing, setIsSyncing] = React.useState(false) // 주석 처리: 동기화 UI 제거 // const { data: session } = useSession() // 주석 처리: 동기화 UI 제거로 불필요 // 첨부파일 목록 로드 React.useEffect(() => { if (!isOpen || !rfqData.id) return const loadAttachments = async () => { setIsLoading(true) try { const result = await getRfqAttachmentsAction(rfqData.id) if (result.success) { setAttachments(result.data) } else { toast.error(result.error || "첨부파일을 불러오는데 실패했습니다") setAttachments([]) } } catch (error) { console.error("첨부파일 로드 오류:", error) toast.error("첨부파일을 불러오는데 실패했습니다") setAttachments([]) } finally { setIsLoading(false) } } loadAttachments() }, [isOpen, rfqData.id]) // 파일 다운로드 핸들러 const handleDownload = async (attachment: RfqAttachment) => { const attachmentId = attachment.attachmentId setDownloadingFiles(prev => new Set([...prev, attachmentId])) try { // POS 파일은 filePath가 uploads/pos/로 시작하는 파일들 if (attachment.filePath.startsWith('uploads/pos/')) { const downloadUrl = `/api/pos/download?revisionId=${attachment.attachmentId}` const link = document.createElement('a') link.href = downloadUrl link.download = attachment.originalFileName document.body.appendChild(link) link.click() document.body.removeChild(link) toast.success(`${attachment.originalFileName} 다운로드가 시작되었습니다.`) } else { // 일반 파일은 기존 downloadFile 함수 사용 const result = await downloadFile( attachment.filePath, attachment.originalFileName, { action: 'download', showToast: true, showSuccessToast: true, onSuccess: (fileName, fileSize) => { console.log(`다운로드 완료: ${fileName} (${formatFileSize(fileSize || 0)})`) }, onError: (error) => { console.error(`다운로드 실패: ${error}`) } } ) if (!result.success) { console.error("다운로드 결과:", result) } } } catch (error) { console.error("파일 다운로드 오류:", error) toast.error("파일 다운로드에 실패했습니다") } finally { setDownloadingFiles(prev => { const newSet = new Set(prev) newSet.delete(attachmentId) return newSet }) } } // 파일 미리보기 핸들러 const handlePreview = async (attachment: RfqAttachment) => { const fileInfo = getFileInfo(attachment.originalFileName) const isPosFile = attachment.filePath.startsWith('uploads/pos/') // POS 파일은 미리보기를 지원하지 않음 if (isPosFile || !fileInfo.canPreview) { const message = isPosFile ? "POS 파일은 미리보기를 지원하지 않습니다. 다운로드를 진행합니다." : "이 파일 형식은 미리보기를 지원하지 않습니다. 다운로드를 진행합니다." toast.info(message) return handleDownload(attachment) } try { const result = await quickPreview(attachment.filePath, attachment.originalFileName) if (!result.success) { console.error("미리보기 결과:", result) } } catch (error) { console.error("파일 미리보기 오류:", error) toast.error("파일 미리보기에 실패했습니다") } } // 스마트 파일 액션 (미리보기 가능하면 미리보기, 아니면 다운로드) // const handleSmartAction = async (attachment: RfqAttachment) => { // const attachmentId = attachment.attachmentId // const fileInfo = getFileInfo(attachment.originalFileName) // const isPosFile = attachment.filePath.startsWith('uploads/pos/') // // // POS 파일은 미리보기를 지원하지 않으므로 바로 다운로드 // if (isPosFile) { // return handleDownload(attachment) // } // // if (fileInfo.canPreview) { // return handlePreview(attachment) // } else { // return handleDownload(attachment) // } // } // POS 파일 동기화 핸들러 - 주석 처리: ECC 매핑 시 자동 처리로 변경 // const handlePosSync = async () => { // if (!session?.user?.id || !rfqData.id) { // toast.error("로그인이 필요하거나 RFQ 정보가 없습니다") // return // } // setIsSyncing(true) // try { // const result = await syncRfqPosFiles(rfqData.id, parseInt(session.user.id)) // if (result.success) { // toast.success( // `POS 파일 동기화 완료: 성공 ${result.successCount}건, 실패 ${result.failedCount}건` // ) // // 성공한 경우 첨부파일 목록 새로고침 // if (result.successCount > 0) { // const refreshResult = await getRfqAttachmentsAction(rfqData.id) // if (refreshResult.success) { // setAttachments(refreshResult.data) // } // } // // 상세 결과 표시 // if (result.details.length > 0) { // const failedItems = result.details.filter(d => d.status === 'failed') // if (failedItems.length > 0) { // console.warn("POS 동기화 실패 항목:", failedItems) // } // } // } else { // toast.error(`POS 파일 동기화 실패: ${result.errors.join(', ')}`) // } // } catch (error) { // console.error("POS 동기화 오류:", error) // toast.error("POS 파일 동기화 중 오류가 발생했습니다") // } finally { // setIsSyncing(false) // } // } // 첨부파일 타입별 색상 const getAttachmentTypeBadgeVariant = (type: string) => { switch (type.toLowerCase()) { case "견적요청서": return "default" case "기술사양서": return "secondary" case "도면": return "outline" default: return "outline" } } return (
견적 첨부파일 {rfqData.rfqCode} - {rfqData.rfqTitle || rfqData.itemName || "견적"} {attachments.length > 0 && ` (${attachments.length}개 파일)`}
{/* POS 동기화 버튼 - 주석 처리: ECC 매핑 시 자동 처리로 변경 */} {/* */}
{isLoading ? (
{[...Array(3)].map((_, i) => (
))}
) : ( 타입 파일명 {/* 설명 */} 리비전 크기 생성자 생성일 액션 {attachments.length === 0 ? (
첨부된 파일이 없습니다.
) : ( attachments.map((attachment) => { const fileInfo = getFileInfo(attachment.originalFileName) const isDownloading = downloadingFiles.has(attachment.attachmentId) return ( {attachment.attachmentType}
{fileInfo.icon}
{attachment.originalFileName}
{/* {attachment.description || "-"} {attachment.revisionComment && (
{attachment.revisionComment}
)}
*/} {attachment.currentRevision} {attachment.fileSize ? formatFileSize(attachment.fileSize) : "-"} {attachment.createdByName || "-"} {attachment.createdAt ? format(new Date(attachment.createdAt), "MM-dd HH:mm") : "-"}
{/* 미리보기 버튼 (미리보기 가능한 파일만) */} {fileInfo.canPreview && ( )} {/* 다운로드 버튼 */} {/* 스마트 액션 버튼 (메인 액션) */} {/* */}
) }) )}
)}
{/* 하단 정보 */} {attachments.length > 0 && !isLoading && (
총 {attachments.length}개 파일 {attachments.some(a => a.fileSize) && ` · 전체 크기: ${formatFileSize( attachments.reduce((sum, a) => sum + (a.fileSize || 0), 0) )}` }
)}
) }