'use client' import * as React from 'react' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Checkbox } from '@/components/ui/checkbox' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { ArrowLeft, Calendar, Building2, Package, User, FileText, Users, Send, CheckCircle, XCircle, Save } from 'lucide-react' import { formatDate } from '@/lib/utils' import { getBiddingCompaniesForPartners, submitPreQuoteResponse, getPrItemsForBidding, getSavedPrItemQuotations, savePreQuoteDraft, setPreQuoteParticipation } from '../pre-quote/service' import { getBiddingConditions } from '../service' import { getPriceAdjustmentFormByBiddingCompanyId } from '../detail/service' import { getIncotermsForSelection, getPaymentTermsForSelection, getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from '@/lib/procurement-select/service' import { PrItemsPricingTable } from './components/pr-items-pricing-table' import { SimpleFileUpload } from './components/simple-file-upload' import { biddingStatusLabels, } from '@/db/schema' import { useToast } from '@/hooks/use-toast' import { useTransition } from 'react' import { useSession } from 'next-auth/react' interface PartnersBiddingPreQuoteProps { biddingId: number companyId: number } interface BiddingDetail { id: number biddingNumber: string revision: number | null projectName: string | null itemName: string | null title: string description: string | null content: string | null contractType: string biddingType: string awardCount: string contractStartDate: Date | null contractEndDate: Date | null preQuoteDate: string | null biddingRegistrationDate: string | null submissionStartDate: string | null submissionEndDate: string | null evaluationDate: string | null currency: string budget: number | null targetPrice: number | null status: string managerName: string | null managerEmail: string | null managerPhone: string | null biddingCompanyId: number | null biddingId: number // bidding의 ID 추가 invitationStatus: string | null preQuoteAmount: string | null preQuoteSubmittedAt: string | null preQuoteDeadline: string | null isPreQuoteSelected: boolean | null isAttendingMeeting: boolean | null // companyConditionResponses에서 가져온 조건들 (제시된 조건과 응답 모두) paymentTermsResponse: string | null taxConditionsResponse: string | null incotermsResponse: string | null proposedContractDeliveryDate: string | null proposedShippingPort: string | null proposedDestinationPort: string | null priceAdjustmentResponse: boolean | null sparePartResponse: string | null isInitialResponse: boolean | null additionalProposals: string | null } export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddingPreQuoteProps) { const router = useRouter() const { toast } = useToast() const [isPending, startTransition] = useTransition() const session = useSession() const [biddingDetail, setBiddingDetail] = React.useState(null) const [isLoading, setIsLoading] = React.useState(true) const [biddingConditions, setBiddingConditions] = React.useState(null) // Procurement 데이터 상태들 const [paymentTermsOptions, setPaymentTermsOptions] = React.useState>([]) const [incotermsOptions, setIncotermsOptions] = React.useState>([]) const [shippingPlaces, setShippingPlaces] = React.useState>([]) const [destinationPlaces, setDestinationPlaces] = React.useState>([]) // 품목별 견적 관련 상태 const [prItems, setPrItems] = React.useState([]) const [prItemQuotations, setPrItemQuotations] = React.useState([]) const [totalAmount, setTotalAmount] = React.useState(0) const [isSaving, setIsSaving] = React.useState(false) // 사전견적 폼 상태 const [responseData, setResponseData] = React.useState({ preQuoteAmount: '', paymentTermsResponse: '', taxConditionsResponse: '', incotermsResponse: '', proposedContractDeliveryDate: '', proposedShippingPort: '', proposedDestinationPort: '', priceAdjustmentResponse: false, isInitialResponse: false, sparePartResponse: '', additionalProposals: '', isAttendingMeeting: false, }) // 사전견적 참여의사 상태 const [participationDecision, setParticipationDecision] = React.useState(null) // 연동제 폼 상태 const [priceAdjustmentForm, setPriceAdjustmentForm] = React.useState({ itemName: '', adjustmentReflectionPoint: '', majorApplicableRawMaterial: '', adjustmentFormula: '', rawMaterialPriceIndex: '', referenceDate: '', comparisonDate: '', adjustmentRatio: '', notes: '', adjustmentConditions: '', majorNonApplicableRawMaterial: '', adjustmentPeriod: '', contractorWriter: '', adjustmentDate: '', nonApplicableReason: '', }) const userId = session.data?.user?.id || '' // Procurement 데이터 로드 함수들 const loadPaymentTerms = React.useCallback(async () => { try { const data = await getPaymentTermsForSelection(); setPaymentTermsOptions(data); } catch (error) { console.error("Failed to load payment terms:", error); } }, []); const loadIncoterms = React.useCallback(async () => { try { const data = await getIncotermsForSelection(); setIncotermsOptions(data); } catch (error) { console.error("Failed to load incoterms:", error); } }, []); const loadShippingPlaces = React.useCallback(async () => { try { const data = await getPlaceOfShippingForSelection(); setShippingPlaces(data); } catch (error) { console.error("Failed to load shipping places:", error); } }, []); const loadDestinationPlaces = React.useCallback(async () => { try { const data = await getPlaceOfDestinationForSelection(); setDestinationPlaces(data); } catch (error) { console.error("Failed to load destination places:", error); } }, []); // 데이터 로드 React.useEffect(() => { const loadData = async () => { try { setIsLoading(true) // 모든 필요한 데이터를 병렬로 로드 const [result, conditions, prItemsData] = await Promise.all([ getBiddingCompaniesForPartners(biddingId, companyId), getBiddingConditions(biddingId), getPrItemsForBidding(biddingId) ]) if (result) { setBiddingDetail(result as BiddingDetail) // 저장된 품목별 견적 정보가 있으면 로드 if (result.biddingCompanyId) { const savedQuotations = await getSavedPrItemQuotations(result.biddingCompanyId) setPrItemQuotations(savedQuotations) // 총 금액 계산 const calculatedTotal = savedQuotations.reduce((sum: number, item: any) => sum + item.bidAmount, 0) setTotalAmount(calculatedTotal) // 저장된 연동제 정보가 있으면 로드 if (result.priceAdjustmentResponse) { const savedPriceAdjustmentForm = await getPriceAdjustmentFormByBiddingCompanyId(result.biddingCompanyId) if (savedPriceAdjustmentForm) { setPriceAdjustmentForm({ itemName: savedPriceAdjustmentForm.itemName || '', adjustmentReflectionPoint: savedPriceAdjustmentForm.adjustmentReflectionPoint || '', majorApplicableRawMaterial: savedPriceAdjustmentForm.majorApplicableRawMaterial || '', adjustmentFormula: savedPriceAdjustmentForm.adjustmentFormula || '', rawMaterialPriceIndex: savedPriceAdjustmentForm.rawMaterialPriceIndex || '', referenceDate: savedPriceAdjustmentForm.referenceDate ? new Date(savedPriceAdjustmentForm.referenceDate).toISOString().split('T')[0] : '', comparisonDate: savedPriceAdjustmentForm.comparisonDate ? new Date(savedPriceAdjustmentForm.comparisonDate).toISOString().split('T')[0] : '', adjustmentRatio: savedPriceAdjustmentForm.adjustmentRatio?.toString() || '', notes: savedPriceAdjustmentForm.notes || '', adjustmentConditions: savedPriceAdjustmentForm.adjustmentConditions || '', majorNonApplicableRawMaterial: savedPriceAdjustmentForm.majorNonApplicableRawMaterial || '', adjustmentPeriod: savedPriceAdjustmentForm.adjustmentPeriod || '', contractorWriter: savedPriceAdjustmentForm.contractorWriter || '', adjustmentDate: savedPriceAdjustmentForm.adjustmentDate ? new Date(savedPriceAdjustmentForm.adjustmentDate).toISOString().split('T')[0] : '', nonApplicableReason: savedPriceAdjustmentForm.nonApplicableReason || '', }) } } } // 기존 응답 데이터로 폼 초기화 setResponseData({ preQuoteAmount: result.preQuoteAmount?.toString() || '', paymentTermsResponse: result.paymentTermsResponse || '', taxConditionsResponse: result.taxConditionsResponse || '', incotermsResponse: result.incotermsResponse || '', proposedContractDeliveryDate: result.proposedContractDeliveryDate || '', proposedShippingPort: result.proposedShippingPort || '', proposedDestinationPort: result.proposedDestinationPort || '', priceAdjustmentResponse: result.priceAdjustmentResponse || false, isInitialResponse: result.isInitialResponse || false, sparePartResponse: result.sparePartResponse || '', additionalProposals: result.additionalProposals || '', isAttendingMeeting: result.isAttendingMeeting || false, }) // 사전견적 참여의사 초기화 setParticipationDecision(result.isPreQuoteParticipated) } if (conditions) { // BiddingConditionsEdit와 같은 방식으로 raw 데이터 사용 setBiddingConditions(conditions) } if (prItemsData) { setPrItems(prItemsData) } // Procurement 데이터 로드 await Promise.all([ loadPaymentTerms(), loadIncoterms(), loadShippingPlaces(), loadDestinationPlaces() ]) } catch (error) { console.error('Failed to load bidding company:', error) toast({ title: '오류', description: '입찰 정보를 불러오는데 실패했습니다.', variant: 'destructive', }) } finally { setIsLoading(false) } } loadData() }, [biddingId, companyId, toast, loadPaymentTerms, loadIncoterms, loadShippingPlaces, loadDestinationPlaces]) // 임시저장 기능 const handleTempSave = () => { if (!biddingDetail || !biddingDetail.biddingCompanyId) { toast({ title: '임시저장 실패', description: '입찰 정보가 올바르지 않습니다.', variant: 'destructive', }) return } // 입찰 마감 상태 체크 const biddingStatus = biddingDetail.status const isClosed = biddingStatus === 'bidding_closed' || biddingStatus === 'vendor_selected' || biddingStatus === 'bidding_disposal' if (isClosed) { toast({ title: "접근 제한", description: "입찰이 마감되어 더 이상 사전견적을 제출할 수 없습니다.", variant: "destructive", }) router.back() return } if (!userId) { toast({ title: '임시저장 실패', description: '사용자 정보를 확인할 수 없습니다. 다시 로그인해주세요.', variant: 'destructive', }) return } setIsSaving(true) startTransition(async () => { try { const result = await savePreQuoteDraft( biddingDetail.biddingCompanyId!, { prItemQuotations, paymentTermsResponse: responseData.paymentTermsResponse, taxConditionsResponse: responseData.taxConditionsResponse, incotermsResponse: responseData.incotermsResponse, proposedContractDeliveryDate: responseData.proposedContractDeliveryDate, proposedShippingPort: responseData.proposedShippingPort, proposedDestinationPort: responseData.proposedDestinationPort, priceAdjustmentResponse: responseData.priceAdjustmentResponse, isInitialResponse: responseData.isInitialResponse, sparePartResponse: responseData.sparePartResponse, additionalProposals: responseData.additionalProposals, priceAdjustmentForm: responseData.priceAdjustmentResponse ? { itemName: priceAdjustmentForm.itemName, adjustmentReflectionPoint: priceAdjustmentForm.adjustmentReflectionPoint, majorApplicableRawMaterial: priceAdjustmentForm.majorApplicableRawMaterial, adjustmentFormula: priceAdjustmentForm.adjustmentFormula, rawMaterialPriceIndex: priceAdjustmentForm.rawMaterialPriceIndex, referenceDate: priceAdjustmentForm.referenceDate, comparisonDate: priceAdjustmentForm.comparisonDate, adjustmentRatio: priceAdjustmentForm.adjustmentRatio ? parseFloat(priceAdjustmentForm.adjustmentRatio) : undefined, notes: priceAdjustmentForm.notes, adjustmentConditions: priceAdjustmentForm.adjustmentConditions, majorNonApplicableRawMaterial: priceAdjustmentForm.majorNonApplicableRawMaterial, adjustmentPeriod: priceAdjustmentForm.adjustmentPeriod, contractorWriter: priceAdjustmentForm.contractorWriter, adjustmentDate: priceAdjustmentForm.adjustmentDate, nonApplicableReason: priceAdjustmentForm.nonApplicableReason, } : undefined }, userId ) if (result.success) { toast({ title: '임시저장 완료', description: result.message, }) } else { toast({ title: '임시저장 실패', description: result.error, variant: 'destructive', }) } } catch (error) { console.error('Temp save error:', error) toast({ title: '임시저장 실패', description: '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', variant: 'destructive', }) } finally { setIsSaving(false) } }) } // 사전견적 참여의사 설정 함수 const handleParticipationDecision = async (participate: boolean) => { if (!biddingDetail?.biddingCompanyId) return startTransition(async () => { const result = await setPreQuoteParticipation( biddingDetail.biddingCompanyId!, participate ) if (result.success) { setParticipationDecision(participate) toast({ title: '설정 완료', description: `사전견적 ${participate ? '참여' : '미참여'}로 설정되었습니다.`, }) } else { toast({ title: '설정 실패', description: result.error, variant: 'destructive', }) } }) } const handleSubmitResponse = () => { if (!biddingDetail) return // 입찰 마감 상태 체크 const biddingStatus = biddingDetail.status const isClosed = biddingStatus === 'bidding_closed' || biddingStatus === 'vendor_selected' || biddingStatus === 'bidding_disposal' if (isClosed) { toast({ title: "접근 제한", description: "입찰이 마감되어 더 이상 사전견적을 제출할 수 없습니다.", variant: "destructive", }) router.back() return } // 견적마감일 체크 if (biddingDetail.preQuoteDeadline) { const now = new Date() const deadline = new Date(biddingDetail.preQuoteDeadline) if (deadline < now) { toast({ title: '견적 마감', description: '견적 마감일이 지나 제출할 수 없습니다.', variant: 'destructive', }) return } } // 필수값 검증 if (prItemQuotations.length === 0 || totalAmount === 0) { toast({ title: '유효성 오류', description: '품목별 견적을 입력해주세요.', variant: 'destructive', }) return } // 품목별 납품일 검증 if (prItemQuotations.length > 0) { for (const quotation of prItemQuotations) { if (!quotation.proposedDeliveryDate?.trim()) { const prItem = prItems.find(item => item.id === quotation.prItemId) toast({ title: '유효성 오류', description: `품목 ${prItem?.itemNumber || quotation.prItemId}의 납품예정일을 입력해주세요.`, variant: 'destructive', }) return } } } const requiredFields = [ { value: responseData.proposedContractDeliveryDate, name: '제안 납품일' }, { value: responseData.paymentTermsResponse, name: '응답 지급조건' }, { value: responseData.taxConditionsResponse, name: '응답 세금조건' }, { value: responseData.incotermsResponse, name: '응답 운송조건' }, { value: responseData.proposedShippingPort, name: '제안 선적지' }, { value: responseData.proposedDestinationPort, name: '제안 하역지' }, { value: responseData.sparePartResponse, name: '스페어파트 응답' }, ] const missingField = requiredFields.find(field => !field.value?.trim()) if (missingField) { toast({ title: '유효성 오류', description: `${missingField.name}을(를) 입력해주세요.`, variant: 'destructive', }) return } startTransition(async () => { const submissionData = { preQuoteAmount: totalAmount, // 품목별 계산된 총 금액 사용 prItemQuotations, // 품목별 견적 데이터 추가 paymentTermsResponse: responseData.paymentTermsResponse, taxConditionsResponse: responseData.taxConditionsResponse, incotermsResponse: responseData.incotermsResponse, proposedContractDeliveryDate: responseData.proposedContractDeliveryDate, proposedShippingPort: responseData.proposedShippingPort, proposedDestinationPort: responseData.proposedDestinationPort, priceAdjustmentResponse: responseData.priceAdjustmentResponse, isInitialResponse: responseData.isInitialResponse, sparePartResponse: responseData.sparePartResponse, additionalProposals: responseData.additionalProposals, priceAdjustmentForm: responseData.priceAdjustmentResponse ? { itemName: priceAdjustmentForm.itemName, adjustmentReflectionPoint: priceAdjustmentForm.adjustmentReflectionPoint, majorApplicableRawMaterial: priceAdjustmentForm.majorApplicableRawMaterial, adjustmentFormula: priceAdjustmentForm.adjustmentFormula, rawMaterialPriceIndex: priceAdjustmentForm.rawMaterialPriceIndex, referenceDate: priceAdjustmentForm.referenceDate, comparisonDate: priceAdjustmentForm.comparisonDate, adjustmentRatio: priceAdjustmentForm.adjustmentRatio ? parseFloat(priceAdjustmentForm.adjustmentRatio) : undefined, notes: priceAdjustmentForm.notes, adjustmentConditions: priceAdjustmentForm.adjustmentConditions, majorNonApplicableRawMaterial: priceAdjustmentForm.majorNonApplicableRawMaterial, adjustmentPeriod: priceAdjustmentForm.adjustmentPeriod, contractorWriter: priceAdjustmentForm.contractorWriter, adjustmentDate: priceAdjustmentForm.adjustmentDate, nonApplicableReason: priceAdjustmentForm.nonApplicableReason, } : undefined } const result = await submitPreQuoteResponse( biddingDetail.biddingCompanyId!, submissionData, userId ) console.log('제출 결과:', result) if (result.success) { toast({ title: '성공', description: result.message, }) // 데이터 새로고침 및 폼 상태 업데이트 const updatedDetail = await getBiddingCompaniesForPartners(biddingId, companyId) console.log('업데이트된 데이터:', updatedDetail) if (updatedDetail) { setBiddingDetail(updatedDetail as BiddingDetail) // 폼 상태도 업데이트된 데이터로 다시 설정 setResponseData({ preQuoteAmount: updatedDetail.preQuoteAmount?.toString() || '', paymentTermsResponse: updatedDetail.paymentTermsResponse || '', taxConditionsResponse: updatedDetail.taxConditionsResponse || '', incotermsResponse: updatedDetail.incotermsResponse || '', proposedContractDeliveryDate: updatedDetail.proposedContractDeliveryDate || '', proposedShippingPort: updatedDetail.proposedShippingPort || '', proposedDestinationPort: updatedDetail.proposedDestinationPort || '', priceAdjustmentResponse: updatedDetail.priceAdjustmentResponse || false, isInitialResponse: updatedDetail.isInitialResponse || false, sparePartResponse: updatedDetail.sparePartResponse || '', additionalProposals: updatedDetail.additionalProposals || '', isAttendingMeeting: updatedDetail.isAttendingMeeting || false, }) // 연동제 데이터도 다시 로드 if (updatedDetail.biddingCompanyId && updatedDetail.priceAdjustmentResponse) { const savedPriceAdjustmentForm = await getPriceAdjustmentFormByBiddingCompanyId(updatedDetail.biddingCompanyId) if (savedPriceAdjustmentForm) { setPriceAdjustmentForm({ itemName: savedPriceAdjustmentForm.itemName || '', adjustmentReflectionPoint: savedPriceAdjustmentForm.adjustmentReflectionPoint || '', majorApplicableRawMaterial: savedPriceAdjustmentForm.majorApplicableRawMaterial || '', adjustmentFormula: savedPriceAdjustmentForm.adjustmentFormula || '', rawMaterialPriceIndex: savedPriceAdjustmentForm.rawMaterialPriceIndex || '', referenceDate: savedPriceAdjustmentForm.referenceDate ? new Date(savedPriceAdjustmentForm.referenceDate).toISOString().split('T')[0] : '', comparisonDate: savedPriceAdjustmentForm.comparisonDate ? new Date(savedPriceAdjustmentForm.comparisonDate).toISOString().split('T')[0] : '', adjustmentRatio: savedPriceAdjustmentForm.adjustmentRatio?.toString() || '', notes: savedPriceAdjustmentForm.notes || '', adjustmentConditions: savedPriceAdjustmentForm.adjustmentConditions || '', majorNonApplicableRawMaterial: savedPriceAdjustmentForm.majorNonApplicableRawMaterial || '', adjustmentPeriod: savedPriceAdjustmentForm.adjustmentPeriod || '', contractorWriter: savedPriceAdjustmentForm.contractorWriter || '', adjustmentDate: savedPriceAdjustmentForm.adjustmentDate ? new Date(savedPriceAdjustmentForm.adjustmentDate).toISOString().split('T')[0] : '', nonApplicableReason: savedPriceAdjustmentForm.nonApplicableReason || '', }) } } } } else { toast({ title: '오류', description: result.error, variant: 'destructive', }) } }) } if (isLoading) { return (

입찰 정보를 불러오는 중...

) } if (!biddingDetail) { return (

입찰 정보를 찾을 수 없습니다.

) } return (
{/* 헤더 */}

{biddingDetail.title}

{biddingDetail.biddingNumber} {biddingDetail.revision && biddingDetail.revision > 0 && ` Rev.${biddingDetail.revision}`} {biddingStatusLabels[biddingDetail.status]}
{/* 입찰 공고 섹션 */} 입찰 공고
{biddingDetail.projectName}
{biddingDetail.itemName}
{/*
{contractTypeLabels[biddingDetail.contractType]}
{biddingTypeLabels[biddingDetail.biddingType]}
{biddingDetail.awardCount === 'single' ? '단수' : '복수'}
*/}
{biddingDetail.managerName}
{/* {biddingDetail.budget && (
{formatCurrency(biddingDetail.budget)}
)} */} {/* 일정 정보 */} {/*
{biddingDetail.submissionStartDate && biddingDetail.submissionEndDate && (
제출기간: {formatDate(biddingDetail.submissionStartDate, 'KR')} ~ {formatDate(biddingDetail.submissionEndDate, 'KR')}
)} {biddingDetail.evaluationDate && (
평가일: {formatDate(biddingDetail.evaluationDate, 'KR')}
)}
*/} {/* 견적마감일 정보 */} {biddingDetail.preQuoteDeadline && (
{(() => { const now = new Date() const deadline = new Date(biddingDetail.preQuoteDeadline) const isExpired = deadline < now const timeLeft = deadline.getTime() - now.getTime() const daysLeft = Math.floor(timeLeft / (1000 * 60 * 60 * 24)) const hoursLeft = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) return (
견적 마감일: {formatDate(biddingDetail.preQuoteDeadline, 'KR')}
{isExpired ? ( 마감됨 ) : daysLeft <= 1 ? ( {daysLeft === 0 ? `${hoursLeft}시간 남음` : `${daysLeft}일 남음`} ) : ( {daysLeft}일 남음 )}
{isExpired && (
⚠️ 견적 마감일이 지났습니다. 견적 제출이 불가능합니다.
)}
) })()}
)}
{/* 현재 설정된 조건 섹션 */} {biddingConditions && ( 현재 설정된 입찰 조건

{biddingConditions.paymentTerms || "미설정"}

{biddingConditions.taxConditions || "미설정"}

{biddingConditions.incoterms || "미설정"}

{biddingConditions.contractDeliveryDate ? formatDate(biddingConditions.contractDeliveryDate, 'KR') : "미설정" }

{biddingConditions.shippingPort || "미설정"}

{biddingConditions.destinationPort || "미설정"}

{biddingConditions.isPriceAdjustmentApplicable ? "적용 가능" : "적용 불가"}

{biddingConditions.sparePartOptions}

)} {/* 사전견적 참여의사 결정 섹션 */} 사전견적 참여의사 결정 {participationDecision === null ? (

해당 입찰의 사전견적에 참여하시겠습니까?

) : (
{participationDecision ? ( ) : ( )} 사전견적 {participationDecision ? '참여' : '미참여'}로 설정되었습니다.
{participationDecision === false && ( <>

미참여로 설정되어 견적 작성 섹션이 숨겨집니다. 참여하시려면 아래 버튼을 클릭해주세요.

)}
)}
{/* 참여 결정 시에만 견적 작성 섹션들 표시 (단, 견적마감일이 지나지 않은 경우에만) */} {participationDecision === true && (() => { // 견적마감일 체크 if (biddingDetail?.preQuoteDeadline) { const now = new Date() const deadline = new Date(biddingDetail.preQuoteDeadline) const isExpired = deadline < now if (isExpired) { return (

견적 마감

견적 마감일({formatDate(biddingDetail.preQuoteDeadline, 'KR')})이 지나 견적 제출이 불가능합니다.

) } } return true // 견적 작성 가능 })() && ( <> {/* 품목별 견적 작성 섹션 */} {prItems.length > 0 && ( )} {/* 견적 문서 업로드 섹션 */} {/* 사전견적 폼 섹션 */} 사전견적 제출하기 {/* 총 금액 표시 (읽기 전용) */}
setResponseData({...responseData, proposedContractDeliveryDate: e.target.value})} title={biddingConditions?.contractDeliveryDate ? `참고 납기일: ${formatDate(biddingConditions.contractDeliveryDate, 'KR')}` : "납품일을 선택하세요"} /> {biddingConditions?.contractDeliveryDate && (

참고 납기일: {formatDate(biddingConditions.contractDeliveryDate, 'KR')}

)}
setResponseData({...responseData, taxConditionsResponse: e.target.value})} placeholder={biddingConditions?.taxConditions ? `참고: ${biddingConditions.taxConditions}` : "세금조건에 대한 의견을 입력하세요"} />
setResponseData({...responseData, sparePartResponse: e.target.value})} placeholder={biddingConditions?.sparePartOptions ? `참고: ${biddingConditions.sparePartOptions}` : "스페어파트 관련 응답을 입력하세요"} />