From c4f5472b961afb237dc819f9dd3f42a7b8f71075 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 18 Nov 2025 10:30:31 +0000 Subject: (최겸) 구매 입찰 수정, 입찰초대 결재 등록, 재입찰, 차수증가, 폐찰, 유찰취소 로직 수정, readonly 추가 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/bidding/bidding-round-actions.tsx | 221 -------------------- .../bidding/create/bidding-create-dialog.tsx | 121 +++++++---- .../bidding/manage/bidding-basic-info-editor.tsx | 136 +++++++------ .../bidding/manage/bidding-companies-editor.tsx | 48 +---- .../manage/bidding-detail-vendor-create-dialog.tsx | 14 -- components/bidding/manage/bidding-items-editor.tsx | 49 ++--- .../bidding/manage/bidding-schedule-editor.tsx | 225 +++++++++++++-------- 7 files changed, 332 insertions(+), 482 deletions(-) delete mode 100644 components/bidding/bidding-round-actions.tsx (limited to 'components/bidding') diff --git a/components/bidding/bidding-round-actions.tsx b/components/bidding/bidding-round-actions.tsx deleted file mode 100644 index 86fea72a..00000000 --- a/components/bidding/bidding-round-actions.tsx +++ /dev/null @@ -1,221 +0,0 @@ -"use client" - -import * as React from "react" -import { useRouter } from "next/navigation" -import { useTransition } from "react" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { RefreshCw, RotateCw } from "lucide-react" -import { increaseRoundOrRebid } from "@/lib/bidding/service" -import { useToast } from "@/hooks/use-toast" - -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog" -import { useSession } from "next-auth/react" - -interface BiddingRoundActionsProps { - biddingId: number - biddingStatus?: string -} - -export function BiddingRoundActions({ biddingId, biddingStatus }: BiddingRoundActionsProps) { - const router = useRouter() - const { toast } = useToast() - const [isPending, startTransition] = useTransition() - const [showRoundDialog, setShowRoundDialog] = React.useState(false) - const [showRebidDialog, setShowRebidDialog] = React.useState(false) - const { data: session } = useSession() - const userId = session?.user?.id - - // 차수증가는 입찰공고 또는 입찰 진행중 상태에서 가능 - const canIncreaseRound = biddingStatus === 'bidding_generated' || biddingStatus === 'bidding_opened' - - // 유찰 및 낙찰은 입찰 진행중 상태에서 가능 (이 컴포넌트에서는 사용하지 않음) - - // 재입찰은 유찰 상태에서만 가능 - const canRebid = biddingStatus === 'bidding_disposal' - - const handleRoundIncrease = () => { - if (!userId) { - toast({ - title: "오류", - description: "사용자 정보를 찾을 수 없습니다.", - variant: "destructive", - }) - return - } - const userIdStr = userId as string - startTransition(async () => { - try { - const result = await increaseRoundOrRebid(biddingId, userIdStr, 'round_increase') - - if (result.success) { - toast({ - title: "성공", - description: result.message, - variant: "default", - }) - setShowRoundDialog(false) - // 새로 생성된 입찰 페이지로 이동 - if (result.biddingId) { - router.push(`/evcp/bid/${result.biddingId}`) - router.refresh() - } - } else { - toast({ - title: "오류", - description: result.error || "차수증가 중 오류가 발생했습니다.", - variant: "destructive", - }) - } - } catch (error) { - console.error('차수증가 실패:', error) - toast({ - title: "오류", - description: "차수증가 중 오류가 발생했습니다.", - variant: "destructive", - }) - } - }) - } - - const handleRebid = () => { - if (!userId) { - toast({ - title: "오류", - description: "사용자 정보를 찾을 수 없습니다.", - variant: "destructive", - }) - return - } - const userIdStr = userId as string - startTransition(async () => { - try { - const result = await increaseRoundOrRebid(biddingId, userIdStr, 'rebidding') - - if (result.success) { - toast({ - title: "성공", - description: result.message, - variant: "default", - }) - setShowRebidDialog(false) - // 새로 생성된 입찰 페이지로 이동 - if (result.biddingId) { - router.push(`/evcp/bid/${result.biddingId}`) - router.refresh() - } - } else { - toast({ - title: "오류", - description: result.error || "재입찰 중 오류가 발생했습니다.", - variant: "destructive", - }) - } - } catch (error) { - console.error('재입찰 실패:', error) - toast({ - title: "오류", - description: "재입찰 중 오류가 발생했습니다.", - variant: "destructive", - }) - } - }) - } - - // 유찰 상태가 아니면 컴포넌트를 렌더링하지 않음 - if (!canIncreaseRound && !canRebid) { - return null - } - - return ( - <> - - - 입찰 차수 관리 - - -
- - -
-

- 유찰 상태에서 차수증가 또는 재입찰을 진행할 수 있습니다. -

