diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-27 03:08:50 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-27 03:08:50 +0000 |
| commit | 79cfa7ea8f21ae227dbb2843ae536fe876ba7c55 (patch) | |
| tree | f12efae72c62286c1a2e9a3f31d695ca22d83b6e /lib/bidding/vendor | |
| parent | e1da84ac863989b9f63b089c09aaa2bbcdc3d6cd (diff) | |
(최겸) 구매 입찰 수정
Diffstat (limited to 'lib/bidding/vendor')
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-detail.tsx | 234 |
1 files changed, 124 insertions, 110 deletions
diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx index 10fe71a9..03429cca 100644 --- a/lib/bidding/vendor/partners-bidding-detail.tsx +++ b/lib/bidding/vendor/partners-bidding-detail.tsx @@ -32,10 +32,11 @@ import { submitPartnerResponse, updatePartnerBiddingParticipation, saveBiddingDraft, - getPriceAdjustmentFormByBiddingCompanyId + getPriceAdjustmentFormByBiddingCompanyId, + getPartnerBiddingItemQuotations } from '../detail/service' import { cancelBiddingResponse } from '../detail/bidding-actions' -import { getPrItemsForBidding, getSavedPrItemQuotations } from '@/lib/bidding/pre-quote/service' +import { getPrItemsForBidding } 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' @@ -68,13 +69,13 @@ interface BiddingDetail { contractType: string biddingType: string awardCount: string | null - contractStartDate: Date | null - contractEndDate: Date | null - preQuoteDate: Date | null - biddingRegistrationDate: Date | null - submissionStartDate: Date | null - submissionEndDate: Date | null - evaluationDate: Date | null + contractStartDate: Date | string | null + contractEndDate: Date | string | null + preQuoteDate: Date | string | null + biddingRegistrationDate: Date | string | null + submissionStartDate: Date | string | null + submissionEndDate: Date | string | null + evaluationDate: Date | string | null currency: string budget: number | null targetPrice: number | null @@ -109,7 +110,7 @@ interface PrItem { materialGroupInfo: string | null materialNumber: string | null materialInfo: string | null - requestedDeliveryDate: Date | null + requestedDeliveryDate: Date | string | null annualUnitPrice: string | null currency: string | null quantity: string | null @@ -160,16 +161,17 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD const [isCancelling, setIsCancelling] = React.useState(false) const [isFinalSubmission, setIsFinalSubmission] = React.useState(false) const [isBiddingNoticeLoading, setIsBiddingNoticeLoading] = React.useState(false) + const [isExpired, setIsExpired] = React.useState(false) // 입찰공고 관련 상태 const [biddingNotice, setBiddingNotice] = React.useState<{ id?: number - biddingId?: number + biddingId?: number | null title?: string content?: string isTemplate?: boolean - createdAt?: string - updatedAt?: string + createdAt?: string | Date + updatedAt?: string | Date } | null>(null) const [biddingConditions, setBiddingConditions] = React.useState<BiddingConditions | null>(null) const [isNoticeOpen, setIsNoticeOpen] = React.useState(false) @@ -274,6 +276,13 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD }) setBiddingDetail(result) + + // 만료 여부 확인 + if (result.submissionEndDate) { + const now = new Date() + const deadline = new Date(result.submissionEndDate) + setIsExpired(deadline < now) + } // 기존 응답 데이터로 폼 초기화 setResponseData({ @@ -297,7 +306,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD if (result?.biddingCompanyId) { try { // 입찰 데이터를 가져와서 본입찰용으로 변환 - const preQuoteData = await getSavedPrItemQuotations(result.biddingCompanyId) + const preQuoteData = await getPartnerBiddingItemQuotations(result.biddingCompanyId) if (preQuoteData && Array.isArray(preQuoteData) && preQuoteData.length > 0) { console.log('입찰 데이터:', preQuoteData) @@ -325,19 +334,19 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD }, 0) setTotalQuotationAmount(total) console.log('계산된 총 금액:', total) + + // 응찰 확정 시에만 입찰 금액을 finalQuoteAmount로 설정 + if (total > 0 && result?.isBiddingParticipated === true) { + console.log('응찰 확정됨, 입찰 금액 설정:', total) + console.log('입찰 금액을 finalQuoteAmount로 설정:', total) + setResponseData(prev => ({ + ...prev, + finalQuoteAmount: total.toString() + })) + } } } - // 응찰 확정 시에만 입찰 금액을 finalQuoteAmount로 설정 - if (totalQuotationAmount > 0 && result?.isBiddingParticipated === true) { - console.log('응찰 확정됨, 입찰 금액 설정:', totalQuotationAmount) - console.log('입찰 금액을 finalQuoteAmount로 설정:', totalQuotationAmount) - setResponseData(prev => ({ - ...prev, - finalQuoteAmount: totalQuotationAmount.toString() - })) - } - // 연동제 데이터 로드 (입찰에서 답변했으면 로드, 아니면 입찰 조건 확인) if (result.priceAdjustmentResponse !== null) { // 입찰에서 이미 답변한 경우 - 연동제 폼 로드 @@ -385,6 +394,16 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD const handleParticipationDecision = async (participated: boolean) => { if (!biddingDetail) return + // 만료 체크 + if (isExpired) { + toast({ + title: "참여 불가", + description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.", + variant: "destructive", + }) + return + } + setIsUpdatingParticipation(true) try { const result = await updatePartnerBiddingParticipation( @@ -409,7 +428,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD // 참여 확정 시 입찰 데이터가 있다면 로드 if (participated && updatedDetail.biddingCompanyId) { try { - const preQuoteData = await getSavedPrItemQuotations(updatedDetail.biddingCompanyId) + const preQuoteData = await getPartnerBiddingItemQuotations(updatedDetail.biddingCompanyId) if (preQuoteData && Array.isArray(preQuoteData) && preQuoteData.length > 0) { console.log('참여확정 후 입찰 데이터:', preQuoteData) @@ -487,18 +506,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD const handleSaveDraft = async () => { if (!biddingDetail || !userId) return - // 제출 마감일 체크 - if (biddingDetail.submissionEndDate) { - const now = new Date() - const deadline = new Date(biddingDetail.submissionEndDate) - if (deadline < now) { - toast({ - title: "접근 제한", - description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.", - variant: "destructive", - }) - return - } + // 제출 마감일 체크 (상태 사용) + if (isExpired) { + toast({ + title: "접근 제한", + description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.", + variant: "destructive", + }) + return } // 입찰 마감 상태 체크 @@ -566,7 +581,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD } } - // 응찰 취소 핸들러 + // 응찰 포기 핸들러 const handleCancelResponse = async () => { if (!biddingDetail || !userId) return @@ -590,7 +605,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD if (result.success) { toast({ - title: '응찰 취소 완료', + title: '응찰 포기 완료', description: '응찰이 취소되었습니다.', }) // 페이지 새로고침 @@ -602,7 +617,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD } } else { toast({ - title: '응찰 취소 실패', + title: '응찰 포기 실패', description: result.error, variant: 'destructive', }) @@ -611,7 +626,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD console.error('Failed to cancel bidding response:', error) toast({ title: '오류', - description: '응찰 취소에 실패했습니다.', + description: '응찰 포기에 실패했습니다.', variant: 'destructive', }) } finally { @@ -622,18 +637,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD const handleSubmitResponse = () => { if (!biddingDetail) return - // 제출 마감일 체크 - if (biddingDetail.submissionEndDate) { - const now = new Date() - const deadline = new Date(biddingDetail.submissionEndDate) - if (deadline < now) { - toast({ - title: "접근 제한", - description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.", - variant: "destructive", - }) - return - } + // 제출 마감일 체크 (상태 사용) + if (isExpired) { + toast({ + title: "접근 제한", + description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.", + variant: "destructive", + }) + return } // 입찰 마감 상태 체크 @@ -692,8 +703,11 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD additionalProposals: responseData.additionalProposals, isFinalSubmission, // 최종제출 여부 추가 // 연동제 데이터 추가 (연동제 적용요건 문의가 있는 경우만) - priceAdjustmentResponse: biddingDetail.isPriceAdjustmentApplicableQuestion ? responseData.priceAdjustmentResponse : undefined, - priceAdjustmentForm: biddingDetail.isPriceAdjustmentApplicableQuestion && responseData.priceAdjustmentResponse !== null ? priceAdjustmentForm : undefined, + priceAdjustmentResponse: biddingDetail.isPriceAdjustmentApplicableQuestion ? (responseData.priceAdjustmentResponse ?? false) as boolean : false, + priceAdjustmentForm: biddingDetail.isPriceAdjustmentApplicableQuestion && (responseData.priceAdjustmentResponse ?? false) as boolean ? { + ...priceAdjustmentForm, + adjustmentRatio: parseFloat(priceAdjustmentForm.adjustmentRatio) || 0 + } : undefined, prItemQuotations: prItemQuotations.length > 0 ? prItemQuotations.map(q => ({ prItemId: q.prItemId, bidUnitPrice: q.bidUnitPrice, @@ -849,57 +863,57 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD {/* 제출 마감일 D-day */} - {biddingDetail.submissionEndDate && ( - <div className="pt-4 border-t"> - <Label className="text-sm font-medium text-muted-foreground mb-2 block">제출 마감 정보</Label> - {(() => { - const now = new Date() - const deadline = new Date(biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' ')) - 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 ( - <div className={`p-3 rounded-lg border-2 ${ - isExpired - ? 'border-red-200 bg-red-50' - : daysLeft <= 1 - ? 'border-orange-200 bg-orange-50' - : 'border-green-200 bg-green-50' - }`}> - <div className="flex items-center justify-between"> - <div className="flex items-center gap-2"> - <Calendar className="w-5 h-5" /> - <span className="font-medium">제출 마감일:</span> - <span className="text-lg font-semibold"> - {biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' ')} - </span> + {biddingDetail.submissionEndDate && ( + <div className="pt-4 border-t"> + <Label className="text-sm font-medium text-muted-foreground mb-2 block">제출 마감 정보</Label> + {(() => { + const now = new Date() + const deadline = new Date(biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' ')) + // isExpired 상태 사용 + 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 ( + <div className={`p-3 rounded-lg border-2 ${ + isExpired + ? 'border-red-200 bg-red-50' + : daysLeft <= 1 + ? 'border-orange-200 bg-orange-50' + : 'border-green-200 bg-green-50' + }`}> + <div className="flex items-center justify-between"> + <div className="flex items-center gap-2"> + <Calendar className="w-5 h-5" /> + <span className="font-medium">제출 마감일:</span> + <span className="text-lg font-semibold"> + {biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' ')} + </span> + </div> + {isExpired ? ( + <Badge variant="destructive" className="ml-2"> + 마감됨 + </Badge> + ) : daysLeft <= 1 ? ( + <Badge variant="secondary" className="ml-2 bg-orange-100 text-orange-800"> + {daysLeft === 0 ? `${hoursLeft}시간 남음` : `${daysLeft}일 남음`} + </Badge> + ) : ( + <Badge variant="secondary" className="ml-2 bg-green-100 text-green-800"> + {daysLeft}일 남음 + </Badge> + )} </div> - {isExpired ? ( - <Badge variant="destructive" className="ml-2"> - 마감됨 - </Badge> - ) : daysLeft <= 1 ? ( - <Badge variant="secondary" className="ml-2 bg-orange-100 text-orange-800"> - {daysLeft === 0 ? `${hoursLeft}시간 남음` : `${daysLeft}일 남음`} - </Badge> - ) : ( - <Badge variant="secondary" className="ml-2 bg-green-100 text-green-800"> - {daysLeft}일 남음 - </Badge> + {isExpired && ( + <div className="mt-2 text-sm text-red-600"> + ⚠️ 제출 마감일이 지났습니다. 입찰 제출이 불가능합니다. + </div> )} </div> - {isExpired && ( - <div className="mt-2 text-sm text-red-600"> - ⚠️ 제출 마감일이 지났습니다. 입찰 제출이 불가능합니다. - </div> - )} - </div> - ) - })()} - </div> - )} + ) + })()} + </div> + )} {/* 일정 정보 */} <div className="pt-4 border-t"> @@ -1086,15 +1100,15 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <div className="flex justify-center gap-4"> <Button onClick={() => handleParticipationDecision(true)} - disabled={isUpdatingParticipation} + disabled={isUpdatingParticipation || isExpired} className="min-w-[120px]" > <CheckCircle className="w-4 h-4 mr-2" /> - 참여하기 + {isExpired ? '마감됨' : '참여하기'} </Button> <Button onClick={() => handleParticipationDecision(false)} - disabled={isUpdatingParticipation} + disabled={isUpdatingParticipation || isExpired} variant="destructive" className="min-w-[120px]" > @@ -1407,7 +1421,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD {/* 응찰 제출 버튼 - 참여 확정 상태일 때만 표시 */} <div className="flex justify-between pt-4 gap-2"> - {/* 응찰 취소 버튼 (최종제출 아닌 경우만) */} + {/* 응찰 포기 버튼 (최종제출 아닌 경우만) */} {biddingDetail.finalQuoteSubmittedAt && !biddingDetail.isFinalSubmission && ( <Button variant="destructive" @@ -1416,14 +1430,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD className="min-w-[100px]" > <Trash2 className="w-4 h-4 mr-2" /> - {isCancelling ? '취소 중...' : '응찰 취소'} + {isCancelling ? '취소 중...' : '응찰 포기'} </Button> )} <div className="flex gap-2 ml-auto"> <Button variant="outline" onClick={handleSaveDraft} - disabled={isSavingDraft || isSubmitting || biddingDetail.isFinalSubmission} + disabled={isSavingDraft || isSubmitting || biddingDetail.isFinalSubmission || isExpired} className="min-w-[100px]" > <Save className="w-4 h-4 mr-2" /> @@ -1431,7 +1445,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD </Button> <Button onClick={handleSubmitResponse} - disabled={isSubmitting || isSavingDraft || biddingDetail.isFinalSubmission} + disabled={isSubmitting || isSavingDraft || biddingDetail.isFinalSubmission || isExpired} className="min-w-[100px]" > <Send className="w-4 h-4 mr-2" /> |
