From ba8cd44a0ed2c613a5f2cee06bfc9bd0f61f21c7 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 7 Nov 2025 08:39:04 +0000 Subject: (최겸) 입찰/견적 수정사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bidding/vendor/partners-bidding-detail.tsx | 904 ++++++++++++++++++++++--- 1 file changed, 808 insertions(+), 96 deletions(-) (limited to 'lib/bidding/vendor/partners-bidding-detail.tsx') diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx index f9241f7b..273c0667 100644 --- a/lib/bidding/vendor/partners-bidding-detail.tsx +++ b/lib/bidding/vendor/partners-bidding-detail.tsx @@ -1,11 +1,16 @@ 'use client' import * as React from 'react' + +// 브라우저 환경 체크 +const isBrowser = typeof window !== 'undefined' import { useRouter } from 'next/navigation' +import { useTransition } from 'react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Label } from '@/components/ui/label' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { ArrowLeft, User, @@ -15,8 +20,10 @@ import { XCircle, Save, FileText, - Building2, - Package + Package, + Trash2, + Calendar, + ChevronDown } from 'lucide-react' import { formatDate } from '@/lib/utils' @@ -24,19 +31,26 @@ import { getBiddingDetailsForPartners, submitPartnerResponse, updatePartnerBiddingParticipation, - saveBiddingDraft + saveBiddingDraft, + getPriceAdjustmentFormByBiddingCompanyId } from '../detail/service' +import { cancelBiddingResponse } from '../detail/bidding-actions' import { getPrItemsForBidding, getSavedPrItemQuotations } from '@/lib/bidding/pre-quote/service' +import { getBiddingConditions } from '@/lib/bidding/service' import { PrItemsPricingTable } from './components/pr-items-pricing-table' import { SimpleFileUpload } from './components/simple-file-upload' +import { getTaxConditionName } from "@/lib/tax-conditions/types" import { biddingStatusLabels, contractTypeLabels, biddingTypeLabels } from '@/db/schema' import { useToast } from '@/hooks/use-toast' -import { useTransition } from 'react' import { useSession } from 'next-auth/react' +import { getBiddingNotice } from '../service' +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' +import { Input } from '@/components/ui/input' +import { Textarea } from '@/components/ui/textarea' interface PartnersBiddingDetailProps { biddingId: number @@ -51,7 +65,6 @@ interface BiddingDetail { itemName: string | null title: string description: string | null - content: string | null contractType: string biddingType: string awardCount: string | null @@ -66,33 +79,46 @@ interface BiddingDetail { budget: number | null targetPrice: number | null status: string - managerName: string | null - managerEmail: string | null - managerPhone: string | null + bidPicName: string | null // 입찰담당자 + supplyPicName: string | null // 조달담당자 biddingCompanyId: number biddingId: number invitationStatus: string finalQuoteAmount: number | null finalQuoteSubmittedAt: Date | null + isFinalSubmission: boolean isWinner: boolean isAttendingMeeting: boolean | null isBiddingParticipated: boolean | null additionalProposals: string | null responseSubmittedAt: Date | null + priceAdjustmentResponse: boolean | null // 연동제 적용 여부 + isPreQuoteParticipated: boolean | null // 사전견적 참여 여부 } interface PrItem { id: number + biddingId: number itemNumber: string | null - prNumber: string | null + projectId: number | null + projectInfo: string | null itemInfo: string | null - materialDescription: string | null + shi: string | null + materialGroupNumber: string | null + materialGroupInfo: string | null + materialNumber: string | null + materialInfo: string | null + requestedDeliveryDate: Date | null + annualUnitPrice: string | null + currency: string | null quantity: string | null quantityUnit: string | null totalWeight: string | null weightUnit: string | null - currency: string | null - requestedDeliveryDate: string | null + priceUnit: string | null + purchaseUnit: string | null + materialWeight: string | null + prNumber: string | null hasSpecDocument: boolean | null } @@ -104,6 +130,22 @@ interface BiddingPrItemQuotation { technicalSpecification?: string } +interface BiddingConditions { + id?: number + biddingId?: number + paymentTerms?: string + taxConditions?: string + incoterms?: string + incotermsOption?: string + contractDeliveryDate?: string + shippingPort?: string + destinationPort?: string + isPriceAdjustmentApplicable?: boolean + sparePartOptions?: string + createdAt?: string + updatedAt?: string +} + export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingDetailProps) { const router = useRouter() const { toast } = useToast() @@ -114,7 +156,23 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD const [isUpdatingParticipation, setIsUpdatingParticipation] = React.useState(false) const [isSavingDraft, setIsSavingDraft] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) - + const [isCancelling, setIsCancelling] = React.useState(false) + const [isFinalSubmission, setIsFinalSubmission] = React.useState(false) + const [isBiddingNoticeLoading, setIsBiddingNoticeLoading] = React.useState(false) + + // 입찰공고 관련 상태 + const [biddingNotice, setBiddingNotice] = React.useState<{ + id?: number + biddingId?: number + title?: string + content?: string + isTemplate?: boolean + createdAt?: string + updatedAt?: string + } | null>(null) + const [biddingConditions, setBiddingConditions] = React.useState(null) + const [isNoticeOpen, setIsNoticeOpen] = React.useState(false) + // 품목별 견적 관련 상태 const [prItems, setPrItems] = React.useState([]) const [prItemQuotations, setPrItemQuotations] = React.useState([]) @@ -125,21 +183,95 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD finalQuoteAmount: '', proposedContractDeliveryDate: '', additionalProposals: '', + priceAdjustmentResponse: null as boolean | null, // null: 미선택, true: 적용, false: 미적용 + }) + + // 연동제 폼 상태 + 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 || '' // 데이터 로드 + // 입찰공고 로드 함수 + const loadBiddingNotice = React.useCallback(async () => { + setIsBiddingNoticeLoading(true) + try { + const notice = await getBiddingNotice(biddingId) + console.log('입찰공고 로드 성공:', notice) + setBiddingNotice(notice) + } catch (error) { + console.error('Failed to load bidding notice:', error) + } finally { + setIsBiddingNoticeLoading(false) + } + }, [biddingId]) + React.useEffect(() => { const loadData = async () => { try { setIsLoading(true) - const [result, prItemsResult] = await Promise.all([ - getBiddingDetailsForPartners(biddingId, companyId), - getPrItemsForBidding(biddingId) + // 데이터 로드 시작 로그 + console.log('입찰 상세 데이터 로드 시작:', { biddingId, companyId }) + + console.log('데이터베이스 쿼리 시작...') + + const [result, prItemsResult, noticeResult, conditionsResult] = await Promise.all([ + getBiddingDetailsForPartners(biddingId, companyId).catch(error => { + console.error('Failed to get bidding details:', error) + return null + }), + getPrItemsForBidding(biddingId).catch(error => { + console.error('Failed to get PR items:', error) + return [] + }), + loadBiddingNotice().catch(error => { + console.error('Failed to load bidding notice:', error) + return null + }), + getBiddingConditions(biddingId).catch(error => { + console.error('Failed to load bidding conditions:', error) + return null + }) ]) - + + console.log('데이터베이스 쿼리 완료:', { + resultExists: !!result, + prItemsExists: !!prItemsResult, + noticeExists: !!noticeResult, + conditionsExists: !!conditionsResult + }) + + console.log('데이터 로드 완료:', { + result: !!result, + prItemsCount: Array.isArray(prItemsResult) ? prItemsResult.length : 0, + noticeResult: !!noticeResult, + conditionsResult: !!conditionsResult + }) + if (result) { + console.log('입찰 상세 데이터 로드 성공:', { + biddingId: result.biddingId, + isBiddingParticipated: result.isBiddingParticipated, + invitationStatus: result.invitationStatus, + finalQuoteAmount: result.finalQuoteAmount + }) + setBiddingDetail(result) // 기존 응답 데이터로 폼 초기화 @@ -147,7 +279,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD finalQuoteAmount: result.finalQuoteAmount?.toString() || '', proposedContractDeliveryDate: result.proposedContractDeliveryDate || '', additionalProposals: result.additionalProposals || '', + priceAdjustmentResponse: result.priceAdjustmentResponse || null, }) + + // 입찰 조건 로드 + if (conditionsResult) { + console.log('입찰 조건 로드:', conditionsResult) + setBiddingConditions(conditionsResult) + } } // PR 아이템 설정 @@ -158,29 +297,70 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD try { // 사전견적 데이터를 가져와서 본입찰용으로 변환 const preQuoteData = await getSavedPrItemQuotations(result.biddingCompanyId) - - // 사전견적 데이터를 본입찰 포맷으로 변환 - const convertedQuotations = preQuoteData.map(item => ({ - prItemId: item.prItemId, - bidUnitPrice: item.bidUnitPrice, - bidAmount: item.bidAmount, - proposedDeliveryDate: item.proposedDeliveryDate || undefined, - technicalSpecification: item.technicalSpecification || undefined - })) - - setPrItemQuotations(convertedQuotations) - - // 총 금액 계산 - const total = convertedQuotations.reduce((sum, q) => sum + q.bidAmount, 0) - setTotalQuotationAmount(total) + + if (preQuoteData && Array.isArray(preQuoteData) && preQuoteData.length > 0) { + console.log('사전견적 데이터:', preQuoteData) + + // 사전견적 데이터를 본입찰 포맷으로 변환 + const convertedQuotations = preQuoteData + .filter(item => item && typeof item === 'object' && item.prItemId) + .map(item => ({ + prItemId: item.prItemId, + bidUnitPrice: item.bidUnitPrice, + bidAmount: item.bidAmount, + proposedDeliveryDate: item.proposedDeliveryDate || undefined, + technicalSpecification: item.technicalSpecification || undefined + })) + + console.log('변환된 견적 데이터:', convertedQuotations) + + if (Array.isArray(convertedQuotations) && convertedQuotations.length > 0) { + setPrItemQuotations(convertedQuotations) + + // 총 금액 계산 + const total = convertedQuotations.reduce((sum, q) => { + const amount = Number(q.bidAmount) || 0 + return sum + amount + }, 0) + setTotalQuotationAmount(total) + console.log('계산된 총 금액:', total) + } + } // 응찰 확정 시에만 사전견적 금액을 finalQuoteAmount로 설정 - if (totalQuotationAmount > 0 && result.isBiddingParticipated === true) { + if (totalQuotationAmount > 0 && result?.isBiddingParticipated === true) { + console.log('응찰 확정됨, 사전견적 금액 설정:', totalQuotationAmount) + console.log('사전견적 금액을 finalQuoteAmount로 설정:', totalQuotationAmount) setResponseData(prev => ({ ...prev, finalQuoteAmount: totalQuotationAmount.toString() })) } + + // 연동제 데이터 로드 (사전견적에서 답변했으면 로드, 아니면 입찰 조건 확인) + if (result.priceAdjustmentResponse !== null) { + // 사전견적에서 이미 답변한 경우 - 연동제 폼 로드 + 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 || '', + comparisonDate: savedPriceAdjustmentForm.comparisonDate || '', + adjustmentRatio: savedPriceAdjustmentForm.adjustmentRatio || '', + notes: savedPriceAdjustmentForm.notes || '', + adjustmentConditions: savedPriceAdjustmentForm.adjustmentConditions || '', + majorNonApplicableRawMaterial: savedPriceAdjustmentForm.majorNonApplicableRawMaterial || '', + adjustmentPeriod: savedPriceAdjustmentForm.adjustmentPeriod || '', + contractorWriter: savedPriceAdjustmentForm.contractorWriter || '', + adjustmentDate: savedPriceAdjustmentForm.adjustmentDate || '', + nonApplicableReason: savedPriceAdjustmentForm.nonApplicableReason || '', + }) + } + } } catch (error) { console.error('Failed to load pre-quote data:', error) } @@ -229,23 +409,38 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD if (participated && updatedDetail.biddingCompanyId) { try { const preQuoteData = await getSavedPrItemQuotations(updatedDetail.biddingCompanyId) - const convertedQuotations = preQuoteData.map(item => ({ - prItemId: item.prItemId, - bidUnitPrice: item.bidUnitPrice, - bidAmount: item.bidAmount, - proposedDeliveryDate: item.proposedDeliveryDate || undefined, - technicalSpecification: item.technicalSpecification || undefined - })) - - setPrItemQuotations(convertedQuotations) - const total = convertedQuotations.reduce((sum, q) => sum + q.bidAmount, 0) - setTotalQuotationAmount(total) - - if (total > 0) { - setResponseData(prev => ({ - ...prev, - finalQuoteAmount: total.toString() - })) + + if (preQuoteData && Array.isArray(preQuoteData) && preQuoteData.length > 0) { + console.log('참여확정 후 사전견적 데이터:', preQuoteData) + + const convertedQuotations = preQuoteData + .filter(item => item && typeof item === 'object' && item.prItemId) + .map(item => ({ + prItemId: item.prItemId, + bidUnitPrice: item.bidUnitPrice, + bidAmount: item.bidAmount, + proposedDeliveryDate: item.proposedDeliveryDate || undefined, + technicalSpecification: item.technicalSpecification || undefined + })) + + console.log('참여확정 후 변환된 견적 데이터:', convertedQuotations) + + if (Array.isArray(convertedQuotations) && convertedQuotations.length > 0) { + setPrItemQuotations(convertedQuotations) + const total = convertedQuotations.reduce((sum, q) => { + const amount = Number(q.bidAmount) || 0 + return sum + amount + }, 0) + setTotalQuotationAmount(total) + console.log('참여확정 후 계산된 총 금액:', total) + + if (total > 0) { + setResponseData(prev => ({ + ...prev, + finalQuoteAmount: total.toString() + })) + } + } } } catch (error) { console.error('Failed to load pre-quote data after participation:', error) @@ -356,6 +551,59 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD } } + // 응찰 취소 핸들러 + const handleCancelResponse = async () => { + if (!biddingDetail || !userId) return + + // 최종제출한 경우 취소 불가 + if (biddingDetail.isFinalSubmission) { + toast({ + title: '취소 불가', + description: '최종 제출된 응찰은 취소할 수 없습니다.', + variant: 'destructive', + }) + return + } + + if (isBrowser && !window.confirm('응찰을 취소하시겠습니까? 작성한 견적 내용이 모두 삭제됩니다.')) { + return + } + + setIsCancelling(true) + try { + const result = await cancelBiddingResponse(biddingDetail.biddingCompanyId, userId) + + if (result.success) { + toast({ + title: '응찰 취소 완료', + description: '응찰이 취소되었습니다.', + }) + // 페이지 새로고침 + if (isBrowser) { + window.location.reload() + } else { + // 서버사이드에서는 라우터로 이동 + router.push(`/partners/bid/${biddingId}`) + } + } else { + toast({ + title: '응찰 취소 실패', + description: result.error, + variant: 'destructive', + }) + } + } catch (error) { + console.error('Failed to cancel bidding response:', error) + toast({ + title: '오류', + description: '응찰 취소에 실패했습니다.', + variant: 'destructive', + }) + } finally { + setIsCancelling(false) + } + } + const handleSubmitResponse = () => { if (!biddingDetail) return // 입찰 마감 상태 체크 @@ -412,6 +660,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD finalQuoteAmount: parseFloat(responseData.finalQuoteAmount), proposedContractDeliveryDate: responseData.proposedContractDeliveryDate, additionalProposals: responseData.additionalProposals, + isFinalSubmission, // 최종제출 여부 추가 prItemQuotations: prItemQuotations.length > 0 ? prItemQuotations.map(q => ({ prItemId: q.prItemId, bidUnitPrice: q.bidUnitPrice, @@ -425,8 +674,8 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD if (result.success) { toast({ - title: '응찰 완료', - description: '견적이 성공적으로 제출되었습니다.', + title: isFinalSubmission ? '응찰 완료' : '임시 저장 완료', + description: isFinalSubmission ? '견적이 최종 제출되었습니다.' : '견적이 임시 저장되었습니다.', }) // 데이터 새로고침 @@ -488,9 +737,6 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD {biddingDetail.biddingNumber} - - Rev. {biddingDetail.revision ?? 0} -
-
- -
- - {biddingDetail.projectName || '미설정'} -
-
-
- -
- - {biddingDetail.itemName || '미설정'} -
-
+
{contractTypeLabels[biddingDetail.contractType]}
@@ -552,22 +785,87 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
{biddingDetail.awardCount === 'single' ? '단수' : biddingDetail.awardCount === 'multiple' ? '복수' : '미설정'}
- +
- {biddingDetail.managerName || '미설정'} + {biddingDetail.bidPicName || '미설정'}
-
- - {/* {biddingDetail.budget && (
- +
- - {formatCurrency(biddingDetail.budget)} + + {biddingDetail.supplyPicName || '미설정'} +
+
+ + + {/* 계약기간 */} + {biddingDetail.contractStartDate && biddingDetail.contractEndDate && ( +
+ +
+
+ {formatDate(biddingDetail.contractStartDate, 'KR')} + ~ + {formatDate(biddingDetail.contractEndDate, 'KR')} +
+ )} + + + {/* 제출 마감일 D-day */} + {/* {biddingDetail.submissionEndDate && ( +
+ + {(() => { + const now = new Date() + const deadline = new Date(biddingDetail.submissionEndDate) + 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.submissionEndDate, 'KR')} + +
+ {isExpired ? ( + + 마감됨 + + ) : daysLeft <= 1 ? ( + + {daysLeft === 0 ? `${hoursLeft}시간 남음` : `${daysLeft}일 남음`} + + ) : ( + + {daysLeft}일 남음 + + )} +
+ {isExpired && ( +
+ ⚠️ 제출 마감일이 지났습니다. 입찰 제출이 불가능합니다. +
+ )} +
+ ) + })()} +
)} */} {/* 일정 정보 */} @@ -576,7 +874,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
{biddingDetail.submissionStartDate && biddingDetail.submissionEndDate && (
- 제출기간: {formatDate(biddingDetail.submissionStartDate, 'KR')} ~ {formatDate(biddingDetail.submissionEndDate, 'KR')} + 응찰기간: {formatDate(biddingDetail.submissionStartDate, 'KR')} ~ {formatDate(biddingDetail.submissionEndDate, 'KR')}
)} {biddingDetail.evaluationDate && ( @@ -589,6 +887,130 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD + {/* 입찰공고 토글 섹션 */} + {biddingNotice && ( + + + + + +
+ + 입찰공고 내용 +
+ +
+
+
+ + +
+ {biddingNotice.title && ( +

{biddingNotice.title}

+ )} + {biddingNotice.content ? ( +
+ ) : ( +

입찰공고 내용이 없습니다.

+ )} +
+ + + + + )} + + {/* 현재 설정된 조건 섹션 */} + {biddingConditions && ( + + + 현재 설정된 입찰 조건 + + +
+
+ +
+

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

+
+
+ +
+ +
+

+ {biddingConditions.taxConditions + ? getTaxConditionName(biddingConditions.taxConditions) + : "미설정" + } +

+
+
+ +
+ +
+

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

+
+
+
+ +
+

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

+
+
+ +
+ +
+

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

+
+
+ +
+ +
+

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

+
+
+ +
+ +
+

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

+
+
+ +
+ +
+

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

+
+
+ + +
+ +
+

{biddingConditions.sparePartOptions}

+
+
+
+
+
+ )} + + {/* 참여 상태에 따른 섹션 표시 */} {biddingDetail.isBiddingParticipated === false ? ( @@ -687,25 +1109,315 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD readOnly={false} /> )} + + {/* 연동제 적용 여부 - SHI가 연동제를 요구하고, 사전견적에서 답변하지 않은 경우만 표시 */} + {biddingConditions?.isPriceAdjustmentApplicable && biddingDetail.priceAdjustmentResponse === null && ( + <> +
+ + { + const newValue = value === 'apply' ? true : value === 'not-apply' ? false : null + setResponseData({...responseData, priceAdjustmentResponse: newValue}) + }} + > +
+ + +
+
+ + +
+
+
+ + {/* 연동제 상세 정보 */} + {responseData.priceAdjustmentResponse !== null && ( + + + 하도급대금등 연동표 + + + {/* 공통 필드 - 품목등의 명칭 */} +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, itemName: e.target.value})} + placeholder="품목명을 입력하세요" + required + /> +
+ + {/* 연동제 적용 시 - 모든 필드 표시 */} + {responseData.priceAdjustmentResponse === true && ( + <> +
+
+ + setPriceAdjustmentForm({...priceAdjustmentForm, adjustmentReflectionPoint: e.target.value})} + placeholder="반영시점을 입력하세요" + required + /> +
+ +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, adjustmentRatio: e.target.value})} + placeholder="비율을 입력하세요" + required + /> +
+ +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, adjustmentPeriod: e.target.value})} + placeholder="조정주기를 입력하세요" + required + /> +
+ +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, referenceDate: e.target.value})} + required + /> +
+ +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, comparisonDate: e.target.value})} + required + /> +
+ +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, contractorWriter: e.target.value})} + placeholder="작성자명을 입력하세요" + required + /> +
+ +
+ + setPriceAdjustmentForm({...priceAdjustmentForm, adjustmentDate: e.target.value})} + required + /> +
+
+ +
+ +