From 347fd44ab12c14978addaf37bd12cf58b917a75c Mon Sep 17 00:00:00 2001 From: leedj312 Date: Sun, 22 Jun 2025 16:41:42 +0900 Subject: [PATCH] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=EB=A7=A4=EC=B9=AD?= =?UTF-8?q?=20=EA=B3=84=EC=82=B0=EC=8B=9D=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wr/app/matMng/coMat/ruseRvwHstry/page.tsx | 2 - .../wr/app/store/matDmd/coMatRvw/headList.tsx | 18 +- apps/wr/app/store/matDmd/coMatRvw/type.tsx | 2 + .../lib/func/calculateConstCost .tsx | 80 ++++++ apps/wr/components/lib/func/commom.tsx | 3 + .../ui/form/coMat/conCostForm/conCostForm.tsx | 243 +++++++++++++----- .../ui/modal/coMat/constCostFeeTable.tsx | 10 +- 7 files changed, 271 insertions(+), 87 deletions(-) create mode 100644 apps/wr/components/lib/func/calculateConstCost .tsx diff --git a/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx b/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx index 7a5a59a93..4d0d4ea54 100644 --- a/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx @@ -94,7 +94,6 @@ export default function RuseRvwHstry() { } else { - console.log("시도코드", sggCd); ruseRvwInqua(pageNation, 13, String(startYear), String(endYear), sggCd); } }; @@ -193,7 +192,6 @@ export default function RuseRvwHstry() { } }, [rvwHistRes]); - useEffect(() => {}, [form.formState]); useEffect(() => { console.log("변경여부 ", form.formState.isDirty); diff --git a/apps/wr/app/store/matDmd/coMatRvw/headList.tsx b/apps/wr/app/store/matDmd/coMatRvw/headList.tsx index b13796950..7b43e4b00 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/headList.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/headList.tsx @@ -187,13 +187,13 @@ export const rateFeeHeadListExport: CommonTableProps[] = [ ]; export const usageProcData = [ - { usage: "청소", process: "총인처리시설 + 염소소독" }, - { usage: "화장실용수", process: "총인처리시설 + 염소소독" }, - { usage: "세척", process: "총인처리시설 + UV소독" }, - { usage: "살수", process: "총인처리시설 + UV소독" }, - { usage: "조경", process: "총인처리시설 + UV소독" }, - { usage: "농업", process: "총인처리시설 + UV소독" }, - { usage: "하천", process: "총인처리시설 + UV소독" }, - { usage: "지하수", process: "전처리시설 + RO" }, - { usage: "공업", process: "전처리시설 + RO" }, + { usage: "청소", process: "총인처리시설+염소소독" }, + { usage: "화장실용수", process: "총인처리시설+염소소독" }, + { usage: "세척", process: "총인처리시설+UV소독" }, + { usage: "살수", process: "총인처리시설+UV소독" }, + { usage: "조경", process: "총인처리시설+UV소독" }, + { usage: "농업", process: "총인처리시설+UV소독" }, + { usage: "하천", process: "총인처리시설+UV소독" }, + { usage: "지하수", process: "전처리시설+RO" }, + { usage: "공업", process: "전처리시설+RO" }, ]; diff --git a/apps/wr/app/store/matDmd/coMatRvw/type.tsx b/apps/wr/app/store/matDmd/coMatRvw/type.tsx index b4990c7eb..90c2790d5 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/type.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/type.tsx @@ -93,6 +93,8 @@ import { z } from "zod"; demandMatching: DemandMatchingSchema.nullish(), demandCst: DemandCstSchema, ruseUtztnFcltNm:z.string().nullish(), + totalConstCost:z.number().nullish(), + }); diff --git a/apps/wr/components/lib/func/calculateConstCost .tsx b/apps/wr/components/lib/func/calculateConstCost .tsx new file mode 100644 index 000000000..73cd506b4 --- /dev/null +++ b/apps/wr/components/lib/func/calculateConstCost .tsx @@ -0,0 +1,80 @@ +export const findBounds = ( + data: any[], + fctCd: string, + qty: number +): { lower?: any; upper?: any } => { + const filtered = data.filter((item) => item.fctCd === fctCd); + + const lowers = filtered.filter((item) => Number(item.fctCpc) <= qty); + const uppers = filtered.filter((item) => Number(item.fctCpc) > qty); + + const lower = + lowers.length > 0 + ? lowers.reduce((acc, curr) => + Number(curr.fctCpc) > Number(acc.fctCpc) ? curr : acc + ) + : undefined; + const upper = + uppers.length > 0 + ? uppers.reduce((acc, curr) => + Number(curr.fctCpc) < Number(acc.fctCpc) ? curr : acc + ) + : undefined; + + return { lower, upper }; +}; + +// (기울기) a = (y2 - y1) / (x2 - x1) +// (절편) b = y1 - a * x1 +// ↓ +// 결과값 y = a * x + b + +export const getInterpolation = ( + Upper: { stdCost: number; fctCpc: number }, + Lower: { stdCost: number; fctCpc: number }, + qtyYear: number +): number => { + const slope = + (Number(Upper?.stdCost) - Number(Lower?.stdCost)) / + (Number(Upper?.fctCpc) - Number(Lower?.fctCpc)); + const intercept = Number(Lower?.stdCost) - slope * Number(Lower?.fctCpc); + return slope * qtyYear + intercept; +}; + +export const transformAndInterpolate = ( + rawData: any, + dmndQtyYear: number +): any => { + const result: any = {}; + + for (const key in rawData) { + const item = rawData[key]; + + const range = { + Lower: { + fctCpc: item.lower?.stdCost, + stdCost: item.lower?.rt, + }, + Upper: { + fctCpc: item.upper?.stdCost, + stdCost: item.upper?.rt, + }, + }; + console.log("range ", range); + result[key] = getInterpolation(range.Upper, range.Lower, dmndQtyYear); + } + + return result; +}; + +export const getConstFctCode = (fctCd: string) => { + const fctMap: { [key: string]: string } = { + 총인처리시설: "001", + 염소소독: "002", + UV소독: "003", + 전처리시설: "004", + RO: "005", + }; + + return fctMap[fctCd]; +}; diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index ba0f5c588..85e1a36b0 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -153,3 +153,6 @@ export const getdmdMatFlag = (flag: string|undefined) => { } return flagMat; }; + + + diff --git a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx index 21746c099..f62e8b2a2 100644 --- a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx +++ b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx @@ -6,6 +6,12 @@ import type { } from "@/app/store/matDmd/coMatRvw/type"; import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { formH1Class } from "@/components/lib/class/comClass"; +import { + findBounds, + getConstFctCode, + getInterpolation, + transformAndInterpolate, +} from "@/components/lib/func/calculateConstCost "; import { ConstCostFeeTable } from "@/components/ui/modal/coMat/constCostFeeTable"; import { RateFeeTableModal } from "@/components/ui/modal/coMat/rateFeeModal"; import { @@ -17,7 +23,8 @@ import { } from "@common/business_components/ui"; import PopoverUi from "@common/business_components/ui/popover_ui"; import { Button, Input, Label } from "@common/components/ui"; -import { useEffect, useRef, useState } from "react"; +import type { read } from "fs"; +import { useCallback, useEffect, useRef, useState } from "react"; import { type UseFormReturn } from "react-hook-form"; export default function ConCostForm({ @@ -27,7 +34,14 @@ export default function ConCostForm({ form: UseFormReturn; type: string; }) { - const { coMatRvwUpdate, ruseRvwCalUpdate,getRateFee ,getConstCostFee} = useWrStore((state) => state); + const { + constCostRes, + rateFeeRes, + coMatRvwUpdate, + ruseRvwCalUpdate, + getRateFee, + getConstCostFee, + } = useWrStore((state) => state); const ref = useRef(false); @@ -38,9 +52,8 @@ export default function ConCostForm({ getRateFee(String(currentYear)); ref.current = true; } - }, [ref, getConstCostFee ,getRateFee ]); + }, [ref, getConstCostFee, getRateFee]); - const [isTrfMo, setIsTrfMo] = useState<{ isModal: boolean; }>({ isModal: false }); @@ -59,6 +72,82 @@ 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 calculateConstCost = useCallback(() => { + const usgPrcsProcs = usgPrcsProcsWatch; + const dmndQtyYear = dmndQtyWatch * 365; + if (usgPrcsProcs && usgPrcsProcs?.length > 0) { + const firstProc = + getConstFctCode(usgPrcsProcs?.split("+")[0] || "") || ""; + const secondProc = + getConstFctCode(usgPrcsProcs?.split("+")[1] || "") || ""; + + const { lower: firstLower, upper: firstUpper } = findBounds( + constCostRes, + firstProc || "", + Number(dmndQtyYear) + ); + const { lower: secondLower, upper: secondUpper } = findBounds( + constCostRes, + secondProc || "", + Number(dmndQtyYear) + ); + + const firstConstFee = getInterpolation( + firstUpper, + firstLower, + dmndQtyYear + ); + const secondConstFee = getInterpolation( + secondUpper, + secondLower, + dmndQtyYear + ); + const result = Math.round(firstConstFee + secondConstFee) / 100; + + const groupCodes = ["BD", "CE", "SE", "WD"]; + const groupedBounds: { [key: string]: { lower?: any; upper?: any } } = {}; + for (const code of groupCodes) { + const groupItems = rateFeeRes.filter((item) => item.incExpCd === code); + const lowers = groupItems.filter( + (item) => Number(item.stdCost) <= result + ); + const uppers = groupItems.filter( + (item) => Number(item.stdCost) > result + ); + + const lower = + lowers.length > 0 + ? lowers.reduce((acc, curr) => + Number(curr.stdCost) > Number(acc.stdCost) ? curr : acc + ) + : undefined; + + const upper = + uppers.length > 0 + ? uppers.reduce((acc, curr) => + Number(curr.stdCost) < Number(acc.stdCost) ? curr : acc + ) + : undefined; + + groupedBounds[code] = { lower, upper }; + } + + const resultRate = transformAndInterpolate(groupedBounds, result); + const fcltSbsdEpsTrf =result * (resultRate.BD + resultRate.CE + resultRate.SE + resultRate.WD); + + const realResult = + result +fcltSbsdEpsTrf; + + form.setValue("demandCst.prcsFcltCpctCstrnCst", result); + form.setValue( "demandCst.fcltSbsdEpsTrf",fcltSbsdEpsTrf); + form.setValue("totalConstCost", realResult); + } + }, [constCostRes, rateFeeRes, usgPrcsProcsWatch, dmndQtyWatch]); const onSubmit = (value: DmdCoReqType) => { if (type === "mat") { @@ -77,7 +166,7 @@ export default function ConCostForm({ }; useEffect(() => { - console.log(" form ", form.formState.errors); + // console.log(" form ", form.formState.errors); }, [form.formState]); return ( @@ -90,7 +179,7 @@ export default function ConCostForm({

