diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-11 11:21:35 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-11 11:21:35 +0000 |
| commit | 47e527f5f763658600696ee58451fb666e692f5a (patch) | |
| tree | 67f159fd0cbad5e0553c7958caa3075127121e76 /lib/bidding/vendor/partners-bidding-detail.tsx | |
| parent | ee77f36b1ceece1236d45fba102c3ea410acebc1 (diff) | |
(최겸) 구매 입찰 세부기능 수정
Diffstat (limited to 'lib/bidding/vendor/partners-bidding-detail.tsx')
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-detail.tsx | 190 |
1 files changed, 123 insertions, 67 deletions
diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx index 8d24ca66..4b316eee 100644 --- a/lib/bidding/vendor/partners-bidding-detail.tsx +++ b/lib/bidding/vendor/partners-bidding-detail.tsx @@ -60,13 +60,13 @@ interface BiddingDetail { content: string | null contractType: string biddingType: string - awardCount: string + awardCount: string | null contractPeriod: string | null - preQuoteDate: string | null - biddingRegistrationDate: string | null - submissionStartDate: string | null - submissionEndDate: string | null - evaluationDate: string | null + preQuoteDate: Date | null + biddingRegistrationDate: Date | null + submissionStartDate: Date | null + submissionEndDate: Date | null + evaluationDate: Date | null currency: string budget: number | null targetPrice: number | null @@ -78,12 +78,12 @@ interface BiddingDetail { biddingId: number invitationStatus: string finalQuoteAmount: number | null - finalQuoteSubmittedAt: string | null + finalQuoteSubmittedAt: Date | null isWinner: boolean isAttendingMeeting: boolean | null isBiddingParticipated: boolean | null additionalProposals: string | null - responseSubmittedAt: string | null + responseSubmittedAt: Date | null } interface PrItem { @@ -101,11 +101,11 @@ interface PrItem { hasSpecDocument: boolean | null } -interface PrItemQuotation { +interface BiddingPrItemQuotation { prItemId: number bidUnitPrice: number bidAmount: number - proposedDeliveryDate?: string | null + proposedDeliveryDate?: string technicalSpecification?: string } @@ -122,7 +122,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD // 품목별 견적 관련 상태 const [prItems, setPrItems] = React.useState<PrItem[]>([]) - const [prItemQuotations, setPrItemQuotations] = React.useState<PrItemQuotation[]>([]) + const [prItemQuotations, setPrItemQuotations] = React.useState<BiddingPrItemQuotation[]>([]) const [totalQuotationAmount, setTotalQuotationAmount] = React.useState(0) // 응찰 폼 상태 @@ -169,7 +169,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD prItemId: item.prItemId, bidUnitPrice: item.bidUnitPrice, bidAmount: item.bidAmount, - proposedDeliveryDate: item.proposedDeliveryDate || '', + proposedDeliveryDate: item.proposedDeliveryDate || undefined, technicalSpecification: item.technicalSpecification || undefined })) @@ -219,14 +219,43 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD if (result.success) { toast({ - title: '성공', - description: result.message, + title: participated ? '참여 확정' : '미참여 확정', + description: participated + ? '입찰에 참여하셨습니다. 이제 견적을 작성할 수 있습니다.' + : '입찰 참여를 거절하셨습니다.', }) // 데이터 새로고침 const updatedDetail = await getBiddingDetailsForPartners(biddingId, companyId) if (updatedDetail) { setBiddingDetail(updatedDetail) + + // 참여 확정 시 사전견적 데이터가 있다면 로드 + 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() + })) + } + } catch (error) { + console.error('Failed to load pre-quote data after participation:', error) + } + } } } else { toast({ @@ -248,7 +277,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD } // 품목별 견적 변경 핸들러 - const handleQuotationsChange = (quotations: PrItemQuotation[]) => { + const handleQuotationsChange = (quotations: BiddingPrItemQuotation[]) => { console.log('견적 변경:', quotations) setPrItemQuotations(quotations) } @@ -282,7 +311,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD prItemId: q.prItemId, bidUnitPrice: q.bidUnitPrice, bidAmount: q.bidAmount, - proposedDeliveryDate: q.proposedDeliveryDate || undefined, + proposedDeliveryDate: q.proposedDeliveryDate, technicalSpecification: q.technicalSpecification })) @@ -367,7 +396,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD prItemId: q.prItemId, bidUnitPrice: q.bidUnitPrice, bidAmount: q.bidAmount, - proposedDeliveryDate: q.proposedDeliveryDate || undefined, + proposedDeliveryDate: q.proposedDeliveryDate, technicalSpecification: q.technicalSpecification })) : undefined, }, @@ -445,7 +474,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <div className="flex items-center gap-2 mt-1"> <Badge variant="outline" className="font-mono"> {biddingDetail.biddingNumber} - {biddingDetail.revision > 0 && ` Rev.${biddingDetail.revision}`} + {biddingDetail.revision && biddingDetail.revision > 0 && ` Rev.${biddingDetail.revision}`} </Badge> <Badge variant={ biddingDetail.status === 'bidding_disposal' ? 'destructive' : @@ -460,24 +489,13 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD {/* 입찰 참여여부 상태 표시 */} <div className="flex items-center gap-2"> - {biddingDetail.isBiddingParticipated === null ? ( - <div className="flex items-center gap-2"> - <Badge variant="outline">참여 결정 대기</Badge> - <Button - onClick={() => handleParticipationDecision(false)} - disabled={isUpdatingParticipation} - variant="destructive" - size="sm" - > - <XCircle className="w-4 h-4 mr-1" /> - 미응찰 - </Button> - </div> - ) : ( - <Badge variant={biddingDetail.isBiddingParticipated ? 'default' : 'destructive'}> - {biddingDetail.isBiddingParticipated ? '응찰' : '미응찰'} - </Badge> - )} + <Badge variant={ + biddingDetail.isBiddingParticipated === null ? 'outline' : + biddingDetail.isBiddingParticipated === true ? 'default' : 'destructive' + }> + {biddingDetail.isBiddingParticipated === null ? '참여 결정 대기' : + biddingDetail.isBiddingParticipated === true ? '응찰' : '미응찰'} + </Badge> </div> </div> @@ -516,10 +534,10 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD </div> <div> <Label className="text-sm font-medium text-muted-foreground">낙찰수</Label> - <div className="mt-1">{biddingDetail.awardCount === 'single' ? '단수' : '복수'}</div> + <div className="mt-1">{biddingDetail.awardCount === 'single' ? '단수' : biddingDetail.awardCount === 'multiple' ? '복수' : '미설정'}</div> </div> <div> - <Label className="text-sm font-medium text-muted-foreground">담당자</Label> + <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.managerName || '미설정'}</span> @@ -527,7 +545,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD </div> </div> - {biddingDetail.budget && ( + {/* {biddingDetail.budget && ( <div> <Label className="text-sm font-medium text-muted-foreground">예산</Label> <div className="flex items-center gap-2 mt-1"> @@ -535,7 +553,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <span className="font-semibold">{formatCurrency(biddingDetail.budget)}</span> </div> </div> - )} + )} */} {/* 일정 정보 */} <div className="pt-4 border-t"> @@ -560,33 +578,73 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD {/* 참여 상태에 따른 섹션 표시 */} {biddingDetail.isBiddingParticipated === false ? ( /* 미응찰 상태 표시 */ - <Card> - <CardHeader> + <Card> + <CardHeader> <CardTitle className="flex items-center gap-2"> <XCircle className="w-5 h-5 text-destructive" /> 입찰 참여 거절 </CardTitle> - </CardHeader> - <CardContent> + </CardHeader> + <CardContent> <div className="text-center py-8"> <XCircle className="w-16 h-16 text-destructive mx-auto mb-4" /> <h3 className="text-lg font-semibold text-destructive mb-2">입찰에 참여하지 않기로 결정했습니다</h3> <p className="text-muted-foreground"> 해당 입찰에 대한 견적 제출 및 관련 기능은 이용할 수 없습니다. </p> - </div> - </CardContent> - </Card> - ) : biddingDetail.isBiddingParticipated === true || biddingDetail.isBiddingParticipated === null ? ( + </div> + </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} + className="min-w-[120px]" + > + <CheckCircle className="w-4 h-4 mr-2" /> + 참여하기 + </Button> + <Button + onClick={() => handleParticipationDecision(false)} + disabled={isUpdatingParticipation} + variant="destructive" + className="min-w-[120px]" + > + <XCircle className="w-4 h-4 mr-2" /> + 미참여 + </Button> + </div> + </div> + </CardContent> + </Card> + ) : biddingDetail.isBiddingParticipated === true ? ( /* 응찰 폼 섹션 */ - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - <Send className="w-5 h-5" /> - 응찰하기 - </CardTitle> - </CardHeader> - <CardContent className="space-y-6"> + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <Send className="w-5 h-5" /> + 응찰하기 + </CardTitle> + </CardHeader> + <CardContent className="space-y-6"> {/* 품목별 견적 섹션 */} {/* <div className="space-y-2"> <Label htmlFor="finalQuoteAmount">총 견적금액 *</Label> @@ -648,24 +706,22 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD rows={4} /> </div> */} - {/* 응찰 제출 버튼 - 미응찰 상태가 아닐 때만 표시 */} - {(biddingDetail.isBiddingParticipated === true || biddingDetail.isBiddingParticipated === null) && ( + {/* 응찰 제출 버튼 - 참여 확정 상태일 때만 표시 */} <div className="flex justify-end pt-4 gap-2"> <Button - variant="outline" - onClick={handleSaveDraft} - disabled={isSavingDraft || isSubmitting} - className="min-w-[100px]" - > - <Save className="w-4 h-4 mr-2" /> - {isSavingDraft ? '저장 중...' : '임시 저장'} - </Button> + variant="outline" + onClick={handleSaveDraft} + disabled={isSavingDraft || isSubmitting} + className="min-w-[100px]" + > + <Save className="w-4 h-4 mr-2" /> + {isSavingDraft ? '저장 중...' : '임시 저장'} + </Button> <Button onClick={handleSubmitResponse} disabled={isSubmitting || isSavingDraft} className="min-w-[100px]"> <Send className="w-4 h-4 mr-2" /> {isSubmitting ? '제출 중...' : '응찰 제출'} </Button> </div> - )} </CardContent> </Card> ) : null} |
