wr: 표준공사비 모의산정

This commit is contained in:
2026-05-08 17:53:51 +09:00
parent b38dfd261b
commit edc2fd5720
3 changed files with 165 additions and 110 deletions
@@ -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<DmdCoReqType>({
defaultValues: {
dmndQty: 0,
demandCst: { cstrnCst: 0, usgPrcsProcs: "", prcsFcltCpctCstrnCst: 0, fcltSbsdCstrnCst: 0 },
totalConstCost: 0
} as any
});
return <ConCostForm form={form} type="simulation" readonly={false} />;
}
@@ -74,9 +74,7 @@ export default function CoMatRvw() {
const [toDate, setToDate] = useState<string>(
String(new Date().getFullYear())
);
const [selectData, setSelectData] = useState<DmdCoReqType>(
{} as DmdCoReqType
);
const [selectData, setSelectData] = useState<DmdCoReqType | null>(null); // selectData를 null로 초기화
const [selectionVersion, setSelectionVersion] = useState(0);
const bottomRef = useRef<HTMLDivElement | null>(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 (
<>
<ContentsDiv>
@@ -290,6 +301,9 @@ export default function CoMatRvw() {
>
조회
</Button>
<Button className={btnClass} onClick={()=>constCostSimulation()} >
표준공사비 모의산정
</Button>
</div>
</div>
</SearchFormRight>
@@ -773,5 +787,3 @@ export default function CoMatRvw() {
</>
);
}
@@ -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<DmdCoReqType>;
form?: UseFormReturn<DmdCoReqType>;
type: string;
selectionVersion: number;
readonly: boolean;
selectionVersion?: number;
readonly?: boolean;
}) {
// 시뮬레이션 모드를 위한 로컬 폼 생성
const localForm = useForm<DmdCoReqType>({
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 (
<>
<Form {...form}>
<Form {...activeForm}>
{/* <h3 className="mt-4 ml-2 text-2xl font-medium text-label">
표준공사비 산정
</h3> */}
@@ -334,36 +357,35 @@ export default function ConCostForm({
<Button className="mx-3" size={"sm"} onClick={calculateConstCost}>
계산
</Button>
<Button
className="mr-3"
size={"sm"}
onClick={form.handleSubmit(onSubmit)}
disabled={readonly}
>
저장
</Button>
<Label>검토완료</Label>
<Checkbox
className="ml-3 mr-6"
checked={checkFinish}
onCheckedChange={
(checked) => {
if (readonly) return;
setCheckFinish(checked === true);
}
// 'indeterminate' 상태를 무시하고 순수 boolean 값으로만 상태를 업데이트합니다.
}
></Checkbox>
<Label>관로 표준공사비 산정 프로그램</Label>
<Button
color="green"
size={"sm"}
onClick={zipDowonLoad}
className="bg-none ml-6"
>
다운로드
</Button>
{!isSim && (
<>
<Button
className="mr-3"
size={"sm"}
onClick={activeForm.handleSubmit(onSubmit)}
disabled={readonly}
>
저장
</Button>
<Label>검토완료</Label>
<Checkbox
className="ml-3 mr-6"
checked={checkFinish}
onCheckedChange={(checked) => {
if (readonly) return;
setCheckFinish(checked === true);
}}
/>
</>
)}
{!isSim && (
<>
<Label>관로 표준공사비 산정 프로그램</Label>
<Button color="green" size={"sm"} onClick={zipDowonLoad} className="bg-none ml-6">
다운로드
</Button>
</>
)}
</div>
<div className="col-span-12 ml-4 mr-4">
<h1 className={formH1Class}>기본사항</h1>
@@ -371,25 +393,33 @@ export default function ConCostForm({
<div className="grid grid-cols-12 gap-4 ml-4 mr-4">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
control={activeForm.control}
name="useUsgCd"
render={({ field }) => (
<FormUi label="용도">
<Input
placeholder=""
type="text"
onChange={field.onChange}
value={useUsgCdLabel}
readOnly
className={readOnlyInputClass}
/>
{isSim ? (
<SelectBox
label=""
selectArray={code.sewageOutFclttUse || []}
onSelectValue={field.onChange}
value={field.value}
/>
) : (
<Input
placeholder=""
type="text"
value={useUsgCdLabel}
readOnly
className={readOnlyInputClass}
/>
)}
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<FormField
control={form.control}
control={activeForm.control}
name="dmndQty"
render={({ field }) => (
<FormUi label="요구수량(m³/일)">
@@ -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 : ""}
/>
</FormUi>
)}
@@ -439,7 +469,7 @@ export default function ConCostForm({
bodyClassName={"w-[25vw] max-w-none"}
></PopoverUi>
<FormField
control={form.control}
control={activeForm.control}
name="demandCst.usgPrcsProcs"
render={({ field }) => (
<FormUi label="">
@@ -457,7 +487,7 @@ export default function ConCostForm({
</div>
<div className="col-span-3 mt-4">
<FormField
control={form.control}
control={activeForm.control}
name="dmndQty"
render={({ field }) => (
<FormUi label="처리시설용량(m³/일)">
@@ -502,7 +532,7 @@ export default function ConCostForm({
></PopoverUi>
</div>
<FormField
control={form.control}
control={activeForm.control}
name="demandCst.prcsFcltCpctCstrnCst"
render={({ field }) => (
<FormUi label="">
@@ -540,7 +570,7 @@ export default function ConCostForm({
></PopoverUi>
</div>
<FormField
control={form.control}
control={activeForm.control}
name="demandCst.fcltSbsdCstrnCst"
render={({ field }) => (
<FormUi label="">
@@ -585,7 +615,7 @@ export default function ConCostForm({
bodyClassName={"w-[22vw]"}
></PopoverUi>
<FormField
control={form.control}
control={activeForm.control}
name="totalConstCost"
render={({ field }) => (
<FormUi label="">
@@ -604,35 +634,28 @@ export default function ConCostForm({
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>공급처 선정</Label>
<Button
size={"sm"}
onClick={(e) => {
searchCandidate(e);
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
}}
className="bg-none"
>
후보지 조회
</Button>
{form && (
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>공급처 선정</Label>
<Button size={"sm"} onClick={searchCandidate} className="bg-none">
후보지 조회
</Button>
</div>
<FormUi label="">
<Input
placeholder=""
type="text"
value={lastSub?.ruseUtztnFcltNm || ""}
readOnly
className={readOnlyInputClass}
/>
</FormUi>
</div>
<FormUi label="">
<Input
placeholder=""
type="text"
value={lastSub?.ruseUtztnFcltNm || ""}
readOnly
className={readOnlyInputClass}
/>
</FormUi>
</div>
)}
<div className="col-span-3 mt-4">
<FormField
control={form.control}
control={activeForm.control}
name="demandCst.cstrnCst"
render={({ field }) => (
<FormUi label="공급관로 표준공사비(백만원)">
@@ -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));
}}