From cd0ce0cbe8af8719a6f542098ec78f2a5c1222ce Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 1 Dec 2025 10:28:05 +0000 Subject: (최겸) 구매 입찰 사전견적 개발(rfq-last) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ProjectSelector.tsx | 2 +- components/bidding/ProjectSelectorBid.tsx | 2 +- .../bidding/manage/create-pre-quote-rfq-dialog.tsx | 421 +++++++++------------ 3 files changed, 186 insertions(+), 239 deletions(-) (limited to 'components') diff --git a/components/ProjectSelector.tsx b/components/ProjectSelector.tsx index 58fa2c23..6b16668b 100644 --- a/components/ProjectSelector.tsx +++ b/components/ProjectSelector.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button" import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command" import { cn } from "@/lib/utils" -import { getProjects, type Project } from "@/lib/rfqs/service" +import { getProjects, type Project } from "@/lib/rfq-last/service" interface ProjectSelectorProps { selectedProjectId?: number | null; diff --git a/components/bidding/ProjectSelectorBid.tsx b/components/bidding/ProjectSelectorBid.tsx index a87c8dce..f0167e82 100644 --- a/components/bidding/ProjectSelectorBid.tsx +++ b/components/bidding/ProjectSelectorBid.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button" import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command" import { cn } from "@/lib/utils" -import { getProjects, type Project } from "@/lib/rfqs/service" +import { getProjects, type Project } from "@/lib/rfq-last/service" interface ProjectSelectorProps { selectedProjectId?: number | null; diff --git a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx index cdcf1ef1..de3c19ff 100644 --- a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx +++ b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx @@ -46,12 +46,14 @@ import { cn } from "@/lib/utils" import { toast } from "sonner" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" -import { createPreQuoteRfqAction, previewGeneralRfqCode } from "@/lib/bidding/service" +import { createPreQuoteRfqAction } from "@/lib/bidding/pre-quote/service" +import { previewGeneralRfqCode } from "@/lib/rfq-last/service" import { MaterialGroupSelectorDialogSingle } from "@/components/common/material/material-group-selector-dialog-single" import { MaterialSearchItem } from "@/lib/material/material-group-service" import { MaterialSelectorDialogSingle } from "@/components/common/selectors/material/material-selector-dialog-single" import { MaterialSearchItem as SAPMaterialSearchItem } from "@/components/common/selectors/material/material-service" import { PurchaseGroupCodeSelector } from "@/components/common/selectors/purchase-group-code/purchase-group-code-selector" +import type { PurchaseGroupCodeWithUser } from "@/components/common/selectors/purchase-group-code" import { getBiddingById } from "@/lib/bidding/service" // 아이템 스키마 @@ -67,14 +69,17 @@ const itemSchema = z.object({ // 사전견적용 일반견적 생성 폼 스키마 const createPreQuoteRfqSchema = z.object({ - rfqType: z.string().min(1, "견적 종류를 선택해주세요"), + rfqType: z.string().optional(), rfqTitle: z.string().min(1, "견적명을 입력해주세요"), dueDate: z.date({ required_error: "제출마감일을 선택해주세요", - }), + }).optional(), // 필수값 해제 picUserId: z.number().optional(), projectId: z.number().optional(), remark: z.string().optional(), + biddingNumber: z.string().optional(), // 입찰 No. 추가 + contractStartDate: z.date().optional(), // 계약기간 시작 + contractEndDate: z.date().optional(), // 계약기간 종료 items: z.array(itemSchema).min(1, "최소 하나의 자재를 추가해주세요"), }) @@ -122,7 +127,7 @@ export function CreatePreQuoteRfqDialog({ const [isLoading, setIsLoading] = React.useState(false) const [previewCode, setPreviewCode] = React.useState("") const [isLoadingPreview, setIsLoadingPreview] = React.useState(false) - const [selectedBidPic, setSelectedBidPic] = React.useState(undefined) + const [selectedBidPic, setSelectedBidPic] = React.useState(undefined) const { data: session } = useSession() const userId = React.useMemo(() => { @@ -165,24 +170,31 @@ export function CreatePreQuoteRfqDialog({ }, }) + /* const { fields, append, remove } = useFieldArray({ control: form.control, name: "items", }) + */ - // 입찰담당자 정보 로드 + // 견적담당자 정보 로드 React.useEffect(() => { const loadBiddingInfo = async () => { if (!biddingId || !open) return try { const bidding = await getBiddingById(biddingId) - if (bidding && bidding.bidPicId) { - // 입찰담당자 정보를 로드하는 로직 추가 필요 - // 현재는 임시로 bidPicId를 사용 + if (bidding) { setSelectedBidPic({ - USER_ID: bidding.bidPicId, - DISPLAY_NAME: bidding.bidPicName || '입찰담당자' + DISPLAY_NAME: bidding.bidPicName || '', + PURCHASE_GROUP_CODE: bidding.bidPicCode || '', + EMPLOYEE_NUMBER: '', + user: bidding.bidPicId ? { + id: bidding.bidPicId, + name: bidding.bidPicName || '', + email: '', + employeeNumber: null + } : undefined }) } } catch (error) { @@ -192,17 +204,58 @@ export function CreatePreQuoteRfqDialog({ loadBiddingInfo() }, [biddingId, open]) + + // 프로젝트 정보 상태 추가 + const [projectInfo, setProjectInfo] = React.useState("") // 다이얼로그가 열릴 때 폼 초기화 React.useEffect(() => { if (open) { + // 입찰 정보를 기반으로 기본값 설정 + let rfqTitle = ""; + let projectId: number | undefined = undefined; + let contractStartDate: Date | undefined = undefined; + let contractEndDate: Date | undefined = undefined; + let biddingNumber = ""; + + const loadDetailedBiddingInfo = async () => { + if (biddingId) { + try { + const bidding = await getBiddingById(biddingId) + if (bidding) { + rfqTitle = bidding.title; + biddingNumber = bidding.biddingNumber; + + // 프로젝트 정보 설정 + const pCode = bidding.projectCode || ""; + const pName = bidding.projectName || ""; + setProjectInfo(pCode && pName ? `${pCode} - ${pName}` : pCode || pName || ""); + + // 폼 값 설정 + form.setValue("rfqTitle", rfqTitle); + form.setValue("rfqType", "pre_bidding"); // 기본값 설정 + if (biddingNumber) form.setValue("biddingNumber", biddingNumber); + + if (bidding.contractStartDate) form.setValue("contractStartDate", new Date(bidding.contractStartDate)); + if (bidding.contractEndDate) form.setValue("contractEndDate", new Date(bidding.contractEndDate)); + } + } catch (e) { + console.error(e); + } + } + }; + loadDetailedBiddingInfo(); + form.reset({ - rfqType: "", + rfqType: "pre_bidding", // 기본값 rfqTitle: "", - dueDate: undefined, - picUserId: selectedBidPic?.USER_ID, + dueDate: undefined, // 필수값 해제되었으므로 undefined 가능 + picUserId: selectedBidPic?.user?.id, projectId: undefined, remark: "", + biddingNumber: "", + contractStartDate: undefined, + contractEndDate: undefined, items: initialItems.length > 0 ? initialItems : [ { itemCode: "", @@ -217,11 +270,11 @@ export function CreatePreQuoteRfqDialog({ }) setPreviewCode("") } - }, [open, initialItems, form, selectedBidPic]) + }, [open, initialItems, form, selectedBidPic, biddingId]) // 견적담당자 선택 시 RFQ 코드 미리보기 생성 React.useEffect(() => { - if (!selectedBidPic?.USER_ID) { + if (!selectedBidPic?.user?.id) { setPreviewCode("") return } @@ -230,7 +283,7 @@ export function CreatePreQuoteRfqDialog({ (async () => { setIsLoadingPreview(true) try { - const code = await previewGeneralRfqCode(selectedBidPic.USER_ID) + const code = await previewGeneralRfqCode(selectedBidPic.user!.id) setPreviewCode(code) } catch (error) { console.error("코드 미리보기 오류:", error) @@ -277,25 +330,26 @@ export function CreatePreQuoteRfqDialog({ return } - if (!selectedBidPic?.USER_ID) { - toast.error("입찰담당자를 선택해주세요") - return - } - const picUserId = selectedBidPic.USER_ID + + const picUserId = selectedBidPic?.user?.id || session?.user?.id setIsLoading(true) try { // 서버 액션 호출 (입찰 조건 포함) const result = await createPreQuoteRfqAction({ + // biddingId, // createPreQuoteRfqAction 인터페이스 변경됨 biddingId, - rfqType: data.rfqType, + rfqType: data.rfqType || "pre_bidding", rfqTitle: data.rfqTitle, - dueDate: data.dueDate, + dueDate: data.dueDate ? new Date(data.dueDate) : undefined, // optional이지만 submit시에는 값이 있을 수 있음 (없으면 서비스에서 처리) picUserId, projectId: data.projectId, remark: data.remark || "", + biddingNumber: data.biddingNumber, // 추가 + contractStartDate: data.contractStartDate, // 추가 + contractEndDate: data.contractEndDate, // 추가 items: data.items as Array<{ itemCode: string; itemName: string; @@ -336,7 +390,8 @@ export function CreatePreQuoteRfqDialog({ } } - // 아이템 추가 + // 아이템 추가 (사용안함) + /* const handleAddItem = () => { append({ itemCode: "", @@ -348,6 +403,7 @@ export function CreatePreQuoteRfqDialog({ remark: "", }) } + */ return ( @@ -371,7 +427,7 @@ export function CreatePreQuoteRfqDialog({
{/* 견적 종류 */} -
+ {/*
견적 종류 * - + + + )} /> -
+
*/} {/* 제출마감일 */} ( - 제출마감일 * + 제출마감일 @@ -420,7 +467,7 @@ export function CreatePreQuoteRfqDialog({ {field.value ? ( format(field.value, "yyyy-MM-dd") ) : ( - 제출마감일을 선택하세요 + 제출마감일을 선택하세요 (선택) )} @@ -468,23 +515,24 @@ export function CreatePreQuoteRfqDialog({ /> {/* 프로젝트 선택 */} - ( +
프로젝트 - {/* ProjectSelector는 별도 컴포넌트 필요 */} field.onChange(e.target.value ? Number(e.target.value) : undefined)} + value={projectInfo} + readOnly + className="bg-muted" + placeholder="프로젝트 정보 없음" /> - +
+ ( + )} /> @@ -502,7 +550,7 @@ export function CreatePreQuoteRfqDialog({ selectedCode={selectedBidPic} onCodeSelect={(code) => { setSelectedBidPic(code) - field.onChange(code.USER_ID) + field.onChange(code.user?.id) }} placeholder="입찰담당자 선택" /> @@ -526,6 +574,87 @@ export function CreatePreQuoteRfqDialog({
)} + {/* 계약기간 */} +
+ ( + + 계약기간 시작 + + + + + + + + + + + + + )} + /> + + ( + + 계약기간 종료 + + + + + + + + + + + + + )} + /> +
+ {/* 비고 */} - {/* 아이템 정보 섹션 */} -
-
-

자재 정보

- -
- -
- {fields.map((field, index) => ( -
-
- - 자재 #{index + 1} - - {fields.length > 1 && ( - - )} -
- - {/* 자재그룹 선택 */} -
- - 자재그룹(자재그룹명) * - -
- { - const itemCode = form.watch(`items.${index}.itemCode`); - const itemName = form.watch(`items.${index}.itemName`); - if (itemCode && itemName) { - return { - materialGroupCode: itemCode, - materialGroupDescription: itemName, - displayText: `${itemCode} - ${itemName}` - } as MaterialSearchItem; - } - return null; - })()} - onMaterialSelect={(material) => { - form.setValue(`items.${index}.itemCode`, material?.materialGroupCode || ''); - form.setValue(`items.${index}.itemName`, material?.materialGroupDescription || ''); - }} - placeholder="자재그룹을 검색하세요..." - title="자재그룹 선택" - description="원하는 자재그룹을 검색하고 선택해주세요." - triggerVariant="outline" - /> -
-
- - {/* 자재코드 선택 */} -
- - 자재코드(자재명) - -
- { - const materialCode = form.watch(`items.${index}.materialCode`); - const materialName = form.watch(`items.${index}.materialName`); - if (materialCode && materialName) { - return { - materialCode: materialCode, - materialName: materialName, - displayText: `${materialCode} - ${materialName}` - } as SAPMaterialSearchItem; - } - return null; - })()} - onMaterialSelect={(material) => { - form.setValue(`items.${index}.materialCode`, material?.materialCode || ''); - form.setValue(`items.${index}.materialName`, material?.materialName || ''); - }} - placeholder="자재코드를 검색하세요..." - title="자재코드 선택" - description="원하는 자재코드를 검색하고 선택해주세요." - triggerVariant="outline" - /> -
-
- -
- {/* 수량 */} - ( - - - 수량 * - - - field.onChange(Number(e.target.value))} - /> - - - - )} - /> - - {/* 단위 */} - ( - - - 단위 * - - - - - )} - /> -
- - {/* 비고 */} -
- ( - - 비고 - - - - - - )} - /> -
-
- ))} -
-
+ {/* 아이템 정보 섹션 (자동 매핑되므로 UI 제거) */} + {/*
+ ... +
*/} -- cgit v1.2.3