summaryrefslogtreecommitdiff
path: root/lib/rfq-last/table/rfq-items-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/table/rfq-items-dialog.tsx')
-rw-r--r--lib/rfq-last/table/rfq-items-dialog.tsx150
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>