wr: 수요처매칭 용도 수정

This commit is contained in:
2026-03-26 18:16:17 +09:00
parent 41504e6e0f
commit ce0e6e9d99
10 changed files with 79 additions and 39 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1
#NEXT_PUBLIC_WR_SERVER_API_URL = http://192.168.1.222:8883/reuse-service/api/v1
# NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1
NEXT_PUBLIC_WR_SERVER_API_URL = http://192.168.1.222:8883/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API
# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
@@ -1,6 +1,6 @@
"use client";
/**
* 📄 CoMatRvw 수요매칭 검토
* 📄 CoMatRvw 수요처 매칭 검토
* Next.js App Router (app/) 기반 페이지 컴포넌트 예시
*
* - 대민에서 신청하는 수요처의 수요등록 정보를 운영담당자가 수요 매칭 검토를 진행한다.
@@ -8,7 +8,7 @@
*/
import { useWrStore } from "@/app/store";
import { btnClass } from "@/app/store/common/code";
import { usageProcData } from "@/app/store/matDmd/coMatRvw/headList";
import { usageProcData } from "@/app/store/matDmd/coMatRvw/headList"; // 용도에 따른 처리공정
import {
DmdCoReqSchema,
type DmdCoReqType,
@@ -112,19 +112,29 @@ 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" });
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 ??
useUsgCdWatch;
if (normalizedUseUsgCd !== useUsgCdWatch) {
form.setValue("useUsgCd", normalizedUseUsgCd);
}
form.setValue(
"demandCst.usgPrcsProcs",
usageProcData?.find((item) => item.usage === useUsgCdWatch)?.process ??
usageProcData?.find((item) => item.useUsgCd === normalizedUseUsgCd)?.process ??
""
);
}
}, [useUsgCdWatch, form, selectionVersion]);
}, [useUsgCdWatch, form, selectionVersion, code.sewageOutFclttUse]);
useEffect(() => {
if (coMatRvwRes && coMatRvwRes.length > 0) {
+2
View File
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { App } from "../src/app";
import { setCookie } from "../src/shared/lib/cookie";
setCookie("site-id-key", process.env.NEXT_PUBLIC_SITE_ID || "WR");
export const metadata: Metadata = {
title: "물재이용매칭시스템",
description: "물재이용매칭 시스템입니다",
+3
View File
@@ -77,6 +77,7 @@ export const getCommonUpCd = async (set: any) => {
pplQulity: lwCdCode["pplQulity"] ?? [],
pplLenght: lwCdCode["pplLenght"] ?? [],
dmdUszCd: lwCdCode["dmdUszCd"] ?? [],
sewageOutFclttUse: lwCdCode["sewageOutFclttUse"] ?? [],
ruseMngMtd: lwCdCode["ruseMngMtd"] ?? [],
rusePrcsMthdCd: lwCdCode["rusePrcsMthdCd"] ?? [],
erctUswtrPrcsMeth: lwCdCode["erctUswtrPrcsMeth"] ?? [],
@@ -233,6 +234,7 @@ export type comCodeState = {
pplQulity: SelectDataType[];
pplLenght: SelectDataType[];
dmdUszCd: SelectDataType[];
sewageOutFclttUse: SelectDataType[];
buildingType: {
RST: string;
RNB: string;
@@ -274,6 +276,7 @@ export const comCodeStateExport: comCodeState = {
pplLenght: lwCds["pplLenght"] ?? [],
yn: yn,
dmdUszCd: lwCds["dmdUszCd"] ?? [],
sewageOutFclttUse: lwCds["sewageOutFclttUse"] ?? [],
buildingType: buildingType,
ruseMngMtd: lwCds["ruseMngMtd"] ?? [],
ruseSeCd: ruseSeCd,
+6 -3
View File
@@ -39,7 +39,8 @@ export const comFcltStateExport: comFcltState = {
cdtnv: 0,
crmty: 0,
rmrk: "",
reqDt: ""
reqDt: "",
zip: "",
},
constCostRes:[],
rateFeeRes:[],
@@ -51,7 +52,7 @@ export type comFcltState = {
searchFclts: pubHasuType[];
demandCompList: DemandCompType[];
checkDemand: DemandCompType;
constCostRes:conCostFeeType[],
constCostRes:conCostFeeType[], // 표준사업비
rateFeeRes:rateFeeType[],
};
@@ -164,7 +165,9 @@ export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => {
} catch (e) {
console.log(" error ", e);
}
},getConstCostFee: async (year: string) => {
},
// 공사금액 조회 : ONSEMIRO_NEW.EM_STD_COST_REUSE 설치관리표준사업비 재이용시설 공사비
getConstCostFee: async (year: string) => {
try {
const url = `/prcsFclt/CstRuse/${year}`;
const constCost = await getFetch(fetchType.COMMON, url);
@@ -187,13 +187,11 @@ 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" },
{ useUsgCd: "01", usage: "청소화장실용수", process: "총인처리시설+염소소독" },
{ useUsgCd: "02", usage: "세척살수용수", process: "총인처리시설+UV소독" },
{ useUsgCd: "03", usage: "조경용수", process: "총인처리시설+UV소독" },
{ useUsgCd: "05", usage: "지하수충전", process: "전처리시설+RO" },
{ useUsgCd: "06", usage: "농업용수", process: "총인처리시설+UV소독" },
{ useUsgCd: "07", usage: "하천등유지용수", process: "총인처리시설+UV소독" },
{ useUsgCd: "08", usage: "공업용수", process: "전처리시설+RO" },
];
+2 -1
View File
@@ -139,6 +139,7 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => {
return "매칭 검토 저장 오류입니다.";
}
},
// 수요처 매칭 검토 현황
coMatRvwSearch: async (
pageNo: number,
pageCnt: number,
@@ -160,7 +161,7 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => {
url += `/0`;
}
console.log(" url ", url);
// console.log(" url ", url);
const coMatRvwRes = await getFetchWithCount(fetchType.WR, url);
return set((state: coRvwState) => ({
@@ -1,3 +1,18 @@
/**
* 주어진 코드와 수량 기준으로 보간 계산에 사용할 하한/상한 구간 데이터를 찾는다.
*
* 같은 코드(`fctCd`)를 가진 데이터만 추린 뒤,
* `qtyCode` 값이 현재 수량(`qty`) 이하인 항목 중 가장 큰 값을 `lower`,
* 초과인 항목 중 가장 작은 값을 `upper`로 반환한다.
* 한쪽 경계만 존재하면 같은 값을 상/하한에 보정해 반환한다.
*
* @param data 전체 기준 데이터 목록
* @param fctCd 찾고 싶은 시설/구분 코드 값
* @param dataCode 코드 비교에 사용할 객체 속성명
* @param qtyCode 수량 비교에 사용할 객체 속성명
* @param qty 현재 비교 기준 수량
* @returns 보간 계산에 사용할 하한(lower), 상한(upper) 데이터
*/
export const findBounds = (
data: any[],
fctCd: string,
@@ -20,6 +20,7 @@ import {
Form,
FormField,
FormUi,
SelectBox,
UiTable,
} from "@common/business_components/ui";
import PopoverUi from "@common/business_components/ui/popover_ui";
@@ -47,12 +48,13 @@ export default function ConCostForm({
readonly: boolean;
}) {
const {
constCostRes,
constCostRes, // 표준사업비
rateFeeRes,
coMatRvwUpdate,
ruseRvwCalUpdate,
getRateFee,
getConstCostFee,
code,
} = useWrStore((state) => state);
const alert = useAlertStore((state) => state.setMessage);
@@ -122,20 +124,19 @@ export default function ConCostForm({
const dmndQtyYear = dmndQtyWatch;
if (usgPrcsProcs && usgPrcsProcs?.length > 0) {
const firstProc =
getConstFctCode(usgPrcsProcs?.split("+")[0] || "") || "";
const secondProc =
getConstFctCode(usgPrcsProcs?.split("+")[1] || "") || "";
const firstProc = getConstFctCode(usgPrcsProcs?.split("+")[0] || "") || ""; // 첫번째 공정
const secondProc = getConstFctCode(usgPrcsProcs?.split("+")[1] || "") || ""; // 두번째 공정
// 첫번째 처리공정 코드에 해당하는 표준공사비 테이블이서 요구수량을 감싸는 하한/상한 구간을 찾는 부분이다.
const { lower: firstLower, upper: firstUpper } = findBounds(
constCostRes,
constCostRes, // 표준공사비
firstProc || "",
"fctCd",
"fctCpc",
Number(dmndQtyYear)
);
const { lower: secondLower, upper: secondUpper } = findBounds(
constCostRes,
constCostRes, // 표준공사비
secondProc || "",
"fctCd",
"fctCpc",
@@ -365,14 +366,17 @@ export default function ConCostForm({
name="useUsgCd"
render={({ field }) => (
<FormUi label="용도">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(e.target.value);
<SelectBox
className="w-full"
label=""
selectClass={""}
selectArray={code.sewageOutFclttUse || []}
onSelectValue={(val: string | number) => {
if (readonly) return;
field.onChange(String(val));
}}
value={field.value ?? ""}
readOnly={true}
readOnly={readonly}
/>
</FormUi>
)}
@@ -42,22 +42,24 @@ const FormField = <
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState)
// 필드명으로 필드의 상태를 가져옴.
// errors, isDirty, dirtyFields, touchedFields, isSubmitted, isSubmitSuccessful, isValid, defaultValues, disabled
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formItemId: `${id}-form-item`, // 접근성 속성 연결용
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
@@ -86,6 +88,8 @@ const FormItem = React.forwardRef<
})
FormItem.displayName = "FormItem"
// React.forwardRef를 사용해서 부모가 Label DOM ref를 직접 받을 수 있게 하고,
// 현재 FormField의 에러 상태와 form item id를 연결해 접근성(htmlFor)과 에러 스타일을 함께 처리한다.
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>