'use client' import * as React from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, } from '@/components/ui/dialog' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Package, FileText, Download, Calculator, Save } from 'lucide-react' import { formatDate } from '@/lib/utils' import { downloadFile } from '@/lib/file-download' import { getSpecDocumentsForPrItem } from '../../pre-quote/service' import { useToast } from '@/hooks/use-toast' interface PrItem { id: number itemNumber: string | null prNumber: string | null itemInfo: string | null materialDescription: string | null quantity: string | null quantityUnit: string | null currency: string | null requestedDeliveryDate: string | null hasSpecDocument: boolean | null } interface PrItemQuotation { prItemId: number bidUnitPrice: number bidAmount: number proposedDeliveryDate?: string technicalSpecification?: string } interface SpecDocument { id: number fileName: string originalFileName: string fileSize: number | null filePath: string title: string | null description: string | null uploadedAt: string } interface PrItemsPricingDialogProps { prItems: PrItem[] initialQuotations?: PrItemQuotation[] currency?: string onSave: (quotations: PrItemQuotation[], totalAmount: number) => void readOnly?: boolean children: React.ReactNode } export function PrItemsPricingDialog({ prItems, initialQuotations = [], currency = 'KRW', onSave, readOnly = false, children }: PrItemsPricingDialogProps) { const { toast } = useToast() const [open, setOpen] = React.useState(false) const [quotations, setQuotations] = React.useState([]) const [specDocuments, setSpecDocuments] = React.useState>({}) const [loadingSpecs, setLoadingSpecs] = React.useState>({}) // 다이얼로그 열릴 때 초기 견적 데이터 설정 React.useEffect(() => { if (open) { const initQuotations = prItems.map(item => { const existing = initialQuotations.find(q => q.prItemId === item.id) if (existing) { return existing } return { prItemId: item.id, bidUnitPrice: 0, bidAmount: 0, proposedDeliveryDate: '', technicalSpecification: '' } }) setQuotations(initQuotations) } }, [open, 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 })) } } // 견적 데이터 업데이트 const updateQuotation = (prItemId: number, field: keyof PrItemQuotation, value: any) => { const updatedQuotations = quotations.map(q => { if (q.prItemId === prItemId) { const updated = { ...q, [field]: value } // 단가가 변경되면 금액 자동 계산 if (field === 'bidUnitPrice') { const prItem = prItems.find(item => item.id === prItemId) const quantity = parseFloat(prItem?.quantity || '1') updated.bidAmount = updated.bidUnitPrice * quantity } return updated } return q }) setQuotations(updatedQuotations) } // 파일 다운로드 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 handleSave = () => { const totalAmount = quotations.reduce((sum, q) => sum + q.bidAmount, 0) onSave(quotations, totalAmount) setOpen(false) toast({ title: '저장 완료', description: '품목별 견적이 저장되었습니다.', }) } // 통화 포맷팅 const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: currency, }).format(amount) } // 총 금액 계산 const totalAmount = quotations.reduce((sum, q) => sum + q.bidAmount, 0) return ( {children} 품목별 견적 작성 각 품목별로 견적 단가를 입력하여 총 사전견적 금액을 계산합니다.
아이템번호 PR번호 품목정보 자재내역 수량 단위 견적단가 견적금액 납품예정일 기술사양 SPEC {prItems.map((item) => { const quotation = quotations.find(q => q.prItemId === item.id) || { prItemId: item.id, bidUnitPrice: 0, bidAmount: 0, proposedDeliveryDate: '', technicalSpecification: '' } return ( {item.itemNumber || '-'} {item.prNumber || '-'}
{item.itemInfo || '-'}
{item.materialDescription || '-'}
{item.quantity ? parseFloat(item.quantity).toLocaleString() : '-'} {item.quantityUnit || '-'} {readOnly ? ( {quotation.bidUnitPrice.toLocaleString()} ) : ( updateQuotation( item.id, 'bidUnitPrice', parseFloat(e.target.value) || 0 )} className="w-32 text-right" placeholder="단가" /> )}
{formatCurrency(quotation.bidAmount)}
{readOnly ? ( quotation.proposedDeliveryDate ? formatDate(quotation.proposedDeliveryDate, 'KR') : '-' ) : ( updateQuotation( item.id, 'proposedDeliveryDate', e.target.value )} className="w-40" /> )} {readOnly ? (
{quotation.technicalSpecification || '-'}
) : (