diff options
Diffstat (limited to 'lib/rfq-last/table')
| -rw-r--r-- | lib/rfq-last/table/rfq-items-dialog.tsx | 150 |
1 files changed, 115 insertions, 35 deletions
diff --git a/lib/rfq-last/table/rfq-items-dialog.tsx b/lib/rfq-last/table/rfq-items-dialog.tsx index daa692e9..eb6c05b1 100644 --- a/lib/rfq-last/table/rfq-items-dialog.tsx +++ b/lib/rfq-last/table/rfq-items-dialog.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { format } from "date-fns" -import { Package, ExternalLink } from "lucide-react" +import { Package, ExternalLink, Download, FileText } from "lucide-react" import { Dialog, DialogContent, @@ -26,6 +26,8 @@ import { Separator } from "@/components/ui/separator" import { toast } from "sonner" import { RfqsLastView } from "@/db/schema" import { getRfqItemsAction } from "../service" +import { getDesignDocumentsForRfqItemsAction } from "@/lib/pos" +import { downloadFile } from "@/lib/file-download" // 품목 타입 interface RfqItem { @@ -82,35 +84,61 @@ export function RfqItemsDialog({ isOpen, onClose, rfqData }: RfqItemsDialogProps const [items, setItems] = React.useState<RfqItem[]>([]) const [statistics, setStatistics] = React.useState<ItemStatistics | null>(null) const [isLoading, setIsLoading] = React.useState(false) + // 자재코드별 설계 문서 매핑 + const [designDocuments, setDesignDocuments] = React.useState<Record<string, { + id: number; + fileName: string; + filePath: string; + fileSize: number | null; + fileType: string | null; + description: string | null; + }>>({}) + const [isLoadingDocs, setIsLoadingDocs] = React.useState(false) - // 품목 목록 로드 + // 품목 목록 및 설계 문서 로드 React.useEffect(() => { if (!isOpen || !rfqData.id) return - const loadItems = async () => { + const loadData = async () => { setIsLoading(true) + setIsLoadingDocs(true) + try { - const result = await getRfqItemsAction(rfqData.id) + // 1. 품목 목록 로드 + const itemsResult = await getRfqItemsAction(rfqData.id) - if (result.success) { - setItems(result.data) - setStatistics(result.statistics) + if (itemsResult.success) { + setItems(itemsResult.data) + setStatistics(itemsResult.statistics || null) } else { - toast.error(result.error || "품목을 불러오는데 실패했습니다") + toast.error(itemsResult.error || "품목을 불러오는데 실패했습니다") setItems([]) setStatistics(null) } + + // 2. 설계 문서 매핑 로드 + const docsResult = await getDesignDocumentsForRfqItemsAction(rfqData.id) + + if (docsResult.success && docsResult.documents) { + setDesignDocuments(docsResult.documents) + } else { + console.warn("설계 문서 매핑 로드 실패:", docsResult.error) + setDesignDocuments({}) + } + } catch (error) { - console.error("품목 로드 오류:", error) - toast.error("품목을 불러오는데 실패했습니다") + console.error("데이터 로드 오류:", error) + toast.error("데이터를 불러오는데 실패했습니다") setItems([]) setStatistics(null) + setDesignDocuments({}) } finally { setIsLoading(false) + setIsLoadingDocs(false) } } - loadItems() + loadData() }, [isOpen, rfqData.id]) // 사양서 링크 열기 @@ -118,6 +146,28 @@ export function RfqItemsDialog({ isOpen, onClose, rfqData }: RfqItemsDialogProps window.open(specUrl, '_blank', 'noopener,noreferrer') } + // 설계 문서 다운로드 + const handleDownloadDesignDoc = async (materialCode: string, fileName: string, filePath: string) => { + try { + await downloadFile(filePath, fileName, { + action: 'download', + showToast: true + }) + } catch (error) { + console.error("설계 문서 다운로드 오류:", error) + toast.error("설계 문서 다운로드에 실패했습니다") + } + } + + // 파일 크기 포맷팅 + const formatFileSize = (bytes: number | null) => { + if (!bytes) return "" + const sizes = ['B', 'KB', 'MB', 'GB'] + if (bytes === 0) return '0 B' + const i = Math.floor(Math.log(bytes) / Math.log(1024)) + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i] + } + // 수량 포맷팅 const formatQuantity = (quantity: number | null, uom: string | null) => { if (!quantity) return "-" @@ -181,7 +231,7 @@ export function RfqItemsDialog({ isOpen, onClose, rfqData }: RfqItemsDialogProps <TableHead className="w-[100px]">중량</TableHead> <TableHead className="w-[100px]">납기일</TableHead> <TableHead className="w-[100px]">PR번호</TableHead> - <TableHead className="w-[80px]">사양</TableHead> + <TableHead className="w-[120px]">사양/설계문서</TableHead> <TableHead>비고</TableHead> </TableRow> </TableHeader> @@ -217,7 +267,8 @@ export function RfqItemsDialog({ isOpen, onClose, rfqData }: RfqItemsDialogProps <TableHead className="w-[100px]">중량</TableHead> <TableHead className="w-[100px]">납기일</TableHead> <TableHead className="w-[100px]">PR번호</TableHead> - <TableHead className="w-[100px]">사양</TableHead> + <TableHead className="w-[100px]">PR 아이템 번호</TableHead> + <TableHead className="w-[120px]">사양/설계문서</TableHead> <TableHead className="w-[100px]">프로젝트</TableHead> <TableHead>비고</TableHead> </TableRow> @@ -278,36 +329,65 @@ export function RfqItemsDialog({ isOpen, onClose, rfqData }: RfqItemsDialogProps </span> </TableCell> <TableCell> - <div className="flex flex-col"> - <span className="text-xs font-mono">{item.prNo || "-"}</span> - {item.prItem && item.prItem !== item.prNo && ( - <span className="text-xs text-muted-foreground font-mono"> - {item.prItem} - </span> - )} - </div> + <span className="text-xs font-mono">{item.prNo || "-"}</span> </TableCell> <TableCell> - <div className="flex items-center gap-1"> - {item.specNo && ( - <span className="text-xs font-mono">{item.specNo}</span> - )} - {item.specUrl && ( - <Button - variant="ghost" - size="sm" - className="h-5 w-5 p-0" - onClick={() => handleOpenSpec(item.specUrl!)} - title="사양서 열기" - > - <ExternalLink className="h-3 w-3" /> - </Button> + <span className="text-xs font-mono">{item.prItem || "-"}</span> + </TableCell> + <TableCell> + <div className="flex flex-col gap-1"> + {/* 기존 스펙 정보 */} + <div className="flex items-center gap-1"> + {item.specNo && ( + <span className="text-xs font-mono">{item.specNo}</span> + )} + {item.specUrl && ( + <Button + variant="ghost" + size="sm" + className="h-5 w-5 p-0" + onClick={() => handleOpenSpec(item.specUrl!)} + title="사양서 열기" + > + <ExternalLink className="h-3 w-3" /> + </Button> + )} + </div> + + {/* 설계 문서 다운로드 */} + {item.materialCode && designDocuments[item.materialCode] && ( + <div className="flex items-center gap-1"> + <FileText className="h-3 w-3 text-blue-500" /> + <Button + variant="ghost" + size="sm" + className="h-5 p-1 text-xs text-blue-600 hover:text-blue-800" + onClick={() => handleDownloadDesignDoc( + item.materialCode!, + designDocuments[item.materialCode!].fileName, + designDocuments[item.materialCode!].filePath + )} + title={`설계문서: ${designDocuments[item.materialCode!].fileName} (${formatFileSize(designDocuments[item.materialCode!].fileSize)})`} + > + <Download className="h-3 w-3 mr-1" /> + 설계문서 + </Button> + </div> )} + + {/* 트래킹 번호 */} {item.trackingNo && ( <div className="text-xs text-muted-foreground"> TRK: {item.trackingNo} </div> )} + + {/* 설계 문서 로딩 상태 */} + {isLoadingDocs && item.materialCode && ( + <div className="text-xs text-muted-foreground"> + 설계문서 확인 중... + </div> + )} </div> </TableCell> <TableCell> |
