summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor/components/pr-items-pricing-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/vendor/components/pr-items-pricing-table.tsx')
-rw-r--r--lib/bidding/vendor/components/pr-items-pricing-table.tsx195
1 files changed, 138 insertions, 57 deletions
diff --git a/lib/bidding/vendor/components/pr-items-pricing-table.tsx b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
index 320ed6eb..01885f7a 100644
--- a/lib/bidding/vendor/components/pr-items-pricing-table.tsx
+++ b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
@@ -22,8 +22,14 @@ import {
Calculator
} from 'lucide-react'
import { formatDate } from '@/lib/utils'
-import { downloadFile } from '@/lib/file-download'
+import { downloadFile, formatFileSize, getFileInfo } from '@/lib/file-download'
import { getSpecDocumentsForPrItem } from '../../pre-quote/service'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip'
interface PrItem {
id: number
@@ -57,6 +63,93 @@ interface SpecDocument {
uploadedAt: string
}
+// 파일 다운로드 훅
+const useFileDownload = () => {
+ const [downloadingFiles, setDownloadingFiles] = React.useState<Set<string>>(new Set())
+
+ const handleDownload = async (filePath: string, fileName: string, options?: {
+ action?: 'download' | 'preview'
+ }) => {
+ const fileKey = `${filePath}_${fileName}`
+ if (downloadingFiles.has(fileKey)) return
+
+ setDownloadingFiles(prev => new Set(prev).add(fileKey))
+
+ try {
+ await downloadFile(filePath, fileName, {
+ action: options?.action || 'download',
+ showToast: true,
+ showSuccessToast: true,
+ onError: (error) => {
+ console.error("파일 다운로드 실패:", error)
+ },
+ onSuccess: (fileName, fileSize) => {
+ console.log("파일 다운로드 성공:", fileName, fileSize ? formatFileSize(fileSize) : '')
+ }
+ })
+ } catch (error) {
+ console.error("다운로드 처리 중 오류:", error)
+ } finally {
+ setDownloadingFiles(prev => {
+ const newSet = new Set(prev)
+ newSet.delete(fileKey)
+ return newSet
+ })
+ }
+ }
+
+ return { handleDownload, downloadingFiles }
+}
+
+// 파일 다운로드 링크 컴포넌트
+interface FileDownloadLinkProps {
+ filePath: string
+ fileName: string
+ fileSize?: number | null
+ title?: string | null
+ className?: string
+}
+
+const FileDownloadLink: React.FC<FileDownloadLinkProps> = ({
+ filePath,
+ fileName,
+ fileSize,
+ title,
+ className = ""
+}) => {
+ const { handleDownload, downloadingFiles } = useFileDownload()
+ const fileInfo = getFileInfo(fileName)
+ const fileKey = `${filePath}_${fileName}`
+ const isDownloading = downloadingFiles.has(fileKey)
+
+ return (
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <button
+ onClick={() => handleDownload(filePath, fileName)}
+ disabled={isDownloading}
+ className={`inline-flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 hover:underline disabled:opacity-50 disabled:cursor-not-allowed ${className}`}
+ >
+ <span className="text-xs">{fileInfo.icon}</span>
+ <span className="truncate max-w-[150px]">
+ {isDownloading ? "다운로드 중..." : (title || fileName)}
+ </span>
+ <Download className="h-3 w-3 opacity-60" />
+ </button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <div className="text-xs">
+ <div className="font-medium">{fileName}</div>
+ {fileSize && <div className="text-muted-foreground">{formatFileSize(fileSize)}</div>}
+ <div className="text-muted-foreground">클릭하여 다운로드</div>
+ </div>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ )
+}
+
interface PrItemsPricingTableProps {
prItems: PrItem[]
initialQuotations?: PrItemQuotation[]
@@ -76,9 +169,8 @@ export function PrItemsPricingTable({
}: PrItemsPricingTableProps) {
const [quotations, setQuotations] = React.useState<PrItemQuotation[]>([])
const [specDocuments, setSpecDocuments] = React.useState<Record<number, SpecDocument[]>>({})
- const [loadingSpecs, setLoadingSpecs] = React.useState<Record<number, boolean>>({})
- // 초기 견적 데이터 설정
+ // 초기 견적 데이터 설정 및 SPEC 문서 로드
React.useEffect(() => {
const initQuotations = prItems.map(item => {
const existing = initialQuotations.find(q => q.prItemId === item.id)
@@ -94,27 +186,34 @@ export function PrItemsPricingTable({
}
})
setQuotations(initQuotations)
- }, [prItems, initialQuotations])
- // SPEC 문서 로드
- const loadSpecDocuments = async (prItemId: number) => {
- if (loadingSpecs[prItemId]) return
-
- setLoadingSpecs(prev => ({ ...prev, [prItemId]: true }))
- try {
- const docs = await getSpecDocumentsForPrItem(prItemId)
- // Date를 string으로 변환
- const mappedDocs = docs.map(doc => ({
- ...doc,
- uploadedAt: doc.uploadedAt.toString()
- }))
- setSpecDocuments(prev => ({ ...prev, [prItemId]: mappedDocs }))
- } catch (error) {
- console.error('Failed to load spec documents:', error)
- } finally {
- setLoadingSpecs(prev => ({ ...prev, [prItemId]: false }))
+ // SPEC 문서가 있는 모든 PR 아이템의 문서를 미리 로드
+ const loadAllSpecDocuments = async () => {
+ const itemsWithSpecs = prItems.filter(item => item.hasSpecDocument)
+ console.log('Loading spec documents for items:', itemsWithSpecs.map(item => ({ id: item.id, itemNumber: item.itemNumber })))
+
+ for (const item of itemsWithSpecs) {
+ try {
+ console.log('Loading spec documents for prItemId:', item.id)
+ const docs = await getSpecDocumentsForPrItem(item.id)
+ console.log('Loaded spec documents for item', item.id, ':', docs)
+
+ // Date를 string으로 변환
+ const mappedDocs = docs.map(doc => ({
+ ...doc,
+ uploadedAt: doc.uploadedAt.toString()
+ }))
+
+ setSpecDocuments(prev => ({ ...prev, [item.id]: mappedDocs }))
+ } catch (error) {
+ console.error('Failed to load spec documents for item', item.id, ':', error)
+ }
+ }
}
- }
+
+ loadAllSpecDocuments()
+ }, [prItems, initialQuotations])
+
// 견적 데이터 업데이트
const updateQuotation = (prItemId: number, field: keyof PrItemQuotation, value: any) => {
@@ -142,16 +241,6 @@ export function PrItemsPricingTable({
onTotalAmountChange(totalAmount)
}
- // 파일 다운로드
- const handleDownloadSpec = async (document: SpecDocument) => {
- try {
- await downloadFile(document.filePath, document.originalFileName, {
- showToast: true
- })
- } catch (error) {
- console.error('Failed to download spec document:', error)
- }
- }
// 통화 포맷팅
const formatCurrency = (amount: number) => {
@@ -187,7 +276,7 @@ export function PrItemsPricingTable({
<TableHead>견적단가</TableHead>
<TableHead>견적금액</TableHead>
<TableHead>납품예정일</TableHead>
- <TableHead>기술사양</TableHead>
+ {/* <TableHead>기술사양</TableHead> */}
<TableHead>SPEC</TableHead>
</TableRow>
</TableHeader>
@@ -262,7 +351,7 @@ export function PrItemsPricingTable({
/>
)}
</TableCell>
- <TableCell>
+ {/* <TableCell>
{readOnly ? (
<div className="max-w-32 truncate" title={quotation.technicalSpecification || ''}>
{quotation.technicalSpecification || '-'}
@@ -280,37 +369,29 @@ export function PrItemsPricingTable({
rows={2}
/>
)}
- </TableCell>
+ </TableCell> */}
<TableCell>
{item.hasSpecDocument ? (
<div className="space-y-1">
- {!specDocuments[item.id] ? (
- <Button
- variant="outline"
- size="sm"
- onClick={() => loadSpecDocuments(item.id)}
- disabled={loadingSpecs[item.id]}
- >
- <FileText className="w-3 h-3 mr-1" />
- {loadingSpecs[item.id] ? '로딩...' : 'SPEC 보기'}
- </Button>
- ) : specDocuments[item.id].length > 0 ? (
+ {specDocuments[item.id] && specDocuments[item.id].length > 0 ? (
<div className="space-y-1">
{specDocuments[item.id].map((doc) => (
- <Button
- key={doc.id}
- variant="outline"
- size="sm"
- onClick={() => handleDownloadSpec(doc)}
- className="block text-xs"
- >
- <Download className="w-3 h-3 mr-1" />
- {doc.originalFileName}
- </Button>
+ <div key={doc.id} className="text-xs">
+ <FileDownloadLink
+ filePath={doc.filePath}
+ fileName={doc.originalFileName}
+ fileSize={doc.fileSize}
+ title={doc.title}
+ className="text-xs"
+ />
+ </div>
))}
</div>
) : (
- <Badge variant="secondary">문서 없음</Badge>
+ <div className="flex items-center gap-2">
+ <Badge variant="secondary">문서 없음</Badge>
+ <span className="text-xs text-muted-foreground">로딩 중...</span>
+ </div>
)}
</div>
) : (