처리시설 표준공사비 산정

-
@@ -323,75 +419,80 @@ export default function ConCostForm({ />
- ( - + ( + + { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + /> + + )} + /> +
+
+ ( +
-
- ( - - )} - /> -
-
- - -
+ + )} + /> + +
+ + +
+ {}}>} + body={ + {}}> + } + bodyClass="w-[40vw] max-w-none h-[40vh]" + open={isCmpttnMo.isModal} + onOpenChange={(open) => { + setIsCmpttnMo({ isModal: open }); + }} + /> + {}}> + } bodyClass="w-[40vw] h-[40vh] " open={isTrfMo.isModal} onOpenChange={(open) => { setIsTrfMo({ isModal: open }); }} /> - {}}>} - bodyClass="w-[40vw] max-w-none h-[40vh]" - open={isCmpttnMo.isModal} - onOpenChange={(open) => { - setIsCmpttnMo({ isModal: open }); - }} - /> ); diff --git a/apps/wr/components/ui/modal/coMat/constCostFeeTable.tsx b/apps/wr/components/ui/modal/coMat/constCostFeeTable.tsx index 6748ef487..b47047d9a 100644 --- a/apps/wr/components/ui/modal/coMat/constCostFeeTable.tsx +++ b/apps/wr/components/ui/modal/coMat/constCostFeeTable.tsx @@ -23,12 +23,12 @@ export function ConstCostFeeTable({ const [constCostData, setConstCostData] = useState([]); - function getFctDescription(fctCd: string) { + const getFctDescription = (fctCd: string) => { const fctMap: { [key: string]: string } = { - "001": "총인", - "002": "염소.소독", - "003": "uv소독", - "004": "생물학적탈질", + "001": "총인처리시설", + "002": "염소소독", + "003": "UV소독", + "004": "전처리시설", "005": "RO", };