diff options
Diffstat (limited to 'lib/bidding/vendor/partners-bidding-detail.tsx')
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-detail.tsx | 205 |
1 files changed, 119 insertions, 86 deletions
diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx index 03429cca..bf76de62 100644 --- a/lib/bidding/vendor/partners-bidding-detail.tsx +++ b/lib/bidding/vendor/partners-bidding-detail.tsx @@ -81,6 +81,7 @@ interface BiddingDetail { targetPrice: number | null status: string bidPicName: string | null // 입찰담당자 + bidPicPhone?: string | null // 입찰담당자 전화번호 supplyPicName: string | null // 조달담당자 biddingCompanyId: number biddingId: number @@ -122,6 +123,11 @@ interface PrItem { materialWeight: string | null prNumber: string | null hasSpecDocument: boolean | null + specification: string | null + bidUnitPrice?: string | number | null + bidAmount?: string | number | null + proposedDeliveryDate?: string | Date | null + technicalSpecification?: string | null } interface BiddingPrItemQuotation { @@ -239,7 +245,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD console.error('Failed to get bidding details:', error) return null }), - getPrItemsForBidding(biddingId).catch(error => { + getPrItemsForBidding(biddingId, companyId).catch(error => { console.error('Failed to get PR items:', error) return [] }), @@ -302,50 +308,33 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD // PR 아이템 설정 setPrItems(prItemsResult) + // PR 아이템 결과로부터 견적 정보 추출 및 설정 + if (Array.isArray(prItemsResult) && prItemsResult.length > 0) { + const initialQuotations = prItemsResult.map((item: any) => ({ + prItemId: item.id, + bidUnitPrice: item.bidUnitPrice ? Number(item.bidUnitPrice) : 0, + bidAmount: item.bidAmount ? Number(item.bidAmount) : 0, + proposedDeliveryDate: item.proposedDeliveryDate ? (item.proposedDeliveryDate instanceof Date ? item.proposedDeliveryDate.toISOString().split('T')[0] : item.proposedDeliveryDate) : undefined, + technicalSpecification: item.technicalSpecification || undefined + })); + setPrItemQuotations(initialQuotations); + + // 총 금액 계산 + const total = initialQuotations.reduce((sum: number, q: any) => sum + q.bidAmount, 0); + setTotalQuotationAmount(total); + + // 응찰 확정 시 총 금액 설정 + if (total > 0 && result?.isBiddingParticipated === true) { + setResponseData(prev => ({ + ...prev, + finalQuoteAmount: total.toString() + })); + } + } + // 입찰 데이터를 본입찰용으로 로드 (응찰 확정 시 또는 입찰이 있는 경우) if (result?.biddingCompanyId) { try { - // 입찰 데이터를 가져와서 본입찰용으로 변환 - const preQuoteData = await getPartnerBiddingItemQuotations(result.biddingCompanyId) - - 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 (total > 0 && result?.isBiddingParticipated === true) { - console.log('응찰 확정됨, 입찰 금액 설정:', total) - console.log('입찰 금액을 finalQuoteAmount로 설정:', total) - setResponseData(prev => ({ - ...prev, - finalQuoteAmount: total.toString() - })) - } - } - } // 연동제 데이터 로드 (입찰에서 답변했으면 로드, 아니면 입찰 조건 확인) if (result.priceAdjustmentResponse !== null) { @@ -833,9 +822,16 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD </div> <div> <Label className="text-sm font-medium text-muted-foreground">입찰담당자</Label> - <div className="flex items-center gap-2 mt-1"> - <User className="w-4 h-4" /> - <span>{biddingDetail.bidPicName || '미설정'}</span> + <div className="flex flex-col mt-1"> + <div className="flex items-center gap-2"> + <User className="w-4 h-4" /> + <span>{biddingDetail.bidPicName || '미설정'}</span> + </div> + {biddingDetail.bidPicPhone && ( + <div className="text-xs text-muted-foreground ml-6"> + {biddingDetail.bidPicPhone} + </div> + )} </div> </div> <div> @@ -868,11 +864,12 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <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 deadline = new Date(biddingDetail.submissionEndDate) // 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)) + const kstDeadline = new Date(deadline.getTime() + 9 * 60 * 60 * 1000).toISOString().slice(0, 16).replace('T', ' ') return ( <div className={`p-3 rounded-lg border-2 ${ @@ -887,7 +884,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <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', ' ')} + {kstDeadline} </span> </div> {isExpired ? ( @@ -921,7 +918,13 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm"> {biddingDetail.submissionStartDate && biddingDetail.submissionEndDate && ( <div> - <span className="font-medium">입찰서 제출기간:</span> {new Date(biddingDetail.submissionStartDate).toISOString().slice(0, 16).replace('T', ' ')} ~ {new Date(biddingDetail.submissionEndDate).toISOString().slice(0, 16).replace('T', ' ')} + <span className="font-medium">입찰서 제출기간:</span> {(() => { + const start = new Date(biddingDetail.submissionStartDate!) + const end = new Date(biddingDetail.submissionEndDate!) + const kstStart = new Date(start.getTime() + 9 * 60 * 60 * 1000).toISOString().slice(0, 16).replace('T', ' ') + const kstEnd = new Date(end.getTime() + 9 * 60 * 60 * 1000).toISOString().slice(0, 16).replace('T', ' ') + return `${kstStart} ~ ${kstEnd}` + })()} </div> )} {biddingDetail.evaluationDate && ( @@ -1080,45 +1083,75 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD </CardContent> </Card> ) : biddingDetail.isBiddingParticipated === null ? ( - /* 참여 의사 확인 섹션 */ - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - <Users className="w-5 h-5" /> - 입찰 참여 의사 확인 - </CardTitle> - </CardHeader> - <CardContent> - <div className="text-center py-8"> - <div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4"> - <Users className="w-8 h-8 text-primary" /> - </div> - <h3 className="text-lg font-semibold mb-2">이 입찰에 참여하시겠습니까?</h3> - <p className="text-muted-foreground mb-6"> - 참여를 선택하시면 입찰 작성 및 제출이 가능합니다. - </p> - <div className="flex justify-center gap-4"> - <Button - onClick={() => handleParticipationDecision(true)} - disabled={isUpdatingParticipation || isExpired} - className="min-w-[120px]" - > - <CheckCircle className="w-4 h-4 mr-2" /> - {isExpired ? '마감됨' : '참여하기'} - </Button> - <Button - onClick={() => handleParticipationDecision(false)} - disabled={isUpdatingParticipation || isExpired} - variant="destructive" - className="min-w-[120px]" - > - <XCircle className="w-4 h-4 mr-2" /> - 미참여 - </Button> + <> + {/* 품목 정보 확인 (Read Only) */} + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <Package className="w-5 h-5" /> + 입찰 품목 정보 + </CardTitle> + </CardHeader> + <CardContent> + {prItems.length > 0 ? ( + <PrItemsPricingTable + prItems={prItems} + initialQuotations={prItemQuotations} + currency={biddingDetail?.currency || 'KRW'} + onQuotationsChange={() => {}} + onTotalAmountChange={() => {}} + readOnly={true} + /> + ) : ( + <div className="border rounded-lg p-4 bg-muted/20"> + <p className="text-sm text-muted-foreground text-center py-4"> + 등록된 품목이 없습니다. + </p> + </div> + )} + </CardContent> + </Card> + + {/* 참여 의사 확인 섹션 */} + <Card className="mt-6"> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <Users className="w-5 h-5" /> + 입찰 참여 의사 확인 + </CardTitle> + </CardHeader> + <CardContent> + <div className="text-center py-8"> + <div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4"> + <Users className="w-8 h-8 text-primary" /> + </div> + <h3 className="text-lg font-semibold mb-2">이 입찰에 참여하시겠습니까?</h3> + <p className="text-muted-foreground mb-6"> + 참여를 선택하시면 입찰 작성 및 제출이 가능합니다. + </p> + <div className="flex justify-center gap-4"> + <Button + onClick={() => handleParticipationDecision(true)} + disabled={isUpdatingParticipation || isExpired} + className="min-w-[120px]" + > + <CheckCircle className="w-4 h-4 mr-2" /> + {isExpired ? '마감됨' : '참여하기'} + </Button> + <Button + onClick={() => handleParticipationDecision(false)} + disabled={isUpdatingParticipation || isExpired} + variant="destructive" + className="min-w-[120px]" + > + <XCircle className="w-4 h-4 mr-2" /> + 미참여 + </Button> + </div> </div> - </div> - </CardContent> - </Card> + </CardContent> + </Card> + </> ) : biddingDetail.isBiddingParticipated === true ? ( /* 응찰 폼 섹션 */ <Card> |