-
-
- - {/* 차수증가 확인 다이얼로그 */} - - - - 차수증가 - - 현재 입찰의 정보를 복제하여 새로운 차수의 입찰을 생성합니다. -
- 기존 입찰 조건, 아이템, 벤더 정보가 복제되며, 벤더 제출 정보는 초기화됩니다. -
-
- 계속하시겠습니까? -
-
- - 취소 - - {isPending ? "처리중..." : "확인"} - - -
-
- - {/* 재입찰 확인 다이얼로그 */} - - - - 재입찰 - - 현재 입찰의 정보를 복제하여 재입찰을 생성합니다. -
- 기존 입찰 조건, 아이템, 벤더 정보가 복제되며, 벤더 제출 정보는 초기화됩니다. -
-
- 계속하시겠습니까? -
-
- - 취소 - - {isPending ? "처리중..." : "확인"} - - -
-
- - ) -} - - diff --git a/components/bidding/create/bidding-create-dialog.tsx b/components/bidding/create/bidding-create-dialog.tsx index bdb00a01..c7d79435 100644 --- a/components/bidding/create/bidding-create-dialog.tsx +++ b/components/bidding/create/bidding-create-dialog.tsx @@ -38,12 +38,23 @@ import { import { TAX_CONDITIONS } from '@/lib/tax-conditions/types' import { getBiddingNoticeTemplate } from '@/lib/bidding/service' import TiptapEditor from '@/components/qna/tiptap-editor' + +// Dropzone components +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone" import { PurchaseGroupCodeSelector } from '@/components/common/selectors/purchase-group-code' import { ProcurementManagerSelector } from '@/components/common/selectors/procurement-manager' import type { PurchaseGroupCodeWithUser } from '@/components/common/selectors/purchase-group-code/purchase-group-code-service' import type { ProcurementManagerWithUser } from '@/components/common/selectors/procurement-manager/procurement-manager-service' import { createBidding } from '@/lib/bidding/service' import { useSession } from 'next-auth/react' +import { useRouter } from 'next/navigation' interface BiddingCreateDialogProps { form: UseFormReturn @@ -52,6 +63,7 @@ interface BiddingCreateDialogProps { export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProps) { const { data: session } = useSession() + const router = useRouter() const userId = session?.user?.id ? Number(session.user.id) : null; const [isOpen, setIsOpen] = React.useState(false) @@ -145,6 +157,13 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp React.useEffect(() => { if (isOpen) { + if (userId && session?.user?.name) { + // 현재 사용자의 정보를 임시로 입찰담당자로 설정 + form.setValue('bidPicName', session.user.name) + form.setValue('bidPicId', userId) + // userCode는 현재 세션에 없으므로 이름으로 설정 (실제로는 API에서 가져와야 함) + // form.setValue('bidPicCode', session.user.name) + } loadPaymentTerms() loadIncoterms() loadShippingPlaces() @@ -280,7 +299,7 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp })) // sparePartOptions가 undefined인 경우 빈 문자열로 설정 - const biddingData: CreateBiddingInput = { + const biddingData = { ...data, attachments, vendorAttachments, @@ -298,6 +317,12 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp if (result.success) { toast.success("입찰이 성공적으로 생성되었습니다.") + + // 생성된 입찰의 상세 페이지로 이동 + if ('data' in result && result.data?.id) { + router.push(`/evcp/bid/${result.data.id}`) + } + setIsOpen(false) form.reset() setShiAttachmentFiles([]) @@ -1130,30 +1155,35 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp SHI용 첨부파일

- SHI에서 제공하는 문서나 파일을 업로드하세요 + 내부 보관를 위해 필요한 문서나 파일을 업로드 하세요.

-
-
- -
-

- 파일을 드래그 앤 드롭하거나{' '} - -

-
-
-
+ { + const newFiles = Array.from(files) + setShiAttachmentFiles(prev => [...prev, ...newFiles]) + }} + onDropRejected={() => { + toast({ + title: "File upload rejected", + description: "Please check file size and type.", + variant: "destructive", + }) + }} + > + {() => ( + +
+ +
+ 파일을 드래그하여 업로드 +
+
+
+ )} +
{shiAttachmentFiles.length > 0 && (
@@ -1197,30 +1227,35 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp 협력업체용 첨부파일

- 협력업체에서 제공하는 문서나 파일을 업로드하세요 + 협력사로 제공하는 문서나 파일을 업로드 하세요.

-
-
- -
-

- 파일을 드래그 앤 드롭하거나{' '} - -

-
-
-
+ { + const newFiles = Array.from(files) + setVendorAttachmentFiles(prev => [...prev, ...newFiles]) + }} + onDropRejected={() => { + toast({ + title: "File upload rejected", + description: "Please check file size and type.", + variant: "destructive", + }) + }} + > + {() => ( + +
+ +
+ 파일을 드래그하여 업로드 +
+
+
+ )} +
{vendorAttachmentFiles.length > 0 && (
diff --git a/components/bidding/manage/bidding-basic-info-editor.tsx b/components/bidding/manage/bidding-basic-info-editor.tsx index e92c39a5..f0d56689 100644 --- a/components/bidding/manage/bidding-basic-info-editor.tsx +++ b/components/bidding/manage/bidding-basic-info-editor.tsx @@ -45,6 +45,16 @@ import type { ProcurementManagerWithUser } from '@/components/common/selectors/p import { getBiddingDocuments, uploadBiddingDocument, deleteBiddingDocument } from '@/lib/bidding/detail/service' import { downloadFile } from '@/lib/file-download' +// Dropzone components +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone" + // 입찰 기본 정보 에디터 컴포넌트 interface BiddingBasicInfo { title?: string @@ -78,6 +88,7 @@ interface BiddingBasicInfo { interface BiddingBasicInfoEditorProps { biddingId: number + readonly?: boolean } interface UploadedDocument { @@ -95,7 +106,7 @@ interface UploadedDocument { uploadedBy: string } -export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProps) { +export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingBasicInfoEditorProps) { const [isLoading, setIsLoading] = React.useState(true) const [isSubmitting, setIsSubmitting] = React.useState(false) const [isLoadingTemplate, setIsLoadingTemplate] = React.useState(false) @@ -535,7 +546,7 @@ export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProp 입찰명 * - + @@ -883,7 +894,7 @@ export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProp 입찰개요 -