diff --git a/apps/wr/app/(popUp-layout)/constCostSimulation/page.tsx b/apps/wr/app/(popUp-layout)/constCostSimulation/page.tsx new file mode 100644 index 000000000..870f54b1f --- /dev/null +++ b/apps/wr/app/(popUp-layout)/constCostSimulation/page.tsx @@ -0,0 +1,20 @@ +"use client"; + +/** + * 표준공사비 시뮬레이션 Popup + */ +import { useForm } from "react-hook-form"; +import ConCostForm from "@/components/ui/form/coMat/conCostForm/conCostForm"; +import type { DmdCoReqType } from "@/app/store/matDmd/coMatRvw/type"; + +export default function ConstCostSimulationPage() { + const form = useForm({ + defaultValues: { + dmndQty: 0, + demandCst: { cstrnCst: 0, usgPrcsProcs: "", prcsFcltCpctCstrnCst: 0, fcltSbsdCstrnCst: 0 }, + totalConstCost: 0 + } as any + }); + + return ; +} diff --git a/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx b/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx index 4304945bd..e2e80f745 100644 --- a/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx @@ -74,9 +74,7 @@ export default function CoMatRvw() { const [toDate, setToDate] = useState( String(new Date().getFullYear()) ); - const [selectData, setSelectData] = useState( - {} as DmdCoReqType - ); + const [selectData, setSelectData] = useState(null); // selectData를 null로 초기화 const [selectionVersion, setSelectionVersion] = useState(0); const bottomRef = useRef(null); @@ -122,7 +120,7 @@ export default function CoMatRvw() { if (coMatRvwRes && coMatRvwRes[index]) { setSelectData(coMatRvwRes[index]); setSelectionVersion((v) => v + 1); // 행을 클릭할 때마다 버전을 1씩 증가 - bottomRef.current?.scrollIntoView({ behavior: "smooth" }); // 화면 스크로 처리 + bottomRef.current?.scrollIntoView({ behavior: "smooth" }); // 화면 스크롤 처리 } }; const useUsgCdWatch = useWatch({ control: form.control, name: "useUsgCd" }); @@ -131,7 +129,6 @@ export default function CoMatRvw() { String(useUsgCdWatch ?? ""); useEffect(() => { if (useUsgCdWatch) { - console.log("useUsgCdWatch:" , useUsgCdWatch); const normalizedUseUsgCd = code.sewageOutFclttUse?.find((item) => item.val === useUsgCdWatch)?.val ?? code.sewageOutFclttUse?.find((item) => item.text === useUsgCdWatch)?.val ?? @@ -205,6 +202,20 @@ export default function CoMatRvw() { setSelectionVersion((v) => v + 1); // 강제로 바뀌었다고 알리기 } }, [selectData, form]); + + /** + * 하수 표준공사비 시뮬레이션(모의산정) + */ + const constCostSimulation = () => { + const url = `/constCostSimulation`; + const width = window.screen.availWidth; + const height = 600; + const left = 0; + const top = 0; + const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`; + window.open(url, "candidatePopup", popupOptions); + }; + return ( <> @@ -290,6 +301,9 @@ export default function CoMatRvw() { > 조회 + @@ -773,5 +787,3 @@ export default function CoMatRvw() { ); } - - diff --git a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx index 932382709..40dd1c215 100644 --- a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx +++ b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx @@ -20,20 +20,21 @@ import { Form, FormField, FormUi, + SelectBox, UiTable, } from "@common/business_components/ui"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import PopoverUi from "@common/business_components/ui/popover_ui"; import { Button, Checkbox, Input, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; -import { type UseFormReturn } from "react-hook-form"; +import { useForm, type UseFormReturn } from "react-hook-form"; /** - * ConCostForm - * 호출: (with-layout)/matMng/coMat/coMatRvw 에서 호출 + * ConCostForm + * 호출: (with-layout)/matMng/coMat/coMatRvw * - * 공사비를 산정하고 - * - * */ + * @param form - 외부에서 전달된 폼 객체 (없을 경우 시뮬레이션용 로컬 폼 사용) + * @param type - "mat": 매칭, "hist": 이력, "simulation": 모의산정 + */ export default function ConCostForm({ form, @@ -41,11 +42,22 @@ export default function ConCostForm({ selectionVersion, readonly = false, }: { - form: UseFormReturn; + form?: UseFormReturn; type: string; - selectionVersion: number; - readonly: boolean; + selectionVersion?: number; + readonly?: boolean; }) { + // 시뮬레이션 모드를 위한 로컬 폼 생성 + const localForm = useForm({ + defaultValues: { + dmndQty: 0, + demandCst: { cstrnCst: 0, usgPrcsProcs: "" } + } as any + }); + + const activeForm = form || localForm; + const isSim = type === "simulation"; + const readOnlyInputClass = "bg-slate-50 text-gray-700"; const { constCostRes, // 표준사업비 @@ -105,23 +117,33 @@ export default function ConCostForm({ link.click(); }); }; - const usgPrcsProcsWatch = form.watch("demandCst.usgPrcsProcs"); - const dmndQtyWatch = form.watch("dmndQty"); - const totalConstCost = form.watch("totalConstCost"); - const cstrnCst = form.watch("demandCst.cstrnCst"); - const splyList = form.watch("splyCndtList"); - const useUsgCdWatch = form.watch("useUsgCd"); + const usgPrcsProcsWatch = activeForm.watch("demandCst.usgPrcsProcs"); + const dmndQtyWatch = activeForm.watch("dmndQty"); + const totalConstCost = activeForm.watch("totalConstCost"); + const cstrnCst = activeForm.watch("demandCst.cstrnCst") || 0; + const splyList = activeForm.watch("splyCndtList"); + const useUsgCdWatch = activeForm.watch("useUsgCd"); const useUsgCdLabel = code.sewageOutFclttUse?.find((item) => item.val === useUsgCdWatch)?.text ?? String(useUsgCdWatch ?? ""); + // 용도 변경 시 처리공정 자동 매핑 (시뮬레이션 대응) + useEffect(() => { + if (useUsgCdWatch) { + const matched = usageProcData.find((item) => item.useUsgCd === useUsgCdWatch); + if (matched && matched.process !== usgPrcsProcsWatch) { + activeForm.setValue("demandCst.usgPrcsProcs", matched.process); + } + } + }, [useUsgCdWatch, activeForm, usgPrcsProcsWatch]); + /** * 표준공산비 산정 : "계산"버튼 click시 */ const calculateConstCost = useCallback(() => { const usgPrcsProcs = usgPrcsProcsWatch; - if (selectionVersion == 0) { - alert("먼저 목록에서 수요신청 건을 선택하세요."); + if (!isSim && selectionVersion == 0) { + alert("목록에서 수요신청 건을 선택하세요."); return; } if (!dmndQtyWatch) { @@ -194,12 +216,12 @@ export default function ConCostForm({ 100; const realResult = result + fcltSbsdCstrnCst; - form.setValue("demandCst.prcsFcltCpctCstrnCst", Math.round(result)); - form.setValue("demandCst.fcltSbsdCstrnCst", Math.round(fcltSbsdCstrnCst)); - form.setValue("totalConstCost", Math.round(realResult)); + activeForm.setValue("demandCst.prcsFcltCpctCstrnCst", Math.round(result)); + activeForm.setValue("demandCst.fcltSbsdCstrnCst", Math.round(fcltSbsdCstrnCst)); + activeForm.setValue("totalConstCost", Math.round(realResult)); alert("표준공산비 산정이 완료되었습니다."); } - }, [constCostRes, rateFeeRes, usgPrcsProcsWatch, dmndQtyWatch]); + }, [constCostRes, rateFeeRes, usgPrcsProcsWatch, dmndQtyWatch, isSim, selectionVersion, activeForm]); /** * 저장 버튼 click 시 실행 @@ -235,11 +257,11 @@ export default function ConCostForm({ if (checkFinish) { if (await confirm("검토완료 변경시 공사비는 변경할수 없습니다.\n진행하시겠습니까?")) { - const message = await coMatRvwUpdate( pk, form.getValues("dmdInfoId"), data ); + const message = await coMatRvwUpdate( pk, activeForm.getValues("dmdInfoId"), data ); alert(message); } } else { - const message = await coMatRvwUpdate( pk, form.getValues("dmdInfoId"), data ); + const message = await coMatRvwUpdate( pk, activeForm.getValues("dmdInfoId"), data ); alert(message); } }else{ @@ -292,10 +314,11 @@ export default function ConCostForm({ useEffect(() => { - form.register("dmdInfoSn"); - form.register("dmdInfoId"); - form.register("ruseUtztnFcltNm"); - }, []); + if (!activeForm) return; + activeForm.register("dmdInfoSn"); + activeForm.register("dmdInfoId"); + activeForm.register("ruseUtztnFcltNm"); + }, [activeForm]); /** @@ -309,7 +332,7 @@ export default function ConCostForm({ alert("먼저 목록에서 수요신청 건을 선택하세요."); return; } - const dmdInfoId = form.getValues("dmdInfoId"); + const dmdInfoId = activeForm.getValues("dmdInfoId"); console.log(dmdInfoId) const url = `/splyPopup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`; @@ -324,7 +347,7 @@ export default function ConCostForm({ return ( <> -
+ {/*

표준공사비 산정

*/} @@ -334,36 +357,35 @@ export default function ConCostForm({ - - - { - if (readonly) return; - setCheckFinish(checked === true); - } - - // 'indeterminate' 상태를 무시하고 순수 boolean 값으로만 상태를 업데이트합니다. - } - > - - + {!isSim && ( + <> + + + { + if (readonly) return; + setCheckFinish(checked === true); + }} + /> + + )} + {!isSim && ( + <> + + + + )}

기본사항

@@ -371,25 +393,33 @@ export default function ConCostForm({
( - + {isSim ? ( + + ) : ( + + )} )} />
( @@ -401,8 +431,8 @@ export default function ConCostForm({ field.onChange(Number(e.target.value)); }} value={field.value ?? 0} - readOnly={true} - className={readOnlyInputClass} + readOnly={!isSim} + className={!isSim ? readOnlyInputClass : ""} /> )} @@ -439,7 +469,7 @@ export default function ConCostForm({ bodyClassName={"w-[25vw] max-w-none"} > ( @@ -457,7 +487,7 @@ export default function ConCostForm({
( @@ -502,7 +532,7 @@ export default function ConCostForm({ >
( @@ -540,7 +570,7 @@ export default function ConCostForm({ >
( @@ -585,7 +615,7 @@ export default function ConCostForm({ bodyClassName={"w-[22vw]"} > ( @@ -604,35 +634,28 @@ export default function ConCostForm({ )} />
-
-
- - + {form && ( +
+
+ + +
+ + +
- - - -
+ )}
( @@ -641,7 +664,7 @@ export default function ConCostForm({ type="number" step="0.01" onChange={(e) => { - if (readonly) return; + if (readonly && !isSim) return; field.onChange(Number(e.target.value)); }}