diff options
| -rw-r--r-- | components/bidding/manage/bidding-schedule-editor.tsx | 95 | ||||
| -rw-r--r-- | db/schema/bidding.ts | 4 | ||||
| -rw-r--r-- | lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx | 9 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-list-columns.tsx | 14 |
4 files changed, 83 insertions, 39 deletions
diff --git a/components/bidding/manage/bidding-schedule-editor.tsx b/components/bidding/manage/bidding-schedule-editor.tsx index f2978f95..f3260f04 100644 --- a/components/bidding/manage/bidding-schedule-editor.tsx +++ b/components/bidding/manage/bidding-schedule-editor.tsx @@ -19,7 +19,7 @@ import { BiddingInvitationDialog } from '@/lib/bidding/detail/table/bidding-invi import { sendBiddingBasicContracts, getSelectedVendorsForBidding } from '@/lib/bidding/pre-quote/service' import { registerBidding } from '@/lib/bidding/detail/service' import { useToast } from '@/hooks/use-toast' - +import { format } from 'date-fns' interface BiddingSchedule { submissionStartDate?: string submissionEndDate?: string @@ -112,7 +112,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc }) const [isLoading, setIsLoading] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) - const [biddingInfo, setBiddingInfo] = React.useState<{ title: string; projectName?: string; status: string } | null>(null) + const [biddingInfo, setBiddingInfo] = React.useState<{ title: string; projectName?: string; status: string; biddingNumber?: string } | null>(null) const [isBiddingInvitationDialogOpen, setIsBiddingInvitationDialogOpen] = React.useState(false) const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false) const [selectedVendors, setSelectedVendors] = React.useState<VendorContractRequirement[]>([]) @@ -120,6 +120,19 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc const [approvalTitle, setApprovalTitle] = React.useState('') const [invitationData, setInvitationData] = React.useState<BiddingInvitationData | null>(null) + // 차수 추출 헬퍼 함수 + const getRoundNumber = (biddingNumber: string): number => { + const match = biddingNumber.match(/-(\d+)$/) + return match ? parseInt(match[1]) : 1 + } + + // 차수 증가된 입찰인지 확인 (01이 아닌 경우) + const isRoundIncreased = (biddingNumber?: string): boolean => { + if (!biddingNumber) return false + const round = getRoundNumber(biddingNumber) + return round > 1 + } + // 데이터 로딩 React.useEffect(() => { const loadSchedule = async () => { @@ -132,25 +145,12 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc title: bidding.title || '', projectName: bidding.projectName || undefined, status: bidding.status || '', + biddingNumber: bidding.biddingNumber || undefined, }) - // 날짜를 문자열로 변환하는 헬퍼 - const formatDateTime = (date: unknown): string => { - if (!date) return '' - if (typeof date === 'string') { - // 이미 datetime-local 형식인 경우 - if (date.includes('T')) { - return date.slice(0, 16) - } - return date - } - if (date instanceof Date) return date.toISOString().slice(0, 16) - return '' - } - setSchedule({ - submissionStartDate: formatDateTime(bidding.submissionStartDate), - submissionEndDate: formatDateTime(bidding.submissionEndDate), + submissionStartDate: bidding.submissionStartDate ? new Date(bidding.submissionStartDate).toISOString().slice(0, 16) : '', + submissionEndDate: bidding.submissionEndDate ? new Date(bidding.submissionEndDate).toISOString().slice(0, 16) : '', remarks: bidding.remarks || '', isUrgent: bidding.isUrgent || false, hasSpecificationMeeting: bidding.hasSpecificationMeeting || false, @@ -286,6 +286,48 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc } } + // 차수 증가된 입찰의 직접 입찰공고 함수 (결재 생략) + const handleDirectBiddingInvitation = async (data: BiddingInvitationData, vendors: VendorContractRequirement[]) => { + try { + if (!session?.user?.id) { + toast({ + title: '오류', + description: '사용자 정보가 없습니다.', + variant: 'destructive', + }) + return + } + + // 입찰 등록 (결재 생략) + const result = await registerBidding(biddingId, session.user.id.toString()) + + if (result.success) { + toast({ + title: '입찰공고 완료', + description: '차수 증가된 입찰이 성공적으로 공고되었습니다.', + }) + + // 다이얼로그 닫기 및 페이지 새로고침 + setIsBiddingInvitationDialogOpen(false) + setInvitationData(null) + router.refresh() + } else { + toast({ + title: '오류', + description: result.error || '입찰공고 중 오류가 발생했습니다.', + variant: 'destructive', + }) + } + } catch (error) { + console.error('직접 입찰공고 중 오류 발생:', error) + toast({ + title: '오류', + description: '입찰공고 중 오류가 발생했습니다.', + variant: 'destructive', + }) + } + } + // 입찰 초대 발송 핸들러 - 결재 준비 및 결재 다이얼로그 열기 const handleBiddingInvitationSend = async (data: BiddingInvitationData) => { try { @@ -309,7 +351,14 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc return } - // 결재 데이터 준비 (템플릿 변수, 제목 등) + // 차수 증가된 입찰(02, 03 등)인지 확인 + if (isRoundIncreased(biddingInfo?.biddingNumber)) { + // 차수 증가된 입찰은 결재 생략하고 바로 입찰 등록 + await handleDirectBiddingInvitation(data, vendors) + return + } + + // 일반 입찰의 경우 결재 데이터 준비 (템플릿 변수, 제목 등) const approvalData = await prepareBiddingApprovalData({ biddingId, vendors, @@ -435,7 +484,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc <Input id="submission-start" type="datetime-local" - value={schedule.submissionStartDate || ''} + value={schedule.submissionStartDate} onChange={(e) => handleScheduleChange('submissionStartDate', e.target.value)} /> </div> @@ -444,7 +493,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc <Input id="submission-end" type="datetime-local" - value={schedule.submissionEndDate || ''} + value={schedule.submissionEndDate} onChange={(e) => handleScheduleChange('submissionEndDate', e.target.value)} /> </div> @@ -613,7 +662,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc <span className="font-medium">입찰서 제출 기간:</span> <span> {schedule.submissionStartDate && schedule.submissionEndDate - ? `${new Date(schedule.submissionStartDate).toLocaleString('ko-KR')} ~ ${new Date(schedule.submissionEndDate).toLocaleString('ko-KR')}` + ? `${format(new Date(schedule.submissionStartDate), "yyyy-MM-dd HH:mm")} ~ ${format(new Date(schedule.submissionEndDate), "yyyy-MM-dd HH:mm")}` : '미설정' } </span> @@ -634,7 +683,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc <div className="flex justify-between"> <span className="font-medium">사양설명회 일시:</span> <span> - {new Date(specMeetingInfo.meetingDate).toLocaleString('ko-KR')} + {format(new Date(specMeetingInfo.meetingDate), "yyyy-MM-dd HH:mm")} </span> </div> )} diff --git a/db/schema/bidding.ts b/db/schema/bidding.ts index ab9b3373..d657086f 100644 --- a/db/schema/bidding.ts +++ b/db/schema/bidding.ts @@ -698,10 +698,10 @@ export const biddingStatusLabels = { vendor_selected: '업체선정', bid_opening: '개찰', early_bid_opening: '조기개찰', - rebidding: '재입찰', + rebidding: '입찰종료-재입찰', disposal_cancelled: '유찰취소', bid_closure: '폐찰', - round_increase: '차수증가' + round_increase: '입찰종료-차수증가' } as const export const contractTypeLabels = { diff --git a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx index c1d59677..34ee690f 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx @@ -215,12 +215,7 @@ export function BiddingDetailVendorToolbarActions({ title: "성공", description: successResult.message, }) - // 새로 생성된 입찰의 상세 페이지로 이동 - if (successResult.biddingId) { - router.push(`/evcp/bid/${successResult.biddingId}/info`) - } else { - router.push(`/evcp/bid/${biddingId}/info`) - } + router.push(`/evcp/bid`) onSuccess() } else { const errorResult = result as { success: false; error: string } @@ -326,7 +321,7 @@ export function BiddingDetailVendorToolbarActions({ <DialogHeader> <DialogTitle>차수증가 확인</DialogTitle> <DialogDescription> - 입찰을 차수증가 처리하시겠습니까? 차수증가 후 새로운 입찰 화면으로 이동합니다. + 입찰을 차수증가 처리하시겠습니까? 차수증가 후 입찰 관리 페이지로 이동합니다. </DialogDescription> </DialogHeader> <DialogFooter> diff --git a/lib/bidding/vendor/partners-bidding-list-columns.tsx b/lib/bidding/vendor/partners-bidding-list-columns.tsx index 6e8591c2..d9058e97 100644 --- a/lib/bidding/vendor/partners-bidding-list-columns.tsx +++ b/lib/bidding/vendor/partners-bidding-list-columns.tsx @@ -20,7 +20,7 @@ import { Paperclip, AlertTriangle } from 'lucide-react' -import { formatDate } from '@/lib/utils' +import { format } from 'date-fns' import { biddingStatusLabels, contractTypeLabels } from '@/db/schema' import { PartnersBiddingListItem } from '../detail/service' import { Checkbox } from '@/components/ui/checkbox' @@ -291,9 +291,9 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL } return ( <div className="text-sm"> - <div>{formatDate(startDate, 'KR')}</div> + <div>{format(new Date(startDate), "yyyy-MM-dd")}</div> <div className="text-muted-foreground">~</div> - <div>{formatDate(endDate, 'KR')}</div> + <div>{format(new Date(endDate), "yyyy-MM-dd")}</div> </div> ) }, @@ -315,7 +315,7 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL 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> + <span>{format(new Date(deadline), "yyyy-MM-dd HH:mm")}</span> {isExpired && ( <Badge variant="destructive" className="text-xs"> 마감 @@ -339,9 +339,9 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL return ( <div className="text-sm"> - <div>{formatDate(startDate, 'KR')}</div> + <div>{format(new Date(startDate), "yyyy-MM-dd")}</div> <div className="text-muted-foreground">~</div> - <div>{formatDate(endDate, 'KR')}</div> + <div>{format(new Date(endDate), "yyyy-MM-dd")}</div> </div> ) }, @@ -387,7 +387,7 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL columnHelper.accessor('updatedAt', { header: '최종수정일', cell: ({ row }) => ( - <div className="text-sm">{formatDate(row.original.updatedAt, 'KR')}</div> + <div className="text-sm">{format(new Date(row.original.updatedAt), "yyyy-MM-dd HH:mm")}</div> ), }), |
