diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx index 212eca6e7..fb81197d6 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx @@ -7,7 +7,7 @@ import ListDiv from '@common/business_components/ui/list_div'; import { useEffect, useState } from 'react'; import { Toaster } from 'react-hot-toast'; import SdmLogin from '../sdmLogin/page'; -import type { DemandCompType } from '@/app/store/sdmMng'; +import type { DemandCompBizType } from '@/app/store/sdmMng'; import TransValueFormat from '@/app/common/transValueFormat'; import { useRouter } from 'next/navigation'; @@ -31,8 +31,8 @@ export default function SdmApplyList({}: SdmApplyProps) { const {selectSidoData, selectSigunData} = Common; const [isLogin, setIsLogin] = useState(false); const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false); - const [sdmApplyDetail, setSdmApplyDetail] = useState({} as DemandCompType); - const [transformedList, setTransformedList] = useState([]); + const [sdmApplyDetail, setSdmApplyDetail] = useState({} as DemandCompBizType); + const [transformedList, setTransformedList] = useState([]); useEffect(() => { console.log('sessionStorage : ',sessionStorage.getItem('user')); @@ -60,7 +60,7 @@ export default function SdmApplyList({}: SdmApplyProps) { // 데이터 변환을 위한 별도의 useEffect useEffect(() => { if (sdmInfoStatusList.length > 0) { - const newTransformedList = sdmInfoStatusList.map((item: DemandCompType) => ({ + const newTransformedList = sdmInfoStatusList.map((item: DemandCompBizType) => ({ ...item, ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '', sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '' @@ -121,33 +121,33 @@ export default function SdmApplyList({}: SdmApplyProps) { 총대장균군수(개/100ml) - {} + {} 결합잔류염소(mg/L)​ - {} + {} 탁도(NTU) - {} + {} 생물학적 산소요구량(BOD)(mg/L) - {} + {} 냄새 {} 색도(도) - {} + {} 총질소(T-N)(mg/L) - {} + {} 총인(T-P)(mg/L)​ - {} + {} 수소이온농도(pH)​ {} 염화물(mgCL/L)​ - {} + {} 전기전도도(㎲/cm)​ - {} + {} 비고 {sdmApplyDetail?.rmrk} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx index 33da31f12..4efc58cb5 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useDmStore } from "@/app/store"; -import type { DemandCompType } from "@/app/store/sdmMng"; +import type { DemandCompBizType } from "@/app/store/sdmMng"; import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; import { UiModal } from "@common/business_components/ui/dialog_ui"; import { Button } from "@common/components/ui"; @@ -14,121 +14,99 @@ import * as z from "zod"; import SdmLogin from "../sdmLogin/page"; const sdmInfoFormSchema = z.object({ - sdmNm: z.string() - .min(1, "수요처를 선택해주세요"), + sdmNm: z.string().min(1, "수요처를 선택해주세요"), demandYmd: z.date().optional(), - demandQnty: z.string() + demandQnty: z + .string() .min(1, "요구수량을 입력해주세요") .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), usePurpose: z.string().optional(), - money: z.string() - .regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), + money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), /**요구수질 */ reqWaterQlty: z.string().optional(), - grssClfg: z.string() - .min(1, "총대장균군수를 입력해주세요") - .regex(/^[0-9]+$/, "총대장균군수는 숫자만 입력 가능합니다"), - tn: z.string() - .min(1, "총질소를 입력해주세요") - .regex(/^[0-9]+$/, "총질소는 숫자만 입력 가능합니다"), - combResdch: z.string() - .min(1, "결합잔류염소를 입력해주세요") - .regex(/^[0-9]+$/, "결합잔류염소는 숫자만 입력 가능합니다"), - tph: z.string() - .min(1, "총인산물을 입력해주세요") - .regex(/^[0-9]+$/, "총인산물은 숫자만 입력 가능합니다"), - trbt: z.string() - .min(1, "탁도를 입력해주세요") - .regex(/^[0-9]+$/, "탁도는 숫자만 입력 가능합니다"), - ph: z.string() - .min(1, "수소이온농도를 입력해주세요") - .regex(/^[0-9]+$/, "수소이온농도는 숫자만 입력 가능합니다"), - bod: z.string() - .min(1, "생물학적 산소요구량을 입력해주세요") - .regex(/^[0-9]+$/, "생물학적 산소요구량은 숫자만 입력 가능합니다"), + grssClfg: z.number().nullish(), + tn: z.number().nullish(), + combResdch: z.number().nullish(), + tph: z.number().nullish(), + trbt: z.number().nullish(), + ph: z.string().nullish(), + bod: z.number().nullish(), /**컬럼확인필요 */ - clIonQnt: z.string() - .min(1, "염화물을 입력해주세요") - .regex(/^[0-9]+$/, "염화물은 숫자만 입력 가능합니다"), - smll: z.string() - .min(1, "냄새를 입력해주세요") - .regex(/^[0-9]+$/, "냄새는 숫자만 입력 가능합니다"), - cdtnv: z.string() - .min(1, "전기전도도를 입력해주세요") - .regex(/^[0-9]+$/, "전기전도도는 숫자만 입력 가능합니다"), - crmty: z.string() - .min(1, "색도를 입력해주세요") - .regex(/^[0-9]+$/, "색도는 숫자만 입력 가능합니다"), + clIonQnt: z.number().nullish(), + smll: z.string(), + cdtnv: z.number().nullish(), + crmty: z.number().nullish(), rmrk: z.string().optional(), /* telno: z.string() .min(8, "전화번호는 8자 이상 입력해주세요") .max(12, "전화번호는 12자 이내로 입력해주세요") .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ - candidateTitle: z.array(z.object({ - ruseUtztnFcltNm: z.string(), - ruseUtztnFcltCd: z.string(), - })).optional(), + candidateTitle: z + .array( + z.object({ + ruseUtztnFcltNm: z.string(), + ruseUtztnFcltCd: z.string(), + }) + ) + .optional(), }); export type FormData = z.infer; function SdmInfoInsertContent() { const searchParams = useSearchParams(); - const {insertSdmInfoData, Sdm ,setSubsInfo} = useDmStore((state) => state); + const { insertSdmInfoData, Sdm, setSubsInfo } = useDmStore((state) => state); const { successLogin } = Sdm; const [ismoneyDisabled, setIsmoneyDisabled] = useState(false); const formSchema = sdmInfoFormSchema; const [isLogin, setIsLogin] = useState(false); - const [facilityName, setFacilityName] = useState(''); - const [defaultValues, setDefaultValues] = useState({ - sdmNm: '', + const [facilityName, setFacilityName] = useState(""); + const [defaultValues, setDefaultValues] = useState({ + sdmNm: "", demandYmd: new Date(), - demandQnty: '', - usePurpose: '', - money: '', + demandQnty: "", + usePurpose: "", + money: "", /**요구수질 */ - reqWaterQlty: '', - grssClfg: '', - tn: '', - combResdch: '', - tph: '', - trbt: '', - ph: '', - bod: '', + reqWaterQlty: "", + grssClfg: null, + tn: null, + combResdch: null, + tph: null, + trbt: null, + ph: null, + bod: null, /**컬럼확인필요 */ - clIonQnt: '', - smll: '', - cdtnv: '', - crmty: '', - rmrk: '', + clIonQnt: null, + smll: "", + cdtnv: null, + crmty: null, + rmrk: "", /** 후보지 제목 */ candidateTitle: [ - { ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, - { ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, - { ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, + { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, + { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, + { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, ], }); - - useEffect(() => { - const user = sessionStorage.getItem('user'); - setDefaultValues(prev => ({ + const user = sessionStorage.getItem("user"); + setDefaultValues((prev) => ({ ...prev, - sdmNm: searchParams.get('dmdCoNm') || '', + sdmNm: searchParams.get("dmdCoNm") || "", })); - sessionStorage.setItem('user', searchParams.get('dmdInfoId') || ''); - setIsLogin(user || searchParams.get('dmdInfoId') ? true : false); + sessionStorage.setItem("user", searchParams.get("dmdInfoId") || ""); + setIsLogin(user || searchParams.get("dmdInfoId") ? true : false); }, [searchParams]); useEffect(() => { - const local = localStorage.getItem('user'); + const local = localStorage.getItem("user"); if (local) { setIsLogin(true); } setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) - }, [setSubsInfo]); const form = useForm({ @@ -139,99 +117,138 @@ function SdmInfoInsertContent() { const subsInfo = useDmStore((state) => state.map.subsInfo); - useEffect(()=>{ + useEffect(() => { //map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함 - if(subsInfo.length>0){ - form.setValue('candidateTitle', subsInfo) + if (subsInfo.length > 0) { + form.setValue("candidateTitle", subsInfo); } + }, [subsInfo, form]); -},[subsInfo, form]) + //후보지 팝업 값 셋팅 + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + if (event.data?.type === "SET_SUBS_INFO") { + setSubsInfo(event.data.payload); // zustand로 값 설정 + } + }; -//후보지 팝업 값 셋팅 -useEffect(() => { - const handleMessage = (event: MessageEvent) => { - if (event.data?.type === 'SET_SUBS_INFO') { - setSubsInfo(event.data.payload); // zustand로 값 설정 - } - }; - - window.addEventListener('message', handleMessage); - return () => window.removeEventListener('message', handleMessage); -}, [setSubsInfo]); + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, [setSubsInfo]); // 로그인 성공 시 모달 닫기 및 폼 업데이트 useEffect(() => { if (successLogin) { setIsLogin(true); - const user = sessionStorage.getItem('user'); + const user = sessionStorage.getItem("user"); if (user) { try { const userData = JSON.parse(user); if (userData.dmdCoNm) { setFacilityName(userData.dmdCoNm); - form.setValue('sdmNm', userData.dmdCoNm); + form.setValue("sdmNm", userData.dmdCoNm); } - } catch (error) { - console.error('Error parsing user data:', error); + console.error("Error parsing user data:", error); } } } }, [successLogin, form]); + useEffect(() => { + console.log("Form state changed:", form.formState.errors); + }, [form.formState.errors]); + // 체크박스 상태가 변경될 때마다 money 필드의 에러를 초기화 useEffect(() => { if (ismoneyDisabled) { - form.clearErrors('money'); - form.setValue('money', ''); + form.clearErrors("money"); + form.setValue("money", ""); } }, [ismoneyDisabled, form]); -const router = useRouter(); + const router = useRouter(); const onSubmit: SubmitHandler = async (data) => { try { - // Transform form data to match API schema - const apiData: DemandCompType = { - dmdInfoId: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''), - dmdInfoFk: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''), - dmdCoNm: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdCoNm?.toString() || data.sdmNm, - ctpvCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.ctpvCd?.toString() || searchParams.get('ctpvCd') || '', - sggCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.sggCd?.toString() || searchParams.get('sggCd') || '', - daddr: JSON.parse(sessionStorage.getItem('user') || '{}')?.daddr?.toString() || searchParams.get('daddr') || '', - telno: JSON.parse(sessionStorage.getItem('user') || '{}')?.telno?.toString() || searchParams.get('telno') || '', - rgtr: JSON.parse(sessionStorage.getItem('user') || '{}')?.rgtr?.toString() || searchParams.get('rgtr') || '', - useUsgCd: data.usePurpose || '', - wntUntprc: Number(data.money) || '', + const dmSubsInfo = data.candidateTitle?.filter( + (item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd + ); + // Transform form data to madata.candidateTitletch API schema + const apiData: DemandCompBizType = { + dmdInfoId: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdInfoId?.toString() || + searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""), + dmdInfoFk: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdInfoId?.toString() || + searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""), + dmdCoNm: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdCoNm?.toString() || data.sdmNm, + ctpvCd: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.ctpvCd?.toString() || + searchParams.get("ctpvCd") || + "", + sggCd: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.sggCd?.toString() || + searchParams.get("sggCd") || + "", + daddr: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.daddr?.toString() || + searchParams.get("daddr") || + "", + telno: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.telno?.toString() || + searchParams.get("telno") || + "", + rgtr: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.rgtr?.toString() || + searchParams.get("rgtr") || + "", + useUsgCd: data.usePurpose || "", + wntUntprc: Number(data.money) || "", dmndTmng: data.demandYmd || new Date(), - dmndQty: Number(data.demandQnty) || '', - grssClfg: Number(data.grssClfg) || '', - tn: Number(data.tn) || '', - combResdch: Number(data.combResdch) || '', - tph: Number(data.tph) || '', - trbt: Number(data.trbt) || '', - ph: Number(data.ph) || '', - blgcOxygDmndQnt: Number(data.bod) || '', - clIonQnt: Number(data.clIonQnt) || '', - smll: Number(data.smll) || '', - cdtnv: Number(data.cdtnv) || '', - crmty: Number(data.crmty) || '', - rmrk: data.rmrk || '', + dmndQty: Number(data.demandQnty) || "", + grssClfg: data.grssClfg === null ? -1 : Number(data.grssClfg), + tn: Number(data.tn) || null, + combResdch: Number(data.combResdch) || null, + tph: Number(data.tph) || null, + trbt: Number(data.trbt) || null, + ph: data.ph || "", + blgcOxygDmndQnt: Number(data.bod) ||null, + clIonQnt: Number(data.clIonQnt) || null, + smll: data.smll || "", + cdtnv: Number(data.cdtnv) ||null, + crmty: Number(data.crmty) || null, + rmrk: data.rmrk || "", bzmnNo: "", dmdAplyPswd: "", index: 0, reqDt: "", dmdInfoSn: 0, - status: '', - splyCndtList: data.candidateTitle || [], + status: "", + splyCndtList: dmSubsInfo || [], }; - - await insertSdmInfoData(apiData); setTimeout(() => { router.push("/"); - }, 1000); + }, 1000); // 0.8초 후 이동 } catch (error) { - console.error('Error submitting form:', error); + console.error("Error submitting form:", error); } }; @@ -254,8 +271,10 @@ const router = useRouter(); />
@@ -265,7 +284,7 @@ const router = useRouter();
- {!isLogin ? + {!isLogin ? ( } bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]" @@ -278,7 +297,7 @@ const router = useRouter(); noHeaderYn="Y" noFooterYn="Y" /> - : null} + ) : null} ); @@ -291,4 +310,3 @@ export default function MyForm() { ); } - diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx index a0aa35145..9a68510e3 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx @@ -1,8 +1,8 @@ "use client"; import { useDmStore } from "@/app/store"; -import type { DemandCompType } from "@/app/store/sdmMng"; import SdmInsertForm from '@common/business_components/ui/sdmInsertForm'; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useRef } from "react"; import { Toaster } from "react-hot-toast"; @@ -44,15 +44,9 @@ export default function MyForm() { const handleInsertSdmData = useCallback((data: import('@common/business_components/ui/sdmInsertForm/form').DemandCompType) => { const convertedData: DemandCompType = { ...data, - dmdInfoSn: '', - dmdInfoFk: '', - status: '', - splyCndtList: [] }; formDataRef.current = convertedData; - insertSdmData(convertedData).catch(error => { - console.error('Error inserting SDM data:', error); - }); + insertSdmData(convertedData) }, [insertSdmData]); return ( @@ -70,4 +64,3 @@ export default function MyForm() { ); } - diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 65fadc714..2199aa7b6 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -2,12 +2,13 @@ import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../common"; import toast from "react-hot-toast"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; /**==================================== * Store - 타입 =====================================*/ // 수요처 정보 -export type DemandCompType = { +export type DemandCompBizType = { index : number | ''; dmdInfoId : string | ''; dmdInfoFk : string | ''; @@ -23,17 +24,17 @@ export type DemandCompType = { wntUntprc : number | ''; dmndTmng : Date | ''; dmndQty : number | ''; - grssClfg : number | ''; - tn : number | ''; - combResdch : number | ''; - tph : number | ''; - trbt : number | ''; - ph : number | ''; - blgcOxygDmndQnt : number | ''; - clIonQnt : number | ''; - smll : number | ''; - cdtnv : number | ''; - crmty : number | ''; + grssClfg : number | null //총대장균군수 + tn : number | null //총질소 + combResdch : number | null //결합잔류염소 + tph : number | null //총인 + trbt : number | null //탁도 + ph : string | ''; //수소이온농 + blgcOxygDmndQnt : number | null //생물학적산소요구량 + clIonQnt : number | null //염화물 + smll : string | ''; //냄새 + cdtnv : number | null; //전기전도도 + crmty : number | null //색도 rmrk : string | ''; reqDt : string | ''; dmdInfoSn : number | ''; @@ -152,7 +153,8 @@ export type MoneyDetailType = { clclnBrdnCst : string | ''; clclnEcqrRt : string | ''; } - +// 규칙 타입 선언 +export type DisableRulesType = Record; /**==================================== * Store - 데이터 ====================================*/ @@ -160,6 +162,131 @@ export type MoneyDetailType = { const demandCompList : DemandCompType[] = [ ] + +const disableRules: DisableRulesType = { + "청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'], + "화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'], + "세척": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'], + "살수": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'], + "조경": ["combResdch", "crmty",'tph','cdtnv'], + "농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], + "농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], + "하천": ["combResdch", "trbt", "crmty" ,'clIonQnt','cdtnv'], + '공업':[] + }; + + const defaultRules: Record = { + "청소": { + grssClfg: null, + combResdch:0.2, + trbt: 2, + bod: 5, + crmty: 20, + ph: "5.8~8.5", + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + + }, + "화장실용수": { + grssClfg: null, + combResdch:0.2, + trbt: 2, + bod: 5, + crmty: 20, + ph: "5.8~8.5", + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "세척": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "살수": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "조경": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + clIonQnt:250, + combResdch:null, + crmty:null, + tn:null, + tph: null, + cdtnv: null, + }, + "농업_간접용수": { + grssClfg: null, + trbt: 5, + bod: 8, + ph: "5.8~8.5", + cdtnv:2000, + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + }, + "농업_직접용수": { + grssClfg: null, + trbt: 2, + bod: 8, + ph: "5.8~8.5", + cdtnv:700, + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + }, + "하천": { + grssClfg: 1000, + bod: 5, + tn: 10, + tph: 0.5, + ph: "5.8~8.5", + combResdch:null, + trbt:null, + crmty:null, + clIonQnt: null, + cdtnv:null + }, + 공업:{ + grssClfg: null, + bod: null, + tn: null, + tph: null, + ph: "~", + combResdch:null, + trbt:null, + crmty:null, + clIonQnt: null, + cdtnv:null + } + }; + // 사용용도 const useTypesData : SelectDataType[] = [ { text: '청소', val: "청소" }, @@ -167,7 +294,8 @@ const useTypesData : SelectDataType[] = [ { text: "세척", val: "세척" }, { text: "살수", val: "살수" }, { text: "조경", val: "조경" }, - { text: "농업", val: "농업" }, + { text: "농업_간접용수", val: "농업_간접용수" }, + { text: "농업_직접용수", val: "농업_직접용수" }, { text: "하천", val: "하천" }, { text: "지하수", val: "지하수" }, { text: "공업", val: "공업" }, @@ -280,7 +408,7 @@ const postSdmData = async (data: DemandCompType) => { } // 수요정보 등록 -const postSdmInfoData = async (data: DemandCompType) => { +const postSdmInfoData = async (data: DemandCompBizType) => { try { await postFetch("/reuseFc/request/demandInfo", data); toast.success('수요 정보 등록에 성공하였습니다.'); @@ -360,7 +488,7 @@ const fetchGetSimilarCaseList = async (part: string, value: number|string) => { export type SdmActions = { initializeSdmData: () => void; insertSdmData: (data: DemandCompType) => Promise; - insertSdmInfoData: (data: DemandCompType) => Promise; + insertSdmInfoData: (data: DemandCompBizType) => Promise; getDemandCompList: (searchText: string, pageCnt: number) => Promise; actionLogin: (data: DemandCompType) => Promise; getSdmInfoStatusList: (dmdInfoId: string) => Promise; @@ -378,9 +506,10 @@ export type SdmType ={ moneyDetailList: MoneyDetailType[]; // 수요정보 신청현황 - sdmInfoStatusList: DemandCompType[]; + sdmInfoStatusList: DemandCompBizType[]; upHeadSdmInfoStatus: TableUpperProps[]; - + defaultRules: Record + disableRules: DisableRulesType; // 로그인관련 successNextPage : boolean; successLogin: boolean; @@ -398,7 +527,10 @@ export const SdmState: SdmType = { moneyDetailList: moneyDetailList, // 수요정보 신청현황 sdmInfoStatusList: [], + defaultRules:defaultRules, + disableRules:disableRules, upHeadSdmInfoStatus: upHeadSdmInfoStatus, + // 로그인관련 successNextPage : false, successLogin: false, @@ -428,7 +560,7 @@ export const SdmReducer = (set: any): SdmActions => { } }, - insertSdmInfoData: async (data: DemandCompType) => { + insertSdmInfoData: async (data: DemandCompBizType) => { postSdmInfoData(data); }, @@ -465,9 +597,9 @@ export const SdmReducer = (set: any): SdmActions => { })) return; } - const sdmInfoStatusList : DemandCompType[] = []; + const sdmInfoStatusList : DemandCompBizType[] = []; - res.demandInfo.map((el: DemandCompType, index: number) => { + res.demandInfo.map((el: DemandCompBizType, index: number) => { const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn); const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청'; diff --git a/apps/dm/components/ui/common/searhDemandInput/component.tsx b/apps/dm/components/ui/common/searhDemandInput/component.tsx index 5598129a7..70ccf9efc 100644 --- a/apps/dm/components/ui/common/searhDemandInput/component.tsx +++ b/apps/dm/components/ui/common/searhDemandInput/component.tsx @@ -1,5 +1,5 @@ import { useDmStore } from '@/app/store'; -import type { DemandCompType } from '@/app/store/sdmMng'; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; import React, { useCallback, useEffect, useRef, useState } from 'react'; type thisProps = { diff --git a/apps/dm/components/ui/common/utils/utils.ts b/apps/dm/components/ui/common/utils/utils.ts new file mode 100644 index 000000000..f66437553 --- /dev/null +++ b/apps/dm/components/ui/common/utils/utils.ts @@ -0,0 +1,18 @@ +export const sanitizeInput = (val: string, allowDecimal = false) => { + if (val === "") return ""; // 빈 값 허용 + + // 소숫점 허용 + if (allowDecimal) { + // 숫자와 점만 허용 + let cleaned = val.replace(/[^0-9.]/g, ""); + // 소숫점 여러 개일 경우 첫 번째만 유지 + const parts = cleaned.split("."); + if (parts.length > 2) { + cleaned = parts[0] + "." + parts.slice(1).join(""); + } + return cleaned; + } + + // 정수만 허용 + return val.replace(/[^0-9]/g, ""); +}; \ No newline at end of file diff --git a/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx b/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx index 93ec4e353..c37270df8 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx @@ -1,27 +1,29 @@ import { FormUi } from "@common/business_components/ui"; -import type { ReactNode } from "react"; import type { ControllerRenderProps, FieldError, FieldValues, Path } from "react-hook-form"; - - +import { cloneElement, isValidElement } from "react"; interface AccessibleFormFieldProps { field: ControllerRenderProps>; label: string; required?: boolean; error?: FieldError; - inputComponent: ReactNode; + isDisabled?: boolean; + inputComponent: React.ReactElement<{ disabled?: boolean }>; } export function AccessibleFormField({ label, required = false, inputComponent, + isDisabled = false, }: AccessibleFormFieldProps) { const labelText = required ? `${label} *` : label; return ( - +
- {inputComponent} + {isValidElement(inputComponent) + ? cloneElement(inputComponent, { disabled: isDisabled } as Partial) + : inputComponent} {/* {error?.message && Array.from(new Set(error.message.split('\n'))).map((msg, idx) => (

{msg} diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 63638c43e..478c61ac3 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -11,13 +11,20 @@ import { UiModal, } from "@common/business_components"; import { Input } from "@common/components"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useSearchParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { AccessibleFormField } from "./AccessibleFormField"; -import type { ControllerRenderProps, UseFormReturn, FieldError } from "react-hook-form"; +import { + type ControllerRenderProps, + type UseFormReturn, + type FieldError, + useWatch, +} from "react-hook-form"; import SearchInputText from "../common/searhDemandInput/component"; -import type { DemandCompType } from "@/app/store/sdmMng"; +import React from "react"; +import { sanitizeInput } from "../common/utils/utils"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; export interface SdmInfoRegFormProps { form: UseFormReturn; @@ -35,12 +42,15 @@ export function SdmInfoRegForm({ facilityName, }: SdmInfoRegFormProps) { const { initializeCommonData, Sdm } = useDmStore((state) => state); - const { useTypesData } = Sdm; + const { useTypesData, defaultRules, disableRules } = Sdm; const searchParams = useSearchParams(); const [sdmNmValue, setSdmNmValue] = useState( searchParams.get("dmdCoNm") || "" ); + + const [frontPHdata, setFrontPHdata] = useState("0"); + const [backPHdata, setBackPHdata] = useState("0"); const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false); const [isSimilarCase, setIsSimilarCase] = useState(false); const [key, setKey] = useState(0); @@ -58,6 +68,7 @@ export function SdmInfoRegForm({ useEffect(() => { console.log("candidateTitle ", form.getValues("candidateTitle")); }, [form.getValues]); + useEffect(() => { if (useTypesData && useTypesData.length > 0) { setTimeout(() => { @@ -109,6 +120,54 @@ export function SdmInfoRegForm({ onCheckboxChange?.(checked); }; + useEffect(() => { + const watchPH = form.getValues(`ph`); + if (watchPH && watchPH !== undefined) { + const [frontPH, backPH] = watchPH.split("~"); + if (frontPH) { + setFrontPHdata(frontPH); + } + if (backPH) { + setBackPHdata(backPH); + } + } else { + setFrontPHdata("0"); + setBackPHdata("0"); + } + }, [form.getValues(`ph`)]); + + const isSetPh = useCallback( + (typePHdata: string, index: number) => { + const PHdata = form.getValues("ph"); + const isPH = PHdata?.split("~")[index] || ""; + if (index == 0) { + form.setValue("ph", `${isPH}~${typePHdata}`); + setBackPHdata(typePHdata); + } else if (index == 1) { + form.setValue("ph", `${typePHdata}~${isPH}`); + setFrontPHdata(typePHdata); + } + }, + [form] + ); + + const usePurpose = useWatch({ control: form.control, name: "usePurpose" }); + + useEffect(() => { + if (!usePurpose) return; + + // Apply default values + const defaults = defaultRules[usePurpose]; + + console.log("usePurpose", usePurpose); + console.log("defaults", defaults); + if (defaults) { + Object.entries(defaults).forEach(([field, value]) => { + form.setValue(field as keyof FormData, value, { shouldDirty: true }); + }); + } + }, [form.getValues("usePurpose")]); + // 시설 선택시 이벤트 const selectComp = useCallback( (rtnData: DemandCompType[]) => { @@ -178,7 +237,6 @@ export function SdmInfoRegForm({ required: true, component: (field) => ( ( - - ), - }, - { - name: "tn", - label: "총질소(T-N)(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "combResdch", - label: "결합잔류염소(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "tph", - label: "총인(T-P)(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "trbt", - label: "탁도(NTU)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "ph", - label: "수소이온농도(pH)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "bod", - label: "생물학적 산소요구량(BOD)(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "clIonQnt", - label: "염화물(mgCL/L)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "smll", - label: "냄새", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "cdtnv", - label: "전기전도도(㎲/cm)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "crmty", - label: "색도(도)", - formIndex: 1, - required: true, - component: (field) => ( - - ), - }, - { - name: "rmrk", - label: "비고", - formIndex: 1, - component: (field) => ( - - ), - }, + { name: "candidateTitle", label: CANDIDATE || "공급처 후보지 선택(필수아님)", @@ -466,10 +309,20 @@ export function SdmInfoRegForm({ component: () => (

- -
@@ -495,9 +348,9 @@ export function SdmInfoRegForm({ const normalFields = formFields.filter( (f) => f.formIndex === 0 && f.component ); - const groupFields = formFields.filter( - (f) => f.formIndex === 1 && f.component - ); + // const groupFields = formFields.filter( + // (f) => f.formIndex === 1 && f.component + // ); const candidateFields = formFields.filter( (f) => f.formIndex === 2 && f.component ); @@ -512,17 +365,15 @@ export function SdmInfoRegForm({ name={name} render={({ field }) => name === "money" ? ( - component(field) + // component(field) + {component(field)} ) : ( - // - // {component(field)} - // 0 && ( -
- 요구수질 -
- {groupFields.map(({ name, label, component, required }) => ( -
- ( - // - // {component?.(field)} - // - - )} - /> -
- ))} +
+ 요구수질 +
+ { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "불검출" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + {/* TODO: 총대장균군수 확인 필요*/} + {field.value !== null ? ( + + 이하 + + ) : ( + <> + )} +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const cleanValue = sanitizeInput( + e.target.value, + true + ); + field.onChange( + cleanValue === "" ? null : Number(cleanValue) + ); + }} + value={field.value ?? ""} + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이상 + +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const cleanValue = sanitizeInput( + e.target.value, + true + ); + field.onChange( + cleanValue === "" ? null : Number(cleanValue) + ); + }} + value={field.value ?? ""} + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> +
+ +
+ { + const cleanValue = sanitizeInput(e.target.value, true); + isSetPh(cleanValue, 1); + }} + value={frontPHdata ?? ""} + maxLength={5} + /> + ~ + { + const cleanValue = sanitizeInput(e.target.value, true); + isSetPh(cleanValue, 0); + }} + value={backPHdata ?? ""} + maxLength={5} + /> +
+ { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + + { + field.onChange(val.toString()); + }} + value={field.value || "불쾌하지않을것"} + readOnly={isDisabled} + onBlur={field.onBlur} + ref={field.ref} + /> + + ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> + { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + +
+
+ { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + + 이하 + +
+
+
+ ); + }} + /> + { + return ( + +
+
+ { + field.onChange(e.target.value); + }} + value={ + field.value !== undefined && field.value !== "" + ? `${field.value}` + : "" + } + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> +
+
+
+ ); + }} + />
- )} +
{candidateFields.map(({ name, label, component, required }, index) => component ? ( @@ -581,7 +922,7 @@ export function SdmInfoRegForm({ label={label} required={required} error={ - typeof form.formState.errors[name]?.message === 'string' + typeof form.formState.errors[name]?.message === "string" ? (form.formState.errors[name] as FieldError) : undefined } diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 204b12308..dd764844b 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -1,6 +1,6 @@ import { useDmStore } from "@/app/store"; -import type { DemandCompType } from "@/app/store/sdmMng"; import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { Button, Input } from "@common/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { sha512 } from 'js-sha512';