summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor/partners-bidding-detail.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/vendor/partners-bidding-detail.tsx')
-rw-r--r--lib/bidding/vendor/partners-bidding-detail.tsx205
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>