diff options
Diffstat (limited to 'lib/bidding/vendor')
| -rw-r--r-- | lib/bidding/vendor/components/simple-file-upload.tsx | 20 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-attendance-dialog.tsx | 31 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-list-columns.tsx | 61 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-list.tsx | 12 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-pre-quote.tsx | 389 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-toolbar-actions.tsx | 31 |
6 files changed, 408 insertions, 136 deletions
diff --git a/lib/bidding/vendor/components/simple-file-upload.tsx b/lib/bidding/vendor/components/simple-file-upload.tsx index b1eb8b8f..58b60bdf 100644 --- a/lib/bidding/vendor/components/simple-file-upload.tsx +++ b/lib/bidding/vendor/components/simple-file-upload.tsx @@ -65,15 +65,23 @@ export function SimpleFileUpload({ try { setIsLoading(true) const docs = await getPreQuoteDocuments(biddingId, companyId) + + // docs가 undefined이거나 배열이 아닌 경우 빈 배열로 처리 + if (!docs || !Array.isArray(docs)) { + setDocuments([]) + return + } + // Date를 string으로 변환 const mappedDocs = docs.map(doc => ({ ...doc, - uploadedAt: doc.uploadedAt.toString(), + uploadedAt: doc.uploadedAt?.toString() || '', uploadedBy: doc.uploadedBy || '' })) setDocuments(mappedDocs) } catch (error) { console.error('Failed to load documents:', error) + setDocuments([]) // 에러 시에도 빈 배열로 설정 toast({ title: '오류', description: '업로드된 문서 목록을 불러오는데 실패했습니다.', @@ -155,9 +163,13 @@ export function SimpleFileUpload({ if (result.success) { try { - await downloadFile(result.document?.filePath, result.document?.originalFileName, { - showToast: true - }) + if (result.document?.filePath && result.document?.originalFileName) { + await downloadFile(result.document.filePath, result.document.originalFileName, { + showToast: true + }) + } else { + throw new Error('파일 정보가 없습니다.') + } } catch (error) { toast({ title: '다운로드 실패', diff --git a/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx b/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx index 9205c46a..e93702ed 100644 --- a/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx +++ b/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx @@ -42,6 +42,7 @@ interface PartnersBiddingAttendanceDialogProps { preQuoteDate: string | null biddingRegistrationDate: string | null evaluationDate: string | null + hasSpecificationMeeting?: boolean // 사양설명회 여부 추가 } | null biddingCompanyId: number isAttending: boolean | null @@ -206,6 +207,36 @@ export function PartnersBiddingAttendanceDialog({ if (!biddingDetail) return null + // 사양설명회가 없는 경우 + if (biddingDetail.hasSpecificationMeeting === false) { + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="max-w-md"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <Users className="w-5 h-5" /> + 사양설명회 정보 + </DialogTitle> + </DialogHeader> + + <div className="py-6 text-center"> + <XCircle className="w-12 h-12 text-muted-foreground mx-auto mb-4" /> + <h3 className="text-lg font-medium mb-2">사양설명회 없음</h3> + <p className="text-muted-foreground"> + 해당 입찰 건은 사양설명회가 없습니다. + </p> + </div> + + <DialogFooter> + <Button onClick={() => onOpenChange(false)}> + 확인 + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) + } + return ( <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-4xl max-h-[90vh]"> diff --git a/lib/bidding/vendor/partners-bidding-list-columns.tsx b/lib/bidding/vendor/partners-bidding-list-columns.tsx index 0d1e3123..7058f026 100644 --- a/lib/bidding/vendor/partners-bidding-list-columns.tsx +++ b/lib/bidding/vendor/partners-bidding-list-columns.tsx @@ -94,7 +94,7 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL // 액션 (드롭다운 메뉴) columnHelper.display({ id: 'actions', - header: 'Actions', + header: '액션', cell: ({ row }) => { const handleView = () => { if (setRowAction) { @@ -122,13 +122,7 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL }) } } - - const canManageAttendance = row.original.invitationStatus === 'sent' || - row.original.invitationStatus === 'accepted' - // 사전견적이 가능한 조건: 초대 발송(sent) 상태인 경우 - const canDoPreQuote = row.original.invitationStatus === 'sent' || row.original.invitationStatus === 'pending' || row.original.invitationStatus === 'submitted'; - return ( <DropdownMenu> <DropdownMenuTrigger asChild> @@ -143,20 +137,12 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL <DropdownMenuContent align="end" className="w-[160px]"> <DropdownMenuItem onClick={handleView}> <FileText className="mr-2 h-4 w-4" /> - 상세보기 + 입찰 상세보기 </DropdownMenuItem> - {canDoPreQuote && ( <DropdownMenuItem onClick={handlePreQuote}> <Calculator className="mr-2 h-4 w-4" /> 사전견적하기 </DropdownMenuItem> - )} - {canManageAttendance && ( - <DropdownMenuItem onClick={handleAttendance}> - <Users className="mr-2 h-4 w-4" /> - 참석여부 - </DropdownMenuItem> - )} </DropdownMenuContent> </DropdownMenu> ) @@ -199,6 +185,22 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL }, }), + // 사전견적 참여의사 + columnHelper.accessor('isPreQuoteParticipated', { + header: '사전견적 참여의사', + cell: ({ row }) => { + const participated = row.original.isPreQuoteParticipated + if (participated === null) { + return <Badge variant="outline">미결정</Badge> + } + return ( + <Badge variant={participated ? 'default' : 'destructive'}> + {participated ? '참여' : '미참여'} + </Badge> + ) + }, + }), + // 입찰 참여의사 columnHelper.accessor('invitationStatus', { header: '입찰 참여의사', @@ -250,6 +252,33 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL }, }), + // 사전견적 마감일 + columnHelper.accessor('preQuoteDeadline', { + header: '사전견적 마감일', + cell: ({ row }) => { + const deadline = row.original.preQuoteDeadline + if (!deadline) { + return <div className="text-muted-foreground">-</div> + } + + const now = new Date() + const deadlineDate = new Date(deadline) + const isExpired = deadlineDate < now + + return ( + <div className={`text-sm flex items-center gap-1 ${isExpired ? 'text-red-600' : ''}`}> + <Calendar className="w-4 h-4" /> + <span>{formatDate(deadline, 'KR')}</span> + {isExpired && ( + <Badge variant="destructive" className="text-xs"> + 마감 + </Badge> + )} + </div> + ) + }, + }), + // 계약기간 columnHelper.accessor('contractPeriod', { header: '계약기간', diff --git a/lib/bidding/vendor/partners-bidding-list.tsx b/lib/bidding/vendor/partners-bidding-list.tsx index 2e8d4164..08489da5 100644 --- a/lib/bidding/vendor/partners-bidding-list.tsx +++ b/lib/bidding/vendor/partners-bidding-list.tsx @@ -93,6 +93,17 @@ export function PartnersBiddingList({ companyId }: PartnersBiddingListProps) { case 'view': // 본입찰 초대 여부 확인 const bidding = rowAction.row.original + + // 사전견적 요청 상태에서는 상세보기 제한 + if (bidding.status === 'request_for_quotation') { + toast({ + title: '접근 제한', + description: '사전견적 요청 상태에서는 상세보기를 이용할 수 없습니다.', + variant: 'destructive', + }) + return + } + if (bidding.status === 'bidding_opened' && !bidding.isBiddingInvited) { // 본입찰이 오픈되었지만 초대받지 않은 경우 toast({ @@ -227,6 +238,7 @@ export function PartnersBiddingList({ companyId }: PartnersBiddingListProps) { preQuoteDate: null, biddingRegistrationDate: rowAction.row.original.submissionStartDate?.toISOString() || null, evaluationDate: null, + hasSpecificationMeeting: (rowAction.row.original as any).hasSpecificationMeeting || false, // 사양설명회 여부 추가 } : null} biddingCompanyId={rowAction?.row.original?.biddingCompanyId || 0} isAttending={rowAction?.row.original?.isAttendingMeeting || null} diff --git a/lib/bidding/vendor/partners-bidding-pre-quote.tsx b/lib/bidding/vendor/partners-bidding-pre-quote.tsx index 4cd0efdb..fdd05154 100644 --- a/lib/bidding/vendor/partners-bidding-pre-quote.tsx +++ b/lib/bidding/vendor/partners-bidding-pre-quote.tsx @@ -30,7 +30,8 @@ import { submitPreQuoteResponse, getPrItemsForBidding, getSavedPrItemQuotations, - savePreQuoteDraft + savePreQuoteDraft, + setPreQuoteParticipation } from '../pre-quote/service' import { getBiddingConditions } from '../service' import { getPriceAdjustmentFormByBiddingCompanyId } from '../detail/service' @@ -80,6 +81,7 @@ interface BiddingDetail { invitationStatus: string | null preQuoteAmount: string | null preQuoteSubmittedAt: string | null + preQuoteDeadline: string | null isPreQuoteSelected: boolean | null isAttendingMeeting: boolean | null // companyConditionResponses에서 가져온 조건들 (제시된 조건과 응답 모두) @@ -126,6 +128,9 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin isAttendingMeeting: false, }) + // 사전견적 참여의사 상태 + const [participationDecision, setParticipationDecision] = React.useState<boolean | null>(null) + // 연동제 폼 상태 const [priceAdjustmentForm, setPriceAdjustmentForm] = React.useState({ itemName: '', @@ -211,6 +216,9 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin additionalProposals: result.additionalProposals || '', isAttendingMeeting: result.isAttendingMeeting || false, }) + + // 사전견적 참여의사 초기화 + setParticipationDecision(result.isPreQuoteParticipated) } if (conditions) { @@ -238,64 +246,131 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin // 임시저장 기능 const handleTempSave = () => { - if (!biddingDetail) return + if (!biddingDetail || !biddingDetail.biddingCompanyId) { + toast({ + title: '임시저장 실패', + description: '입찰 정보가 올바르지 않습니다.', + variant: 'destructive', + }) + return + } + + if (!userId) { + toast({ + title: '임시저장 실패', + description: '사용자 정보를 확인할 수 없습니다. 다시 로그인해주세요.', + variant: 'destructive', + }) + return + } setIsSaving(true) startTransition(async () => { - const result = await savePreQuoteDraft( + try { + const result = await savePreQuoteDraft( + biddingDetail.biddingCompanyId!, + { + prItemQuotations, + paymentTermsResponse: responseData.paymentTermsResponse, + taxConditionsResponse: responseData.taxConditionsResponse, + incotermsResponse: responseData.incotermsResponse, + proposedContractDeliveryDate: responseData.proposedContractDeliveryDate, + proposedShippingPort: responseData.proposedShippingPort, + proposedDestinationPort: responseData.proposedDestinationPort, + priceAdjustmentResponse: responseData.priceAdjustmentResponse, + isInitialResponse: responseData.isInitialResponse, + sparePartResponse: responseData.sparePartResponse, + additionalProposals: responseData.additionalProposals, + priceAdjustmentForm: responseData.priceAdjustmentResponse ? { + itemName: priceAdjustmentForm.itemName, + adjustmentReflectionPoint: priceAdjustmentForm.adjustmentReflectionPoint, + majorApplicableRawMaterial: priceAdjustmentForm.majorApplicableRawMaterial, + adjustmentFormula: priceAdjustmentForm.adjustmentFormula, + rawMaterialPriceIndex: priceAdjustmentForm.rawMaterialPriceIndex, + referenceDate: priceAdjustmentForm.referenceDate, + comparisonDate: priceAdjustmentForm.comparisonDate, + adjustmentRatio: priceAdjustmentForm.adjustmentRatio ? parseFloat(priceAdjustmentForm.adjustmentRatio) : undefined, + notes: priceAdjustmentForm.notes, + adjustmentConditions: priceAdjustmentForm.adjustmentConditions, + majorNonApplicableRawMaterial: priceAdjustmentForm.majorNonApplicableRawMaterial, + adjustmentPeriod: priceAdjustmentForm.adjustmentPeriod, + contractorWriter: priceAdjustmentForm.contractorWriter, + adjustmentDate: priceAdjustmentForm.adjustmentDate, + nonApplicableReason: priceAdjustmentForm.nonApplicableReason, + } : undefined + }, + userId + ) + + if (result.success) { + toast({ + title: '임시저장 완료', + description: result.message, + }) + } else { + toast({ + title: '임시저장 실패', + description: result.error, + variant: 'destructive', + }) + } + } catch (error) { + console.error('Temp save error:', error) + toast({ + title: '임시저장 실패', + description: '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', + variant: 'destructive', + }) + } finally { + setIsSaving(false) + } + }) + } + + // 사전견적 참여의사 설정 함수 + const handleParticipationDecision = async (participate: boolean) => { + if (!biddingDetail?.biddingCompanyId) return + + startTransition(async () => { + const result = await setPreQuoteParticipation( biddingDetail.biddingCompanyId!, - { - prItemQuotations, - paymentTermsResponse: responseData.paymentTermsResponse, - taxConditionsResponse: responseData.taxConditionsResponse, - incotermsResponse: responseData.incotermsResponse, - proposedContractDeliveryDate: responseData.proposedContractDeliveryDate, - proposedShippingPort: responseData.proposedShippingPort, - proposedDestinationPort: responseData.proposedDestinationPort, - priceAdjustmentResponse: responseData.priceAdjustmentResponse, - isInitialResponse: responseData.isInitialResponse, - sparePartResponse: responseData.sparePartResponse, - additionalProposals: responseData.additionalProposals, - priceAdjustmentForm: responseData.priceAdjustmentResponse ? { - itemName: priceAdjustmentForm.itemName, - adjustmentReflectionPoint: priceAdjustmentForm.adjustmentReflectionPoint, - majorApplicableRawMaterial: priceAdjustmentForm.majorApplicableRawMaterial, - adjustmentFormula: priceAdjustmentForm.adjustmentFormula, - rawMaterialPriceIndex: priceAdjustmentForm.rawMaterialPriceIndex, - referenceDate: priceAdjustmentForm.referenceDate, - comparisonDate: priceAdjustmentForm.comparisonDate, - adjustmentRatio: priceAdjustmentForm.adjustmentRatio ? parseFloat(priceAdjustmentForm.adjustmentRatio) : undefined, - notes: priceAdjustmentForm.notes, - adjustmentConditions: priceAdjustmentForm.adjustmentConditions, - majorNonApplicableRawMaterial: priceAdjustmentForm.majorNonApplicableRawMaterial, - adjustmentPeriod: priceAdjustmentForm.adjustmentPeriod, - contractorWriter: priceAdjustmentForm.contractorWriter, - adjustmentDate: priceAdjustmentForm.adjustmentDate, - nonApplicableReason: priceAdjustmentForm.nonApplicableReason, - } : undefined - }, - 'current-user' // TODO: 실제 사용자 ID + participate, + userId ) if (result.success) { + setParticipationDecision(participate) toast({ - title: '임시저장 완료', - description: result.message, + title: '설정 완료', + description: `사전견적 ${participate ? '참여' : '미참여'}로 설정되었습니다.`, }) } else { toast({ - title: '임시저장 실패', + title: '설정 실패', description: result.error, variant: 'destructive', }) } - setIsSaving(false) }) } const handleSubmitResponse = () => { if (!biddingDetail) return + // 견적마감일 체크 + if (biddingDetail.preQuoteDeadline) { + const now = new Date() + const deadline = new Date(biddingDetail.preQuoteDeadline) + if (deadline < now) { + toast({ + title: '견적 마감', + description: '견적 마감일이 지나 제출할 수 없습니다.', + variant: 'destructive', + }) + return + } + } + // 필수값 검증 if (prItemQuotations.length === 0 || totalAmount === 0) { toast({ @@ -342,7 +417,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin const result = await submitPreQuoteResponse( biddingDetail.biddingCompanyId!, submissionData, - 'current-user' // TODO: 실제 사용자 ID + userId ) console.log('제출 결과:', result) @@ -493,7 +568,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin <span>{biddingDetail.itemName}</span> </div> </div> - <div> + {/* <div> <Label className="text-sm font-medium text-muted-foreground">계약구분</Label> <div className="mt-1">{contractTypeLabels[biddingDetail.contractType]}</div> </div> @@ -504,7 +579,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin <div> <Label className="text-sm font-medium text-muted-foreground">낙찰수</Label> <div className="mt-1">{biddingDetail.awardCount === 'single' ? '단수' : '복수'}</div> - </div> + </div> */} <div> <Label className="text-sm font-medium text-muted-foreground">담당자</Label> <div className="flex items-center gap-2 mt-1"> @@ -514,7 +589,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin </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"> @@ -522,10 +597,10 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin <span className="font-semibold">{formatCurrency(biddingDetail.budget)}</span> </div> </div> - )} + )} */} {/* 일정 정보 */} - <div className="pt-4 border-t"> + {/* <div className="pt-4 border-t"> <Label className="text-sm font-medium text-muted-foreground mb-2 block">일정 정보</Label> <div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm"> {biddingDetail.submissionStartDate && biddingDetail.submissionEndDate && ( @@ -539,7 +614,60 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin </div> )} </div> - </div> + </div> */} + + {/* 견적마감일 정보 */} + {biddingDetail.preQuoteDeadline && ( + <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.preQuoteDeadline) + 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"> + {formatDate(biddingDetail.preQuoteDeadline, 'KR')} + </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 && ( + <div className="mt-2 text-sm text-red-600"> + ⚠️ 견적 마감일이 지났습니다. 견적 제출이 불가능합니다. + </div> + )} + </div> + ) + })()} + </div> + )} </CardContent> </Card> @@ -617,28 +745,124 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin </Card> )} - {/* 품목별 견적 작성 섹션 */} - {prItems.length > 0 && ( - <PrItemsPricingTable - prItems={prItems} - initialQuotations={prItemQuotations} - currency={biddingDetail?.currency || 'KRW'} - onQuotationsChange={setPrItemQuotations} - onTotalAmountChange={setTotalAmount} - readOnly={false} - /> - )} + {/* 사전견적 참여의사 결정 섹션 */} + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <Users className="w-5 h-5" /> + 사전견적 참여의사 결정 + </CardTitle> + </CardHeader> + <CardContent> + {participationDecision === null ? ( + <div className="space-y-4"> + <p className="text-muted-foreground"> + 해당 입찰의 사전견적에 참여하시겠습니까? + </p> + <div className="flex gap-3"> + <Button + onClick={() => handleParticipationDecision(true)} + disabled={isPending} + className="flex items-center gap-2" + > + <CheckCircle className="w-4 h-4" /> + 참여 + </Button> + <Button + variant="outline" + onClick={() => handleParticipationDecision(false)} + disabled={isPending} + className="flex items-center gap-2" + > + <XCircle className="w-4 h-4" /> + 미참여 + </Button> + </div> + </div> + ) : ( + <div className="space-y-4"> + <div className={`flex items-center gap-2 p-3 rounded-lg ${ + participationDecision ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800' + }`}> + {participationDecision ? ( + <CheckCircle className="w-5 h-5" /> + ) : ( + <XCircle className="w-5 h-5" /> + )} + <span className="font-medium"> + 사전견적 {participationDecision ? '참여' : '미참여'}로 설정되었습니다. + </span> + </div> + {participationDecision === false && ( + <div className="p-4 bg-muted rounded-lg"> + <p className="text-muted-foreground"> + 미참여로 설정되어 견적 작성 섹션이 숨겨집니다. 참여하시려면 아래 버튼을 클릭해주세요. + </p> + </div> + )} + <Button + variant="outline" + size="sm" + onClick={() => setParticipationDecision(null)} + disabled={isPending} + > + 결정 변경하기 + </Button> + </div> + )} + </CardContent> + </Card> + + {/* 참여 결정 시에만 견적 작성 섹션들 표시 (단, 견적마감일이 지나지 않은 경우에만) */} + {participationDecision === true && (() => { + // 견적마감일 체크 + if (biddingDetail?.preQuoteDeadline) { + const now = new Date() + const deadline = new Date(biddingDetail.preQuoteDeadline) + const isExpired = deadline < now + + if (isExpired) { + return ( + <Card> + <CardContent className="pt-6"> + <div className="text-center py-8"> + <XCircle className="w-12 h-12 text-red-500 mx-auto mb-4" /> + <h3 className="text-lg font-semibold text-red-700 mb-2">견적 마감</h3> + <p className="text-muted-foreground"> + 견적 마감일({formatDate(biddingDetail.preQuoteDeadline, 'KR')})이 지나 견적 제출이 불가능합니다. + </p> + </div> + </CardContent> + </Card> + ) + } + } + + return true // 견적 작성 가능 + })() && ( + <> + {/* 품목별 견적 작성 섹션 */} + {prItems.length > 0 && ( + <PrItemsPricingTable + prItems={prItems} + initialQuotations={prItemQuotations} + currency={biddingDetail?.currency || 'KRW'} + onQuotationsChange={setPrItemQuotations} + onTotalAmountChange={setTotalAmount} + readOnly={false} + /> + )} - {/* 견적 문서 업로드 섹션 */} - <SimpleFileUpload - biddingId={biddingId} - companyId={companyId} - userId={userId} - readOnly={false} - /> + {/* 견적 문서 업로드 섹션 */} + <SimpleFileUpload + biddingId={biddingId} + companyId={companyId} + userId={userId} + readOnly={false} + /> - {/* 사전견적 폼 섹션 */} - <Card> + {/* 사전견적 폼 섹션 */} + <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <Send className="w-5 h-5" /> @@ -952,30 +1176,23 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin )} <div className="flex justify-end gap-2 pt-4"> - <> - <Button - variant="outline" - onClick={handleTempSave} - disabled={isSaving || isPending} - > - <Save className="w-4 h-4 mr-2" /> - {isSaving ? '저장중...' : '임시저장'} - </Button> - <Button onClick={handleSubmitResponse} disabled={isPending || isSaving}> - <Send className="w-4 h-4 mr-2" /> - 사전견적 제출 - </Button> - </> - - {/* {biddingDetail?.invitationStatus === 'submitted' && ( - <div className="flex items-center gap-2 text-green-600"> - <CheckCircle className="w-5 h-5" /> - <span className="font-medium">사전견적이 제출되었습니다</span> - </div> - )} */} + <Button + variant="outline" + onClick={handleTempSave} + disabled={isSaving || isPending} + > + <Save className="w-4 h-4 mr-2" /> + {isSaving ? '저장중...' : '임시저장'} + </Button> + <Button onClick={handleSubmitResponse} disabled={isPending || isSaving}> + <Send className="w-4 h-4 mr-2" /> + 사전견적 제출 + </Button> </div> </CardContent> </Card> + </> + )} </div> ) } diff --git a/lib/bidding/vendor/partners-bidding-toolbar-actions.tsx b/lib/bidding/vendor/partners-bidding-toolbar-actions.tsx index c2fb6487..1500f6a7 100644 --- a/lib/bidding/vendor/partners-bidding-toolbar-actions.tsx +++ b/lib/bidding/vendor/partners-bidding-toolbar-actions.tsx @@ -24,16 +24,6 @@ export function PartnersBiddingToolbarActions({ const selectedRows = table.getFilteredSelectedRowModel().rows const selectedBidding = selectedRows.length === 1 ? selectedRows[0].original : null - // 사양설명회 참석 여부 버튼 활성화 조건 - const canManageAttendance = selectedBidding && ( - selectedBidding.invitationStatus === 'sent' || - selectedBidding.invitationStatus === 'accepted' || - selectedBidding.invitationStatus === 'submitted' - ) - - // 참여 의사 결정 버튼 활성화 조건 (sent 상태이고 아직 참여의사를 결정하지 않은 경우) - const canDecideParticipation = selectedBidding - const handleAttendanceClick = () => { if (selectedBidding && setRowAction) { setRowAction({ @@ -43,33 +33,14 @@ export function PartnersBiddingToolbarActions({ } } - const handleParticipationClick = () => { - if (selectedBidding && setRowAction) { - setRowAction({ - type: 'participation', - row: { original: selectedBidding } - }) - } - } + return ( <div className="flex items-center gap-2"> <Button variant="outline" size="sm" - onClick={handleParticipationClick} - disabled={!canDecideParticipation} - className="flex items-center gap-2" - > - <CheckCircle className="w-4 h-4" /> - 참여 의사 결정 - </Button> - - <Button - variant="outline" - size="sm" onClick={handleAttendanceClick} - disabled={!canManageAttendance} className="flex items-center gap-2" > <Users className="w-4 h-4" /> |
