diff --git a/apps/cs/src/pages/link/ui/link-form.tsx b/apps/cs/src/pages/link/ui/link-form.tsx index 05a3da0f1..172215662 100644 --- a/apps/cs/src/pages/link/ui/link-form.tsx +++ b/apps/cs/src/pages/link/ui/link-form.tsx @@ -52,9 +52,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, if (await confirm(confirmMsg)) { if (linkSn) { updateLinkInfo?.(data); - } else { + } else { handleInsertLink?.(data); - } + } } }); }; diff --git a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx index d46a9d863..4c811ce13 100644 --- a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx +++ b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx @@ -66,7 +66,7 @@ function LinkParamsPage() { const userUniqId = getCookie(AUTH_USER_ID) ?? ""; const { setMessage: alert } = useAlertStore((state) => state); - const dbIdList = ['link0001', 'link0002']; + const dbIdList = ['link0001', 'link0002', 'link0007']; // Link 목록 쿼리 const query = useQuery({ 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..9c2f6201e 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,10 +60,10 @@ 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 || '' + sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '', })); setTransformedList(newTransformedList); } @@ -73,6 +73,8 @@ export default function SdmApplyList({}: SdmApplyProps) { if (index !== undefined) { const selectedStatus = transformedList[index]; if (selectedStatus) { + + setSdmApplyDetail(selectedStatus); } } @@ -121,33 +123,33 @@ export default function SdmApplyList({}: SdmApplyProps) { 총대장균군수(개/100ml) - {} + { sdmApplyDetail?.grssClfg!=-1?:''} {sdmApplyDetail?.grssClfg?sdmApplyDetail.grssClfg!=-1? '이하':'불검출':'-'} 결합잔류염소(mg/L)​ - {} + {} {sdmApplyDetail?.combResdch?'이하':'-'} 탁도(NTU) - {} + {} {sdmApplyDetail?.trbt?'이하':'-'} 생물학적 산소요구량(BOD)(mg/L) - {} + {} {sdmApplyDetail?.blgcOxygDmndQnt?'이하':'-'} 냄새 - {} + {} {sdmApplyDetail?.smll?'이하':'-'} 색도(도) - {} + {} {sdmApplyDetail?.crmty?'이하':'-'} 총질소(T-N)(mg/L) - {} + {} {sdmApplyDetail?.tn?'이하':'-'} 총인(T-P)(mg/L)​ - {} + {} {sdmApplyDetail?.tph?'이하':'-'} 수소이온농도(pH)​ {} 염화물(mgCL/L)​ - {} + {} {sdmApplyDetail?.clIonQnt?'이하':'-'} 전기전도도(㎲/cm)​ - {} + {} {sdmApplyDetail?.cdtnv?'이하':'-'} 비고 {sdmApplyDetail?.rmrk} @@ -156,11 +158,11 @@ export default function SdmApplyList({}: SdmApplyProps) { 후보지1 - + { sdmApplyDetail?.splyCndtList?.[0]?.ruseUtztnFcltNm ?? ""} 후보지2 - + {sdmApplyDetail?.splyCndtList?.[1]?.ruseUtztnFcltNm ?? ""} 후보지3 - + {sdmApplyDetail?.splyCndtList?.[2]?.ruseUtztnFcltNm ?? ""} @@ -177,7 +179,7 @@ export default function SdmApplyList({}: SdmApplyProps) { 진행상태 {sdmApplyDetail?.status?.toString()} 공급처매칭결과 - + {sdmApplyDetail?.ruseUtztnFcltNm??''} 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..8e5194bcd 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 { sdmInfoFormSchema, type DemandCompBizType, type SdmInfoFormData } 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"; @@ -9,229 +9,207 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { FormProvider, useForm, type SubmitHandler } from "react-hook-form"; -import { Toaster } from "react-hot-toast"; -import * as z from "zod"; +import toast, { Toaster } from "react-hot-toast"; import SdmLogin from "../sdmLogin/page"; -const sdmInfoFormSchema = z.object({ - sdmNm: z.string() - .min(1, "수요처를 선택해주세요"), - demandYmd: z.date().optional(), - demandQnty: z.string() - .min(1, "요구수량을 입력해주세요") - .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), - usePurpose: z.string().optional(), - 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]+$/, "생물학적 산소요구량은 숫자만 입력 가능합니다"), - /**컬럼확인필요 */ - 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]+$/, "색도는 숫자만 입력 가능합니다"), - 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(), -}); -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({ + const form = useForm({ resolver: zodResolver(formSchema), defaultValues, - mode: "onChange", + mode: "onSubmit", }); 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 onSubmit: SubmitHandler = async (data) => { + 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 +232,10 @@ const router = useRouter(); />
@@ -265,20 +245,21 @@ const router = useRouter();
- {!isLogin ? + {!isLogin ? ( } bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]" open={!isLogin} onOpenChange={(open) => { if (!open) { + toast.dismiss(); router.push("/"); } }} noHeaderYn="Y" noFooterYn="Y" /> - : null} + ) : null} ); @@ -291,4 +272,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/(base-layout)/pages/wrAbout/page.tsx b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx index 04dfa58df..da3324ffc 100644 --- a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx +++ b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx @@ -131,7 +131,7 @@ export default function WrAboutPage() { 물재이용 필요성
-

물 재이용, 왜 필요한가요?

+

물 재이용, 왜 필요한가요?

빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.
물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서
가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다. @@ -237,7 +237,7 @@ export default function WrAboutPage() { 하수 재이용

-

아직 할 일이 많은 하수처리수,
풍족하게 재이용할 수 있어요!

+

아직 할 일이 많은 하수처리수,
풍족하게 재이용할 수 있어요!

사용하고 버려져 공공하수처리장으로 모여든 물은
용도에 맞는 처리 후 재이용 할 수 있습니다.

{ Object.keys(item).length > 0 && !prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId) ); - console.log(newItems) + console.log(newItems); return [...prev, ...newItems]; }); - } catch (error) { console.error("데이터 불러오기 실패:", error); } @@ -232,16 +231,9 @@ const Map = () => { return ( <> - { - if (hasuPk) { - setSelectDataPk(hasuPk); - } - }} - onApply={onApply} - />
{ isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={onCheckedLayersChange} imgVisble={false} + searchBar={(props: any) => ( + { + if (hasuPk) { + setSelectDataPk(hasuPk); + } + }} + onApply={onApply} + {...props} + /> + )} + waterDepth={undefined} />
) { return ( + + 물재이용 대민 + {children} diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 65fadc714..1b63c399d 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -2,12 +2,108 @@ 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"; +import { z } from "zod"; /**==================================== * Store - 타입 =====================================*/ + +export const sdmInfoFormSchema = z.object({ + sdmNm: z.string().min(1, "수요처를 선택해주세요"), + demandYmd: z.date().optional(), + demandQnty: z + .string() + .min(1, "요구수량을 입력해주세요") + .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), + usePurpose: z.string().optional(), + money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), + /**요구수질 */ + reqWaterQlty: z.string().optional(), + 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.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(), +}).superRefine((data, ctx) => { + const disabledFields = disableRules[data.usePurpose ?? ""] || []; + // 모든 수질 관련 필드 + // 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함. + const allFields: (keyof typeof data)[] = [ + "tn", "combResdch", "tph", + "trbt", "ph", "bod", "clIonQnt", + "smll", "cdtnv", "crmty", + ]; + + const allFieldsName: Record = { + tn: "총질소(T-N)(mg/L)", + combResdch: "결합잔류염소(mg/L)", + tph: "총인(T-P)(mg/L)", + trbt: "탁도(NTU)", + ph: "수소이온농도(pH)", + bod: "생물학적 산소요구량(BOD)(mg/L)", + clIonQnt: "염화물(mgCL/L)", + smll: "냄새", + cdtnv: "전기전도도(㎲/cm)", + crmty: "색도(도)", + }; + allFields.forEach((field) => { + const isDisabled = disabledFields.includes(field); + if (!isDisabled) { + // disabled가 아닌 필드는 반드시 값이 있어야 함 + const value = data[field]; + if (typeof value === "string") { + if (value.trim().length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (typeof value === "number") { + if (value <= 0 || Number.isNaN(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (value == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } + }); +}); + +export type SdmInfoFormData = z.infer; + + // 수요처 정보 -export type DemandCompType = { +export type DemandCompBizType = { index : number | ''; dmdInfoId : string | ''; dmdInfoFk : string | ''; @@ -23,22 +119,23 @@ 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 | ''; status : string | ''; splyCndtList:SupplyCandidate[]|[], + ruseUtztnFcltNm?:string|'' } @@ -152,7 +249,8 @@ export type MoneyDetailType = { clclnBrdnCst : string | ''; clclnEcqrRt : string | ''; } - +// 규칙 타입 선언 +export type DisableRulesType = Record; /**==================================== * Store - 데이터 ====================================*/ @@ -160,6 +258,139 @@ export type MoneyDetailType = { const demandCompList : DemandCompType[] = [ ] + +const disableRules: DisableRulesType = { + "청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'], + "화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'], + "세척": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'], + "살수": ["combResdch", "crmty",'tph','tn',"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", + smll:'불쾌하지않을것', + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + + }, + "화장실용수": { + grssClfg: null, + combResdch:0.2, + trbt: 2, + bod: 5, + crmty: 20, + smll:'불쾌하지않을것', + 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, + smll:'불쾌하지않을것', + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "살수": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + smll:'불쾌하지않을것', + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "조경": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + smll:'불쾌하지않을것', + 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, + smll:'불쾌하지않을것', + 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, + smll:'불쾌하지않을것', + 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, + smll:'불쾌하지않을것', + trbt:null, + crmty:null, + clIonQnt: null, + cdtnv:null + } + }; + // 사용용도 const useTypesData : SelectDataType[] = [ { text: '청소', val: "청소" }, @@ -167,9 +398,9 @@ const useTypesData : SelectDataType[] = [ { text: "세척", val: "세척" }, { text: "살수", val: "살수" }, { text: "조경", val: "조경" }, - { text: "농업", val: "농업" }, + { text: "농업_간접용수", val: "농업_간접용수" }, + { text: "농업_직접용수", val: "농업_직접용수" }, { text: "하천", val: "하천" }, - { text: "지하수", val: "지하수" }, { text: "공업", val: "공업" }, ]; @@ -280,7 +511,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 +591,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 +609,10 @@ export type SdmType ={ moneyDetailList: MoneyDetailType[]; // 수요정보 신청현황 - sdmInfoStatusList: DemandCompType[]; + sdmInfoStatusList: DemandCompBizType[]; upHeadSdmInfoStatus: TableUpperProps[]; - + defaultRules: Record + disableRules: DisableRulesType; // 로그인관련 successNextPage : boolean; successLogin: boolean; @@ -398,7 +630,10 @@ export const SdmState: SdmType = { moneyDetailList: moneyDetailList, // 수요정보 신청현황 sdmInfoStatusList: [], + defaultRules:defaultRules, + disableRules:disableRules, upHeadSdmInfoStatus: upHeadSdmInfoStatus, + // 로그인관련 successNextPage : false, successLogin: false, @@ -428,7 +663,7 @@ export const SdmReducer = (set: any): SdmActions => { } }, - insertSdmInfoData: async (data: DemandCompType) => { + insertSdmInfoData: async (data: DemandCompBizType) => { postSdmInfoData(data); }, @@ -465,11 +700,13 @@ 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' ? '매칭완료' : '신청'; + const ruseUtztnFcltNm = matchingInfo?.ruseUtztnFcltNm ? matchingInfo.ruseUtztnFcltNm : ''; + const tmpData = { ...el, @@ -485,7 +722,9 @@ export const SdmReducer = (set: any): SdmActions => { useUsgCd: el.useUsgCd, dmndTmng: el.dmndTmng, dmdInfoSn : el.dmdInfoSn, - status : status + status : status, + splyCndtList: el.splyCndtList, + ruseUtztnFcltNm: ruseUtztnFcltNm || '' } sdmInfoStatusList.push(tmpData); }) 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/map/searchBar/searchBar.tsx b/apps/dm/components/ui/map/searchBar/searchBar.tsx index f2beeff82..7ca64bbce 100644 --- a/apps/dm/components/ui/map/searchBar/searchBar.tsx +++ b/apps/dm/components/ui/map/searchBar/searchBar.tsx @@ -1,8 +1,6 @@ "use client"; import { useDmStore } from "@/app/store"; import { - SearchDiv, - SearchFormLeft, SelectBox, type SelectDataType, } from "@common/business_components/ui"; @@ -48,6 +46,7 @@ export function SearchBar({ }, [Common.selectSigunData] ); + const onSelectValue = useCallback( (val: string, type: string) => { switch (type) { @@ -62,78 +61,58 @@ export function SearchBar({ [initializeSigunData] ); - // useEffect(() => { - // console.log("map.ruseFcltList ", map.ruseFcltList); - // }, [map.ruseFcltList]); - return ( - <> -
-
- - -
- - onSelectValue(val.toString(), "ctpvCd") - } - value={sidoValue || Common.selectSidoData[0]?.val || ""} - /> - - onSelectValue(val.toString(), "sggCd") - } - value={sigunValue} - /> -
- - { - let sggOrCity = ""; - if (searchNm) { - if (sigunValue === "0" || sigunValue === "") { - sggOrCity = sidoValue; - } else { - sggOrCity = sigunValue; - } - findRuseFclt(sggOrCity, searchNm); - } - }} - setRtnData={(rtnData: any) => { - setHasuPk(rtnData); - }} - searchList={map.ruseFcltList || []} - /> -
-
- -
-
-
-
-
+
+
+ onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue || Common.selectSidoData[0]?.val || ""} + /> + onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + + { + let sggOrCity = ""; + if (searchNm) { + if (sigunValue === "0" || sigunValue === "") { + sggOrCity = sidoValue; + } else { + sggOrCity = sigunValue; + } + findRuseFclt(sggOrCity, searchNm); + } + }} + setRtnData={(rtnData: any) => { + setHasuPk(rtnData); + }} + searchList={map.ruseFcltList || []} + /> +
- +
); } 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..c007645bb 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -1,5 +1,4 @@ import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page"; -import type { FormData } from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page"; import { useDmStore } from "@/app/store"; import { CANDIDATE, @@ -11,22 +10,30 @@ 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"; +import type { SdmInfoFormData } from "@/app/store/sdmMng"; export interface SdmInfoRegFormProps { - form: UseFormReturn; + form: UseFormReturn; onCheckboxChange?: (checked: boolean) => void; facilityName?: string; } type FieldComponent = ( - field: ControllerRenderProps + field: ControllerRenderProps ) => JSX.Element; export function SdmInfoRegForm({ @@ -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,52 @@ 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]; + + if (defaults) { + Object.entries(defaults).forEach(([field, value]) => { + form.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true }); + }); + } + }, [form.getValues("usePurpose")]); + // 시설 선택시 이벤트 const selectComp = useCallback( (rtnData: DemandCompType[]) => { @@ -119,7 +176,7 @@ export function SdmInfoRegForm({ ); const formFields: Array<{ - name: keyof FormData; + name: keyof SdmInfoFormData; label: string; component: FieldComponent | undefined; formIndex: number; @@ -178,7 +235,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 +307,20 @@ export function SdmInfoRegForm({ component: () => (

- -
@@ -495,9 +346,7 @@ export function SdmInfoRegForm({ const normalFields = formFields.filter( (f) => f.formIndex === 0 && f.component ); - const groupFields = formFields.filter( - (f) => f.formIndex === 1 && f.component - ); + const candidateFields = formFields.filter( (f) => f.formIndex === 2 && f.component ); @@ -512,17 +361,20 @@ 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 +923,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..7617b4b31 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -1,11 +1,12 @@ 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'; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form"; +import toast from "react-hot-toast"; import * as z from "zod"; export function SdmLoginForm() { @@ -15,10 +16,10 @@ export function SdmLoginForm() { const formatBusinessNumber = (value: string) => { // 숫자만 추출 const digitsOnly = value.replace(/\D/g, ""); - + // 10자리까지만 처리 const limitedDigits = digitsOnly.slice(0, 10); - + // 3-2-5 형식으로 포맷팅 if (limitedDigits.length <= 3) { return limitedDigits; @@ -28,33 +29,33 @@ export function SdmLoginForm() { return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`; } }; - + // 스키마를 useMemo로 최적화 const sdmLoginFormSchema = useMemo(() => z.object({ dmdInfoId: z.string() .min(1, "사업자등록번호를 입력해주세요") .regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다"), - bznmTotNo: isBznmTotNoDisabled + bznmTotNo: isBznmTotNoDisabled ? z.string().optional() : z.string().length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다"), password: z.string() .min(1, "비밀번호를 입력해주세요") - .min(8, "비밀번호는 8자 이상 입력해주세요") + .min(8, "비밀번호는 8자 이상 입력해주세요") .max(16, "비밀번호는 16자 이내로 입력해주세요"), rgtr: z.string().optional(), dmdAplyPswd: z.string().optional(), }), [isBznmTotNoDisabled]); - type FormData = z.infer; - type FieldComponent = (field: ControllerRenderProps) => JSX.Element; - + type SdmLoginForm = z.infer; + type FieldComponent = (field: ControllerRenderProps) => JSX.Element; + const formSchema = sdmLoginFormSchema; useEffect(() => { initializeCommonData(); }, [initializeCommonData]); - const defaultValues: FormData = { + const defaultValues: SdmLoginForm = { dmdInfoId: '', bznmTotNo: '', password: '', @@ -62,7 +63,7 @@ export function SdmLoginForm() { dmdAplyPswd: '', }; - const form = useForm({ + const form = useForm({ resolver: zodResolver(formSchema), defaultValues, mode: "onChange", @@ -72,6 +73,26 @@ export function SdmLoginForm() { setIsBznmTotNoDisabled(checked); }; + const initPassword = () => { + toast( + (t) => ( +
+
+ 관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다. +
+ +
+ ), + { + id: "pwd-reset", + duration: Infinity, + position: "top-center", + } + ); + } + const handleLogin = () => { const data = form.getValues(); const passwordStr = sha512.hex(data.password) @@ -82,61 +103,61 @@ export function SdmLoginForm() { } actionLogin(param as unknown as DemandCompType); }; - + const formFields: Array<{ - name: keyof FormData; + name: keyof SdmLoginForm; label: string; component: FieldComponent | undefined; }> = [ - { - name: "dmdInfoId", - label: "사업자등록번호", - component: (field) => ( - { - const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); - field.onChange(dmdId); - }} - /> - ) - }, - { - name: "bznmTotNo", - label: BZNM_TOT_NO || "종사업자등록번호", - component: (field) => ( - - ( + { + const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); + field.onChange(dmdId); + }} + /> + ) + }, + { + name: "bznmTotNo", + label: BZNM_TOT_NO || "종사업자등록번호", + component: (field) => ( + + + + ) + }, + { + name: "password", + label: "비밀번호", + component: (field) => ( + - - ) - }, - { - name: "password", - label: "비밀번호", - component: (field) => ( - - ) - } - ]; + ) + } + ]; return ( <> @@ -148,11 +169,11 @@ export function SdmLoginForm() { control={form.control} name={name} render={({ field }) => ( - label==='종사업자등록번호' ? - <> + label === '종사업자등록번호' ? + <> {component && component(field)} - : + : {component && component(field)} )} @@ -160,12 +181,15 @@ export function SdmLoginForm() { ))}
- +
+ +
비밀번호 초기화 요청
+
diff --git a/apps/dm/public/images/map/layericon/BML_CBAS_PS.png b/apps/dm/public/images/map/layericon/BML_CBAS_PS.png index 5ff4add3e..0e70939ba 100644 Binary files a/apps/dm/public/images/map/layericon/BML_CBAS_PS.png and b/apps/dm/public/images/map/layericon/BML_CBAS_PS.png differ diff --git a/apps/dm/public/images/map/layericon/BML_STMS_PS.png b/apps/dm/public/images/map/layericon/BML_STMS_PS.png index 423f2e8fc..bf4357181 100644 Binary files a/apps/dm/public/images/map/layericon/BML_STMS_PS.png and b/apps/dm/public/images/map/layericon/BML_STMS_PS.png differ diff --git a/apps/dm/public/images/map/layericon/RDL_ETCT_PS.png b/apps/dm/public/images/map/layericon/RDL_ETCT_PS.png index 29c0b77b4..e5a83f998 100644 Binary files a/apps/dm/public/images/map/layericon/RDL_ETCT_PS.png and b/apps/dm/public/images/map/layericon/RDL_ETCT_PS.png differ diff --git a/apps/dm/public/images/map/layericon/RDL_OCUP_PS.png b/apps/dm/public/images/map/layericon/RDL_OCUP_PS.png index 8a1ac0074..aeae3e266 100644 Binary files a/apps/dm/public/images/map/layericon/RDL_OCUP_PS.png and b/apps/dm/public/images/map/layericon/RDL_OCUP_PS.png differ diff --git a/apps/dm/public/images/map/layericon/RDL_SEPC_PS.png b/apps/dm/public/images/map/layericon/RDL_SEPC_PS.png index 087b4e941..b3db8e0a4 100644 Binary files a/apps/dm/public/images/map/layericon/RDL_SEPC_PS.png and b/apps/dm/public/images/map/layericon/RDL_SEPC_PS.png differ diff --git a/apps/dm/public/images/map/layericon/RDL_STAT_PS.png b/apps/dm/public/images/map/layericon/RDL_STAT_PS.png index 13d8ea689..9a0b11673 100644 Binary files a/apps/dm/public/images/map/layericon/RDL_STAT_PS.png and b/apps/dm/public/images/map/layericon/RDL_STAT_PS.png differ diff --git a/apps/dm/public/images/map/layericon/RDL_TSCT_PS.png b/apps/dm/public/images/map/layericon/RDL_TSCT_PS.png index ffc225159..3776f6c04 100644 Binary files a/apps/dm/public/images/map/layericon/RDL_TSCT_PS.png and b/apps/dm/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/dm/public/img/wrAbout/img_02_1.png b/apps/dm/public/img/wrAbout/img_02_1.png index 3d61c6e5d..aa1c8ee5a 100644 Binary files a/apps/dm/public/img/wrAbout/img_02_1.png and b/apps/dm/public/img/wrAbout/img_02_1.png differ diff --git a/apps/dm/src/entities/auth/api/login-service.ts b/apps/dm/src/entities/auth/api/login-service.ts index 614aadbf9..43ea1c33d 100644 --- a/apps/dm/src/entities/auth/api/login-service.ts +++ b/apps/dm/src/entities/auth/api/login-service.ts @@ -16,7 +16,6 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise { try { - console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN) const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { method: "POST", body: JSON.stringify({ diff --git a/apps/fo/.env.development b/apps/fo/.env.development index 94488f0cd..44fe06736 100644 --- a/apps/fo/.env.development +++ b/apps/fo/.env.development @@ -2,6 +2,7 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver diff --git a/apps/fo/.env.local b/apps/fo/.env.local index 76fb868cd..e125bed3d 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -1,7 +1,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 3000 NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 -NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver diff --git a/apps/fo/.env.production b/apps/fo/.env.production index 16675eaec..52534bd8e 100644 --- a/apps/fo/.env.production +++ b/apps/fo/.env.production @@ -15,6 +15,7 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver diff --git a/apps/fo/app/pages/dashboard/facilityInfo/page.tsx b/apps/fo/app/pages/dashboard/facilityInfo/page.tsx index fa6905fa2..9c16161f5 100644 --- a/apps/fo/app/pages/dashboard/facilityInfo/page.tsx +++ b/apps/fo/app/pages/dashboard/facilityInfo/page.tsx @@ -169,10 +169,9 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}: } else if (index === 8) { el['item'] = (Number(searchYear)-0).toString(); el['provisionalApproval'] = waterUsage1; - } else if(index === 4){ - el['provisionalApproval'] = Math.round((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3 * 100)/100; } }); + console.log('planFcltyList :', planFcltyList); } }, [waterUsage1, waterUsage2, waterUsage3, searchYear, planFcltyList]); // 연도가 바뀌면 hasSearched를 false로 리셋 @@ -226,8 +225,8 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}: :(index < 5 || index > 5)&& (index!= 4) && (head.id ==='provisionalApproval'||head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') ?{el[head.id]} :(head.id === "provisionalApproval" && index === 4) - ?{el[head.id]} - :((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') && index === 4) + ?{Math.round((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3 * 100)/100} + :((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') ) ?null :(head.id === 'item' && index != 5)? {el[head.id]} diff --git a/apps/fo/app/pages/map/page.tsx b/apps/fo/app/pages/map/page.tsx index cbbe0dd11..a388d80ca 100644 --- a/apps/fo/app/pages/map/page.tsx +++ b/apps/fo/app/pages/map/page.tsx @@ -3,6 +3,7 @@ import Gis from "@common/business_components/ui/map/Gis"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import { useEffect, useRef, useState } from "react"; +import { SearchBar } from "./searchBar/searchBar"; interface FeatureData { fid?: string; @@ -13,7 +14,11 @@ interface FeatureData { } interface MapInstance { - getLayers: () => { getArray: () => Array<{ getSource?: () => { getFeatureById?: (id: string) => FeatureData | null } }> }; + getLayers: () => { + getArray: () => Array<{ + getSource?: () => { getFeatureById?: (id: string) => FeatureData | null }; + }>; + }; getView: () => { fit: (extent: number[], options: object) => void }; } @@ -26,20 +31,20 @@ const Map = () => { const fetchData = async () => { try { - const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map` + const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`; const reqUrl = `${API_URL}/lyrStngs/FO_NETWORK`; const response = await fetch(reqUrl, { method: "GET", headers: { accept: "application/json", - } + }, }); if (!response.ok) { throw new Error(`HTTP 오류! 상태: ${response.status}`); } - const data = await response.json(); // 여기서 json 파싱 + const data = await response.json(); // 여기서 json 파싱 setTreeData(data); // 데이터 설정 } catch (error) { console.error("데이터 불러오기 실패:", error); @@ -60,7 +65,12 @@ const Map = () => { } // 항상 같은 select 객체 사용 - const select = (GisApp.Module.select as unknown) as { resetSelectedFeature: () => void; selectFeature: (feature: FeatureData, selected: boolean) => void } | undefined; + const select = GisApp.Module.select as unknown as + | { + resetSelectedFeature: () => void; + selectFeature: (feature: FeatureData, selected: boolean) => void; + } + | undefined; if (!select) return; // 1. 기존 선택 초기화 @@ -114,21 +124,15 @@ const Map = () => { setIsSearchModalOpen(false); }; - // onCheckedLayersChange handler - const handleCheckedLayersChange = (checkedIds: string[], checkedNodesData: unknown[]) => { - // Handle checked layers change if needed - console.log('Checked layers changed:', checkedIds, checkedNodesData); - }; - return (
{ setIsSearchModalOpen={setIsSearchModalOpen} isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} - onCheckedLayersChange={handleCheckedLayersChange} + onCheckedLayersChange={undefined} + imgVisble={false} + searchBar={() => } + waterDepth={undefined} />
); diff --git a/apps/fo/app/pages/map/searchBar/searchBar.tsx b/apps/fo/app/pages/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..0b1315465 --- /dev/null +++ b/apps/fo/app/pages/map/searchBar/searchBar.tsx @@ -0,0 +1,158 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject; +} + +export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState([{ text: "선택", val: "0" }]); + const [sigunData, setSigunData] = useState([{ text: "선택", val: "0" }]); + const [filteredSigunData, setFilteredSigunData] = useState([{ text: "선택", val: "0" }]); + const [facilityData, setFacilityData] = useState([{ text: "선택", val: "0" }]); + const [facilityValue, setFacilityValue] = useState("0"); + + const fetchSido = async () => { + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`; + const res = await fetch(url); + const json = await res.json(); + + const list = json.features.map((f: any) => ({ + text: f.properties.mctpv_korn_nm, + val: f.properties.mctpv_cd, + })); + setSidoData([{ text: "선택", val: "0" }, ...list]); + }; + + const fetchSigun = async () => { + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`; + const res = await fetch(url); + const json = await res.json(); + + const list = json.features.map((f: any) => ({ + text: f.properties.sgg_korn_nm, + val: f.properties.sgg_cd, + })); + setSigunData(list); + }; + + const fetchFacilitiesBySggNm = async (sggNm: string) => { + const encodedSggNm = encodeURIComponent(sggNm); + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`; + const res = await fetch(url); + const json = await res.json(); + + const list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, // f.properties.swtrmt_fclt_cd 도 가능 + })); + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + useEffect(() => { + fetchSido(); + fetchSigun(); + }, []); + + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + } else if (type === "sggCd") { + setSigunValue(val); + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + if (sggNm) fetchFacilitiesBySggNm(sggNm); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData] + ); + + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( +
+
+
+
+ onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> +
+
+ +
+
+
+
+ ); +} diff --git a/apps/fo/app/store/dashboard/index.tsx b/apps/fo/app/store/dashboard/index.tsx index a415d89b3..b23006c10 100644 --- a/apps/fo/app/store/dashboard/index.tsx +++ b/apps/fo/app/store/dashboard/index.tsx @@ -1232,9 +1232,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) } if(operationalStatusList[0] && operationalStatusList[7]){ - operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT) * 100) * 10) / 10+'%'; - operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT) * 100) * 10) / 10+'%'; - operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter) / Number(data.winter.AVG_INFL_AMNT) * 100) * 10) / 10+'%'; + operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData && data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg) / (data.yearData && data.yearData.AVG_INFL_AMNT ? Number(data.yearData.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%'; + operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer && data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer) / (data.summer && data.summer.AVG_INFL_AMNT ? Number(data.summer.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%'; + operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter && data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter) / (data.winter && data.winter.AVG_INFL_AMNT ? Number(data.winter.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%'; operationalStatusList[7].rdi = operationalStatusList[0].rdi === null ? '' : data.summerBod && data.summerBod.INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].rdi) / (data.summerBod&&data.summerBod.INFL_AMNT&&data.summerBod.INFL_AMNT!='null'?Number(data.summerBod.INFL_AMNT):0) * 100) * 10) / 10+'%'; /* operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : Number(Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT)) * 100; operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : Number(Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT)) * 100; diff --git a/apps/fo/app/store/underWaterInput/index.tsx b/apps/fo/app/store/underWaterInput/index.tsx index 70964d496..589c35962 100644 --- a/apps/fo/app/store/underWaterInput/index.tsx +++ b/apps/fo/app/store/underWaterInput/index.tsx @@ -446,7 +446,6 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy let maxWaterUsage = 0; let minWaterUsage = 9999999999; let avgWaterUsage = 0; - console.log('monthData : ', monthData); monthData.forEach((item, index) => { item.usege = item.sum/item.cnt tableData.push({ @@ -465,8 +464,6 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy }); avgWaterUsage = tot/cnt - console.log('avgWaterUsage : ', avgWaterUsage); - console.log('tableData : ', tableData); const newData= [ { msrDt: '평균', diff --git a/apps/fo/public/images/map/layericon/BML_CBAS_PS.png b/apps/fo/public/images/map/layericon/BML_CBAS_PS.png index 5ff4add3e..0e70939ba 100644 Binary files a/apps/fo/public/images/map/layericon/BML_CBAS_PS.png and b/apps/fo/public/images/map/layericon/BML_CBAS_PS.png differ diff --git a/apps/fo/public/images/map/layericon/BML_STMS_PS.png b/apps/fo/public/images/map/layericon/BML_STMS_PS.png index 423f2e8fc..bf4357181 100644 Binary files a/apps/fo/public/images/map/layericon/BML_STMS_PS.png and b/apps/fo/public/images/map/layericon/BML_STMS_PS.png differ diff --git a/apps/fo/public/images/map/layericon/RDL_ETCT_PS.png b/apps/fo/public/images/map/layericon/RDL_ETCT_PS.png index 29c0b77b4..e5a83f998 100644 Binary files a/apps/fo/public/images/map/layericon/RDL_ETCT_PS.png and b/apps/fo/public/images/map/layericon/RDL_ETCT_PS.png differ diff --git a/apps/fo/public/images/map/layericon/RDL_OCUP_PS.png b/apps/fo/public/images/map/layericon/RDL_OCUP_PS.png index 8a1ac0074..aeae3e266 100644 Binary files a/apps/fo/public/images/map/layericon/RDL_OCUP_PS.png and b/apps/fo/public/images/map/layericon/RDL_OCUP_PS.png differ diff --git a/apps/fo/public/images/map/layericon/RDL_SEPC_PS.png b/apps/fo/public/images/map/layericon/RDL_SEPC_PS.png index 087b4e941..b3db8e0a4 100644 Binary files a/apps/fo/public/images/map/layericon/RDL_SEPC_PS.png and b/apps/fo/public/images/map/layericon/RDL_SEPC_PS.png differ diff --git a/apps/fo/public/images/map/layericon/RDL_STAT_PS.png b/apps/fo/public/images/map/layericon/RDL_STAT_PS.png index 13d8ea689..9a0b11673 100644 Binary files a/apps/fo/public/images/map/layericon/RDL_STAT_PS.png and b/apps/fo/public/images/map/layericon/RDL_STAT_PS.png differ diff --git a/apps/fo/public/images/map/layericon/RDL_TSCT_PS.png b/apps/fo/public/images/map/layericon/RDL_TSCT_PS.png index ffc225159..3776f6c04 100644 Binary files a/apps/fo/public/images/map/layericon/RDL_TSCT_PS.png and b/apps/fo/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/fo/src/entities/auth/api/login-service.ts b/apps/fo/src/entities/auth/api/login-service.ts index fafb467e7..3f2d958c6 100644 --- a/apps/fo/src/entities/auth/api/login-service.ts +++ b/apps/fo/src/entities/auth/api/login-service.ts @@ -17,8 +17,6 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise { try { - console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN) - console.log(payload.userId+' '+payload.password) const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { method: "POST", body: JSON.stringify({ diff --git a/apps/sn/.env.development b/apps/sn/.env.development index f12d2c38e..b63add651 100644 --- a/apps/sn/.env.development +++ b/apps/sn/.env.development @@ -5,7 +5,7 @@ NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함 -NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 +NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token diff --git a/apps/sn/.env.local b/apps/sn/.env.local index 253ed7528..2e22382c5 100644 --- a/apps/sn/.env.local +++ b/apps/sn/.env.local @@ -5,7 +5,7 @@ NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver -NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 +NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token diff --git a/apps/sn/.env.production b/apps/sn/.env.production index fd98438d7..acfd68b81 100644 --- a/apps/sn/.env.production +++ b/apps/sn/.env.production @@ -9,5 +9,5 @@ NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000 NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl -NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 +NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api diff --git a/apps/sn/app/(map-layout)/layout.tsx b/apps/sn/app/(map-layout)/layout.tsx index e245cfdf1..e7ab407c3 100644 --- a/apps/sn/app/(map-layout)/layout.tsx +++ b/apps/sn/app/(map-layout)/layout.tsx @@ -1,10 +1,12 @@ "use client"; +import React, { useEffect } from 'react'; import { usePathname, useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { BaseLayout } from "@common/components"; import type { MenuItem } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; +import useStore from '../store/useStore'; export default function MapLayout({ children, @@ -17,6 +19,14 @@ export default function MapLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); + const { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); + + useEffect(() => { + fetchCtpvSggData(); + fetchBIZ_SE_CD(); + fetchBIZ_TY_CD(); + }, []); + const handleMenuClick = (item: MenuItem) => { setCurrentMenuInfo(item); router.push(item.urlPath); diff --git a/apps/sn/app/(map-layout)/map/commonStore.ts b/apps/sn/app/(map-layout)/map/commonStore.ts deleted file mode 100644 index ff903e1e7..000000000 --- a/apps/sn/app/(map-layout)/map/commonStore.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { create } from 'zustand'; - -export type SelectDataType = { - text: string; - val: string; - sido?: string; // 시도 코드 (선택 사항) -} - -interface CityCdStore { - cityCd: string; - setCityCd: (cityCd: string) => void; - sidos: SelectDataType[]; - setSidos: (sidos: SelectDataType[]) => void; - siguns : SelectDataType[]; - setSiguns: (siguns: SelectDataType[]) => void; -} - -export const useCityCdStore = create((set) => ({ - cityCd: '', - setCityCd: (cityCd: string) => set({ cityCd }), - sidos: [], - setSidos: (sidos: SelectDataType[]) => set({ sidos }), - siguns: [], - setSiguns: (siguns: SelectDataType[]) => set({ siguns }), -})); diff --git a/apps/sn/app/(map-layout)/map/components/common/index.ts b/apps/sn/app/(map-layout)/map/components/common/index.ts index 9860b211a..45cb3a18d 100644 --- a/apps/sn/app/(map-layout)/map/components/common/index.ts +++ b/apps/sn/app/(map-layout)/map/components/common/index.ts @@ -12,6 +12,9 @@ type LayerComponentProps = { rainMeasureData?: any; onSaveClick?: () => void; onFormDataChange?: (data: any) => void; // 추가 + chmbrXcnts: string; + chmbrYdnts: string; + gisObject?: Record; // GIS 객체 추가 }; type LayerContentMap = { [key: string]: { @@ -22,7 +25,7 @@ type LayerContentMap = { // 레이어 이름과 컴포넌트를 매핑 // 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업 const layerContentMap: LayerContentMap = { - // "하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리 + "하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리 "하수처리구역": {}, "우수토실": { component: Ssc, }, "중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정 diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index 84de2b4ab..5604d1b55 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -28,20 +28,20 @@ export const MsSwagePplCol = [ // { id: 'lyrTypeNm', title: '레이어타입명' }, ] export const MsSwagePplCol2 = [ - { id: 'rowNumber', title: '일련번호' }, - { id: 'fcltMngNo', title: '시설 관리번호' }, - { id: 'instMngNo', title: '기관 관리번호' }, + { id: 'rowNumber', title: '일련\n번호' }, + { id: 'fcltMngNo', title: '시설\n관리번호' }, + { id: 'instMngNo', title: '기관\n관리번호' }, { id: 'instlYmd', title: '설치일자' }, // { id: 'mapshtNo', title: '도엽번호' }, - { id: 'pdmt', title: '관경(mm)' }, - { id: 'prlg', title: '연장(m)' }, - { id: 'avgGrdn', title: '평균구배(m/m)' }, - { id: 'tmptDpth', title: '시점깊이(m)' }, - { id: 'tmptMhlMngNo', title: '시점맨홀 관리번호' }, - { id: 'scendDpth', title: '종점깊이(m)' }, - { id: 'scendMhlMngNo', title: '종점맨홀 관리번호' }, - { id: 'vrtcLen', title: '세로길이(m)' }, - { id: 'wdthLen', title: '가로길이(m)' }, + { id: 'pdmt', title: '관경\n(mm)' }, + { id: 'prlg', title: '연장\n(m)' }, + { id: 'avgGrdn', title: '평균구배\n(m/m)' }, + { id: 'tmptDpth', title: '시점깊이\n(m)' }, + { id: 'tmptMhlMngNo', title: '시점맨홀\n관리번호' }, + { id: 'scendDpth', title: '종점깊이\n(m)' }, + { id: 'scendMhlMngNo', title: '종점맨홀\n관리번호' }, + { id: 'vrtcLen', title: '세로길이\n(m)' }, + { id: 'wdthLen', title: '가로길이\n(m)' }, // { id: 'fcltyShpCd', title: '시설물형태코드' }, // { id: 'fid', title: 'FID' }, // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, @@ -297,9 +297,9 @@ export const SwtrmtFcltCol = [ // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; export const SwtrmtFcltCol2 = [ - { id: 'rowNumber', title: '일련번호' }, - { id: 'swtrmtFcltNm', title: '하수처리시설명' }, - { id: 'fcltCpct', title: '시설용량(㎥/d)' }, + { id: 'rowNumber', title: '일련\n번호' }, + { id: 'swtrmtFcltNm', title: '하수처리\n시설명' }, + { id: 'fcltCpct', title: '시설\n용량\n(㎥/d)' }, { id: 'rgnExpln', title: '지역설명' }, // { id: 'cmcnYmd', title: '준공일자' }, { id: 'oprtngStrtYmd', title: '가동개시일자' }, @@ -311,7 +311,7 @@ export const SwtrmtFcltCol2 = [ // { id: 'sggNm', title: '시군구명' }, // { id: 'sareaCd', title: '권역코드' }, { id: 'sareaNm', title: '권역명' }, - { id: 'lrgeSareaNm', title: '대권역명' }, + // { id: 'lrgeSareaNm', title: '대권역명' }, { id: 'rvrNm', title: '하천명' }, // { id: 'addr', title: '주소' }, // { id: 'dtlAddr', title: '상세주소' }, @@ -320,19 +320,19 @@ export const SwtrmtFcltCol2 = [ { id: 'dschgWtstNm', title: '방류수계명' }, // { id: 'pllnArea', title: '부지면적(m²)' }, // { id: 'mngRprtvBzentyNm', title: '관리대행업체명' }, - { id: 'swtrmtPpltnCnt', title: '하수처리인구수(인)' }, + { id: 'swtrmtPpltnCnt', title: '하수\n처리\n인구수\n(인)' }, // { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' }, // { id: 'xcrd', title: 'X좌표' }, // { id: 'ycrd', title: 'Y좌표' }, // { id: 'oprtngSttsCd', title: '가동상태코드' }, - { id: 'rprsMsrcCn', title: '대표공법내용' }, + { id: 'rprsMsrcCn', title: '대표\n공법\n내용' }, // { id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량(mg/L)' }, // { id: 'sttyCrtrToc', title: '법정기준TOC(mg/L)' }, // { id: 'sttyCrtrSs', title: '법정기준SS(mg/L)' }, // { id: 'sttyCrtrTn', title: '법정기준총질소(mg/L)' }, // { id: 'sttyCrtrTph', title: '법정기준총인(mg/L)' }, // { id: 'sttyCrtrClbclCnt', title: '법정기준대장균수(군수/100mL)' }, - { id: 'bsnNm', title: '유역명' }, + // { id: 'bsnNm', title: '유역명' }, // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; @@ -353,9 +353,9 @@ export const SwtrmtPrcsZoneCol2 = [ { id: 'swtrmtZoneNm', title: '하수처리구역명' }, // { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' }, // { id: 'ctpv_cd', title: '시도코드' }, - { id: 'ctpvNm', title: '시도명' }, + { id: 'ctpvNm', title: '시도' }, // { id: 'sgg_cd', title: '시군구코드' }, - { id: 'sggNm', title: '시군구명' }, + { id: 'sggNm', title: '시군구' }, { id: 'swtrmtFcltNm', title: '하수처리시설명' }, ]; @@ -686,5 +686,6 @@ export const numberFields = [ 'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt', 'precdngDry', 'rainFall', 'cntncTime', 'rainIn', 'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc', - 'bizAmt', 'count' + 'bizAmt', 'count', + 'chmbr_xcnts', 'chmbr_ydnts' ]; \ No newline at end of file diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx index 08443c73f..ad067383a 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react' import InputSection from '../InputSection'; import { handleInputChange } from '../common'; import { getData } from "@/components/lib/api"; -// import { useBizByFcltStore } from "../../../../store/StoreProvider"; +import useStore from '../../../../store/useStore'; // 하수관로 const inputColumn = [ @@ -30,21 +30,7 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic inspection: { data: inspectionData, setData: setInspectionData }, }; - - // const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD); - const BIZ_SE_CD = [ - { lwpoCd: "100", lwpoCdNm: "관로기술진단" }, - { lwpoCd: "200", lwpoCdNm: "개대체" }, - { lwpoCd: "201", lwpoCdNm: "유지보수" }, - { lwpoCd: "203", lwpoCdNm: "청소, 준설" }, - { lwpoCd: "204", lwpoCdNm: "단순보수" }, - ]; - // const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD); - const BIZ_TY_CD = [ - { lwpoCd: "SV", lwpoCdNm: "용역" }, - { lwpoCd: "SF", lwpoCdNm: "자체" }, - { lwpoCd: "SC", lwpoCdNm: "공사" }, - ]; + const { BIZ_SE_CD, BIZ_TY_CD } = useStore(); // 마운트 및 업데이트 시 API 호출 diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx index 52fc5523d..0ab04eea5 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -6,26 +6,125 @@ import { untrtSwgRainCol3, untrtSwgRainCol4, untrtSwgRainCol5 } from '../common/ import { VerticalTable } from '@/components/ui/verticalTable'; import { getData } from "@/components/lib/api"; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; +import { Label } from '@radix-ui/react-label'; const inputColumn = [ { id: "prrtymngYn", title: "중점여부", type: "select", options: ["우수토실", "중점우수토실"] }, { id: "swtrmtZoneCd", title: "처리구역", type: "input" }, ]; -const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { +const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { featureData: any, isMainSsc?: boolean, - rainMeasureData?: any, + chmbrXcnts?: string, + chmbrYdnts?: string, onSaveClick?: () => void }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [isDataLoaded, setIsDataLoaded] = useState(false); const [formData, setFormData] = useState({}); - const [mntrngsData, setMntrngsData] = useState([]); // 수량/수질 측정 정보 + const [rainMeasureData, setRainMeasureData] = useState({}); // 강우, 측정 정보 + const [mntrngsData, setMntrngsData] = useState([]); // 수량/수질측정 정보 + const [totalCount, setTotalCount] = useState(0); + const [totalDetailCount, setTotalDetailCount] = useState(0); const [selectedRowIndex, setSelectedRowIndex] = useState(-1); + const [curPage, setCurPage] = useState(1); + const [curDetailPage, setCurDetailPage] = useState(1); + const [isResetPage, setIsResetPage] = useState(true); + const [isResetDetailPage, setIsResetDetailPage] = useState(true); + const [isResetSelection, setIsResetSelection] = useState(false); - // console.log('featureData data received:', featureData); // 추가 데이터 확인용 - // console.log('rainMeasureData received:', rainMeasureData); // 추가 데이터 확인용 + console.log('featureData', featureData); + + // 강우, 측정 정보 API 호출 함수 + const fetchRainMeasureData = async (chmbrXcnts: string, chmbrYdnts: string) => { + setIsResetDetailPage(true); + setIsResetSelection(true); + if (!chmbrXcnts || !chmbrYdnts) { + console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts }); + return []; + } + + try { + const queryParams = new URLSearchParams({ + chmbrXcnts: chmbrXcnts, + chmbrYdnts: chmbrYdnts, + page: `${curPage - 1}`, + size: '3', + }); + + console.log('queryParams', queryParams.toString()); + console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); + + const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); + + console.log('추가 데이터 API 호출 성공:', result); + + const processedDataArray: any[] = result.content.map((data: any) => { + // 🎯 formatDisplayData로 기본 포맷팅 적용 + const formattedItem = formatDisplayData(data); + + // 🎯 추가 데이터 조합 + return { + ...formattedItem, + flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`, + }; + }); + setRainMeasureData(processedDataArray); + setTotalCount(result?.totalElements || 0); + } catch (error) { + console.error('추가 데이터 로딩 실패:', error); + return []; + } + }; + + // 수량/수질측정 정보 API 호출 함수 + const fetchMntrngsData = async (adstDstrCd: string, manageNo: string) => { + setIsResetDetailPage(true); + if (!adstDstrCd || !manageNo) { + console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo }); + return []; + } + + try { + const queryParams = new URLSearchParams({ + page: `${curDetailPage - 1}`, + size: '3', + }); + + console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); + + const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); + + console.log('SSC 추가 데이터 API 호출 성공:', result); + + // 각 아이템 전처리 + const processedDataArray = result.content.map((data: any) => { + // 기본 포맷팅 적용 + const formattedItem = formatDisplayData(data); + return { + ...formattedItem, + }; + }); + + console.log('processedDataArray', processedDataArray); + + setMntrngsData(processedDataArray); + setTotalDetailCount(result?.totalElements || 0); + } catch (error) { + console.error('추가 데이터 로딩 실패:', error); + return []; + } + }; + + + // 🎯 추가 데이터는 별도 useEffect로 비동기 로딩 + useEffect(() => { + // 강우/측정 정보 데이터 호출 + if (isMainSsc && chmbrXcnts && chmbrYdnts) { + fetchRainMeasureData(chmbrXcnts, chmbrYdnts) + } + }, [chmbrXcnts, chmbrYdnts]); const openModal = () => { setIsModalOpen(true); @@ -46,6 +145,19 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { } }, [featureData]); + // featureData가 변경될 때 formData 초기화 + useEffect(() => { + // showTable이 true일 때만 fetch (최초 렌더 방지) + setIsResetDetailPage(false); + setSelectedRowIndex(-1); + if (chmbrXcnts && chmbrYdnts) fetchRainMeasureData(chmbrXcnts, chmbrYdnts); + }, [curPage]); + + // 페이지 변경 시마다 데이터 fetch + useEffect(() => { + if (chmbrXcnts && chmbrYdnts && selectedRowIndex !== -1) fetchMntrngsData(rainMeasureData[selectedRowIndex].adstDstrCd, rainMeasureData[selectedRowIndex].manageNo); + }, [curDetailPage]); + // 폼 데이터가 변경될 때 호출될 핸들러 const handleFormDataChange = (newFormData: any) => { setFormData(newFormData); @@ -62,120 +174,70 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { // 테이블 셀 클릭 핸들러 추가 const handleCellClick = async (index: number | undefined, headId: string) => { if (index !== undefined && rainMeasureData) { - const data = await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo); - - setMntrngsData(data); + await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo); setSelectedRowIndex(index); } }; - // 🎯 추가 데이터 API 호출 함수 (독립적으로 실행) - const fetchMntrngsData = useCallback(async (adstDstrCd: string, manageNo: string) => { - if (!adstDstrCd || !manageNo) { - console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo }); - return []; - } - - try { - const queryParams = new URLSearchParams({ - pageable: `{}`, - }); - - console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain?${queryParams.toString()}`); - - const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); - - console.log('SSC 추가 데이터 API 호출 성공:', result); - - // 🎯 result가 페이지네이션 객체인 경우 content 배열 추출 - let dataArray: any[] = []; - if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) { - dataArray = result.content; - } else if (Array.isArray(result)) { - dataArray = result; - } else { - console.warn('예상치 못한 API 응답 형식:', result); - return []; - } - - // 🎯 각 아이템을 전처리하여 반환 - const processedDataArray = dataArray.map((data: any) => { - // 🎯 formatDisplayData로 기본 포맷팅 적용 - const formattedItem = formatDisplayData(data); - - // 🎯 추가 데이터 조합 - return { - ...formattedItem, - }; - }); - - console.log('processedDataArray', processedDataArray); - - return processedDataArray; - } catch (error) { - console.error('추가 데이터 로딩 실패:', error); - return []; - } - }, []); - const renderModalBody = () => (
- {/* 강우정보 테이블 */} - +
+ + {/* 강우정보 테이블 */} + - {/* 측정정보 테이블 */} - - {/* 커스텀 헤더 */} - - 수량 - 수질 - - - 측정시작일시 - 측정종료일시 - 측정간격(분) - 측정기기설치위치 - 유량계 종류 및 모델명 - 채수시작일시 - 채수종료일시 - 채수횟수 - - + {/* 측정정보 테이블 */} + + {/* 커스텀 헤더 */} + + 수량 + 수질 + + + 측정시작일시 + 측정종료일시 + 측정간격(분) + 측정기기설치위치 + 유량계 종류 및 모델명 + 채수시작일시 + 채수종료일시 + 채수횟수 + + +
{/* 수량/수질측정 정보 테이블 */}
) diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx index 50d6d5606..6d182072b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx @@ -1,13 +1,15 @@ +'use client' +import React, { useState,useEffect } from 'react' import Modal from '@/components/ui/modal'; import { VerticalTable } from '@/components/ui/verticalTable'; import { UiTable } from '@common/business_components/ui'; -import { Button, TableCell } from '@common/components/ui'; -import React, { useState } from 'react' +import { Button } from '@common/components/ui'; +import { useDcTree, flattenFolders, getFileDownload, addFileName, type FlattenedFolder} from '../../searchModal/searchModalReportApi'; // 하수처리시설 const headList = [ { id: "title", title: "하수도정비 기본계획보고서" }, - { id: "cad", title: "하수도대장 평면도" },] +]; const modalHeadList = [ { id: "no", title: "번호" }, { id: "year", title: "승인연도" }, @@ -16,9 +18,10 @@ const modalHeadList = [ { id: "date", title: "입력시간" }, ] -const SwtrmtFclt = () => { +const SwtrmtFclt = ({ gisObject }: { gisObject: any }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [modalTitle, setModalTitle] = useState(''); + const [dcDataOne, setDcDataOne] = useState({}); const openModal = (title: string) => { setIsModalOpen(true); setModalTitle(title) @@ -26,22 +29,59 @@ const SwtrmtFclt = () => { const closeModal = () => { setIsModalOpen(false); }; + const ctpvNm = gisObject?.ctpv_nm; + //const reportOriginData = useDcTree(ctpvNm); + const reportOriginData = useDcTree("강원특별자치도"); + useEffect(() => { + const fetch = async () => { + + if (!reportOriginData || reportOriginData.length === 0) { + return; + } + const reportData = flattenFolders(reportOriginData); + let filterReportData = [...reportData]; + if (gisObject && gisObject.sgg_nm !== undefined && gisObject.sgg_nm !== "") { // 시군구검색 + //const sggNm = gisObject?.sgg_nm; + const sggNm = "강릉시"; + filterReportData = filterReportData.filter(item => item.sigungu === sggNm); + } + addFileName(filterReportData).then(data => { + filterReportData = data.filter(item => item.filePath !== undefined && item.filePath !== ""); + if (filterReportData !== undefined && filterReportData.length > 0) { + setDcDataOne(filterReportData[0]); + } + }); + console.log("filterReportData", filterReportData); + }; + fetch(); + }, [reportOriginData]); + + // 하수도정비기본계획 filedownload 핸들러 + const handleFileDownloadClick = () => { + if (dcDataOne && dcDataOne !== undefined && dcDataOne.fileSn !== undefined && dcDataOne.fileSn !== "" + && dcDataOne.filePath !== undefined && dcDataOne.filePath !== "" && dcDataOne.fileName !== undefined && dcDataOne.fileName !== "" ) { + console.log("dcDataOne", dcDataOne); + getFileDownload(String(dcDataOne.fileSn), String(dcDataOne.fileName), String(dcDataOne.filePath)); + } + }; + + //cad: , return (
- openModal('하수도정비 기본계획보고서')}>보고서, - cad: , + handleFileDownloadClick()}>다운로드 }} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void { throw new Error("Function not implemented."); }}> - + {/*
-
-
+
*/}
) } diff --git a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx similarity index 81% rename from apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx rename to apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx index e9a4f9e9a..f50595c10 100644 --- a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -5,13 +5,13 @@ import { VerticalTable } from "@/components/ui/verticalTable"; import { useGisContext } from "@common/business_components/ui/map/Gis"; import { Button } from "@common/components/ui"; import { useEffect, useRef, useState, useCallback, useMemo } from "react"; -import { getLayerContentComponent } from "./components/common"; -import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "./components/common/table"; -import InfilChart from './components/popupComponent/chart/infilChart'; -import RdiiChart from './components/popupComponent/chart/rdiiChart'; -import { useInfilChartModalStore, useRdiiChartModalStore } from "./components/popupComponent/chartModalStore"; +import { getLayerContentComponent } from "../common"; +import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table"; +import InfilChart from './chart/infilChart'; +import RdiiChart from './chart/rdiiChart'; +import { useInfilChartModalStore, useRdiiChartModalStore } from "./chartModalStore"; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; -import { codeMapper } from "./components/common/codeMapper"; +import { codeMapper } from "../common/codeMapper"; import { searchUpperPpls } from '@common/business_components/ui/map/Gis'; import LoadingOverlay from '@/components/ui/LoadingBar'; @@ -21,7 +21,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const [layerNm, setLayerNm] = useState(""); // 레이어 이름 상태 const [layerSpecificNm, setLayerSpecificNm] = useState(""); // 레이어 이름 상태 const [tableData, setTableData] = useState(null); // 피처 데이터 - const [rainMeasureData, setRainMeasureData] = useState([]); // 강우/측정 측정 정보 const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태 const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태 const [buttonChk, setButtonChk] = useState(true) @@ -33,18 +32,17 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, // 🎯 InfilChart 및 RdiiChart 관련 상태 추가 const { setIsOpen: setIsModalInfil } = useInfilChartModalStore(); const { setIsOpen: setIsModalRdii } = useRdiiChartModalStore(); - const [curPage, setCurPage] = useState(1); // 현재 페이지 상태 const [isClicked, setIsClicked] = useState(true); // 클릭 여부 상태 const [isLoading, setIsLoading] = useState(false); // 로딩 상태 + const [chmbrXcnts, setChmbrXcnts] = useState(''); // 로딩 상태 + const [chmbrYdnts, setChmbrYdnts] = useState(''); // 로딩 상태 // 🎯 관로관리번호 추출 const pplMngNo = featureData?.values_?.fclt_mng_no || ""; - // const featureId = featureData.getId(); // 피처의 id 가져오기 const featureId = typeof featureData.getId === 'function' ? featureData.getId() : featureData.fid; - console.log('featureId', featureId); // 🎯 데이터 전처리 메모이제이션 const processedData = useMemo(() => { @@ -263,18 +261,9 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, data: { ...processedFormattedData } })); - // 강우/측정 정보 데이터 호출 - if (tableData?.chmbr_xcnts && tableData?.chmbr_ydnts) { - fetchRainMeasureData(tableData.chmbr_xcnts, tableData.chmbr_ydnts) - .then(response => { - console.log('additionalResponse', response); - setRainMeasureData(response || []); - }) - .catch(error => { - console.error('추가 데이터 로딩 실패:', error); - setRainMeasureData([]); - }); - } + setChmbrXcnts(data.chmbr_xcnts); + setChmbrYdnts(data.chmbr_ydnts); + break; default: break; @@ -317,76 +306,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, fetchData(); }, [featureId, processedData]); - // 🎯 추가 데이터 API 호출 함수 - const fetchRainMeasureData = useCallback(async (chmbrXcnts: string, chmbrYdnts: string) => { - if (!chmbrXcnts || !chmbrYdnts) { - console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts }); - return []; - } - - try { - const queryParams = new URLSearchParams({ - chmbrXcnts: chmbrXcnts, - chmbrYdnts: chmbrYdnts, - pageable: `{}`, - }); - - console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); - - const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); - - console.log('추가 데이터 API 호출 성공:', result); - - // 🎯 result가 페이지네이션 객체인 경우 content 배열 추출 - let dataArray: any[] = []; - if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) { - dataArray = result.content; - } else if (Array.isArray(result)) { - dataArray = result; - } else { - console.warn('예상치 못한 API 응답 형식:', result); - return []; - } - - // 🎯 각 아이템을 전처리하여 반환 - const processedDataArray = dataArray.map((data: any) => { - // 🎯 formatDisplayData로 기본 포맷팅 적용 - const formattedItem = formatDisplayData(data); - - // 🎯 추가 데이터 조합 - return { - ...formattedItem, - flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`, - }; - }); - - return processedDataArray; - } catch (error) { - console.error('추가 데이터 로딩 실패:', error); - return []; - } - }, []); - - // 🎯 추가 데이터는 별도 useEffect로 비동기 로딩 - useEffect(() => { - if (layerNm === '중점우수토실' && featureData?.values_) { - const data = featureData.values_; - - if (data?.chmbr_xcnts && data?.chmbr_ydnts) { - // 🎯 추가 데이터를 비동기로 로딩 (UI 블로킹 없음) - fetchRainMeasureData(data.chmbr_xcnts, data.chmbr_ydnts) - .then(response => { - console.log('추가 데이터 로딩 완료:', response); - setRainMeasureData(response || []); - }) - .catch(error => { - console.error('추가 데이터 최종 로딩 실패:', error); - setRainMeasureData([]); - }); - } - } - }, [layerNm, featureData?.values_, fetchRainMeasureData]); - // 저장 시 모달 띄우기 const handleSaveClick = () => { setIsModalOpen(true); // 모달 열기 @@ -434,7 +353,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, else if (layerNm === '하수펌프장') apiEndpoint = 'v1/fcltpump'; else if (layerNm === '하수맨홀') apiEndpoint = 'v1/fcltmhl'; - bodyData['fid'] = featureId.toString().split('.').pop() || null; + bodyData['fcltMngNo'] = featureData.values_.fclt_mng_no.toString() || null; // 빈 키 제거 if (bodyData[""] !== undefined) delete bodyData[""]; @@ -484,7 +403,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
- {/* 🎯 하수관로일 때만 Infil/RDII 버튼 표시 */} + {/* 하수관로일 때만 Infil/RDII 버튼 표시 */} {layerNm === '하수관로' && ( <>
- {/* 🎯 InfilChart 및 RdiiChart 컴포넌트 추가 */}
diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 2dbbcdd1a..b45b41936 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -5,7 +5,9 @@ import { getData } from "../../components/lib/api"; import { useCallback, useEffect, useRef, useState } from "react"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; -import FeatureClickEventPopup from "./featureClickEventPopup"; +import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; +import { SearchBar } from "./searchBar/searchBar"; +import { WaterDepth } from "./waterDepth/waterDepth"; // 타입 정의 추가 export type LyrStngProps = { @@ -39,6 +41,11 @@ const Map = () => { const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + + useEffect(() => { + fetchData(); + }, []); + const fetchData = async () => { try { const data = await getData("v1/map/lyrStngs/SN_NETWORK"); @@ -48,9 +55,6 @@ const Map = () => { } }; - useEffect(() => { - fetchData(); - }, []); // SearchModal, GisContent 등에서 호출 const onFeatureSelect = (data: any) => { let map = gisMaps.current; @@ -113,6 +117,8 @@ const Map = () => { setIsOpen(true); setIsSearchModalOpen(false); }; + + return (
{ isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={undefined} + searchBar={SearchBar} + // waterDepth={WaterDepth} + waterDepth={undefined} />
); diff --git a/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx b/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..43ceb14de --- /dev/null +++ b/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { useState } from "react"; +import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox"; + + +export function SearchBar() { + const [ctpv, setCtpv] = useState(0); + const [sgg, setSgg] = useState(0); + const [sggName, setSggName] = useState(''); + const [selectedSwagePrcsFclt, setSelectedSwagePrcsFclt] = useState('0'); + + return ( + <> +
+ { + setCtpv(sidoId); + setSgg(0); + }} + onSigunguChange={setSgg} + setSggName={setSggName} + selectedSwagePrcsFclt={selectedSwagePrcsFclt} + onSwagePrcsFcltChange={setSelectedSwagePrcsFclt} + /> +
+ + ); +} diff --git a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx index 310528aed..018f67883 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import { MS_SWAGE_MHL_USE_CODES, MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from '../components/common/table'; -import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { SelectBox } from '@common/business_components/ui'; import { Button, Input, Label } from '@common/components'; @@ -21,7 +19,6 @@ export const BASIC_TYPES = [ ]; interface BasicTabProps { - open?: boolean; basicType: string; city: number; sgg: number; @@ -33,13 +30,11 @@ interface BasicTabProps { var2: string; yearFilter: { start: string; end: string; }; handleSearch: () => void; - // onOpenChange?: (open: boolean) => void; - // onFeatureSelect?: (row: any) => void; - // setTableData?: (data: any[]) => void; - // setTableCount?: (count: number) => void; setBasicType: (type: string) => void; setCity: (city: number) => void; + setCtpvName: (name: string) => void; setSgg: (sgg: number) => void; + setSggName: (name: string) => void; setYearFilter: (filter: { start: string; end: string }) => void; setStart1: (value: string) => void; setStart2: (value: string) => void; @@ -50,7 +45,6 @@ interface BasicTabProps { } const BasicTab: React.FC = ({ - // open, basicType, city, sgg, @@ -62,13 +56,11 @@ const BasicTab: React.FC = ({ var2, yearFilter, handleSearch, - // onOpenChange, - // onFeatureSelect, - // setTableData, - // setTableCount, setBasicType, setCity, + setCtpvName, setSgg, + setSggName, setYearFilter, setStart1, setStart2, @@ -78,7 +70,7 @@ const BasicTab: React.FC = ({ setVar2, }) => { return ( -
+
{/* 구분 */}
@@ -124,12 +116,18 @@ const BasicTab: React.FC = ({ start2={start2} end1={end1} end2={end2} + city={city} + sgg={sgg} yearFilter={yearFilter} setVar1={setVar1} setStart1={setStart1} setStart2={setStart2} setEnd1={setEnd1} setEnd2={setEnd2} + setCity={setCity} + setCtpvName={setCtpvName} + setSgg={setSgg} + setSggName={setSggName} setYearFilter={setYearFilter} handleSearch={handleSearch} /> diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index 5f24c65fa..c52a4ddab 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -1,40 +1,15 @@ +import React from 'react'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { SelectBox } from '@common/business_components/ui'; import { Button, Input, Label } from '@common/components'; -import React from 'react'; -//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi'; -// import { useBizByFcltStore } from "../../../store/StoreProvider"; +import useStore from '../../../store/useStore'; // 운영정보 '구분' 선택 목록 -// const WORK_TYPES = [ -// { val: "maintenance", text: "보수 및 갱신" }, -// { val: "cleaning", text: "청소 및 준설" }, -// { val: "inspection", text: "점검 및 진단" }, -// ]; export const WORK_TYPES = [ { val: "['200', '201', '204']", text: "보수 및 갱신" }, { val: "['100']", text: "점검 및 진단" }, { val: "['203']", text: "청소 및 준설" }, ]; -// const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD); -export const BIZ_SE_CD = [ - { lwpoCd: "100", lwpoCdNm: "관로기술진단" }, - { lwpoCd: "200", lwpoCdNm: "개대체" }, - { lwpoCd: "201", lwpoCdNm: "유지보수" }, - { lwpoCd: "203", lwpoCdNm: "청소, 준설" }, - { lwpoCd: "204", lwpoCdNm: "단순보수" }, -]; -// const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD); -export const BIZ_TY_CD = [ - { lwpoCd: "0", lwpoCdNm: "전체" }, - { lwpoCd: "SV", lwpoCdNm: "용역" }, - { lwpoCd: "SF", lwpoCdNm: "자체" }, - { lwpoCd: "SC", lwpoCdNm: "공사" }, -]; -export const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({ - val: item.lwpoCd, - text: item.lwpoCdNm, -})); // 운영정보 - 사업 현황 export const operationTableHeadlist = [ { id: 'rowNumber', title: '일련번호' }, @@ -80,15 +55,11 @@ export const operationInspectionTableHeadlist = [ ]; interface OperationTabProps { - open: boolean; workType: string; var1: string; var2: string; yearFilter: { start: string; end: string; }; handleSearch: () => void; - // onOpenChange: (open: boolean) => void; - // setTableData: (data: any[]) => void; - // setTableCount: (count: number) => void; setWorkType: (type: string) => void; setYearFilter: (filter: { start: string; end: string }) => void; setVar1: (value: string) => void; @@ -96,26 +67,30 @@ interface OperationTabProps { } const OperationTab: React.FC = ({ - open, workType, var1, var2, yearFilter, handleSearch, - // onOpenChange, - // setTableData, - // setTableCount, setWorkType, setVar1, setVar2, setYearFilter, }) => { + + const { BIZ_TY_CD } = useStore(); + + const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({ + val: item.lwpoCd, + text: item.lwpoCdNm, + })); + return ( -
+
({ diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx index 53dd6d0e0..5ca3f7059 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx @@ -165,7 +165,7 @@ const ReportTab: React.FC = ({ open }) => { return ( <> -
+
= ({ const [isResetSelection, setIsResetSelection] = useState(false); const [isResetDetailSelection, setIsResetDetailSelection] = useState(false); const [detailBizId, setDetailBizId] = useState(0); - const [city, setCity] = useState(0); - const [sgg, setSgg] = useState(0); - const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report - const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report - const [gubun, setGubun] = useState(""); // 하수정비기본계획 report - const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report + const [city, setCity] = useState(0); + const [ctpvName, setCtpvName] = useState(''); + const [sgg, setSgg] = useState(0); + const [sggName, setSggName] = useState(''); const [start1, setStart1] = useState(""); const [start2, setStart2] = useState(""); const [end1, setEnd1] = useState(""); @@ -79,7 +75,8 @@ const SearchModal: React.FC = ({ const [var4, setVar4] = useState(""); // 시작, 종료 const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - const [filteredSiguns, setFilteredSiguns] = useState([{ val: '0', text: '전체', sido: '0' }]); + + const { BIZ_TY_CD } = useStore(); // 오늘 날짜 구하기 @@ -341,13 +338,17 @@ const SearchModal: React.FC = ({ cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`); else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`); else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`); - // 처리구역 면적 - if (start2 && end2) - cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); - else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); - else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); - // 하수처리시설명 - if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); + + // 시도 + if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`); + if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`); + // // 처리구역 면적 + // if (start2 && end2) + // cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); + // else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); + // else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); + // // 하수처리시설명 + // if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); return cqlFilters; }; // 하수처리구역용 CQL 필터 생성 @@ -561,7 +562,6 @@ const SearchModal: React.FC = ({ {/* 기본정보 탭 */} {activeTab === 'basic' && ( = ({ handleSearch={handleSearch} setBasicType={setBasicType} setCity={setCity} + setCtpvName={setCtpvName} setSgg={setSgg} + setSggName={setSggName} setYearFilter={setYearFilter} setStart1={setStart1} setStart2={setStart2} @@ -588,7 +590,6 @@ const SearchModal: React.FC = ({ {/* 운영정보 탭 */} {activeTab === 'operation' && ( traverse(root, {}, 0)); + if (data && data.length > 0 && data[0].clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리 + data.forEach((root) => traverse(root, {}, 0)); + } else { + data.forEach((root) => traverse(root, {}, 1)); + } return result; } @@ -96,13 +100,15 @@ export const useDcTree = (clsfSystmNm:string) => { if (result) { setData(result); } + console.log("useDcTree호출"); + console.log("clsfSystmNm:", clsfSystmNm, "data:", result); }; fetch(); - console.log("useDcTree호출"); }, [clsfSystmNm]); return data; }; + export const getDcFile = async (clsfSystmSn:string) => { const queryString = 'clsfSystmSn='+clsfSystmSn; const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`); @@ -119,7 +125,7 @@ export const addFileName = async (data:FlattenedFolder[]) => { }) ); return fileReportData; -} +}; export const getDcFileprcs = async (fileSn:string) => { const queryString = 'fileSn='+fileSn; const result = await getDcData(`/api/v1/dc/fileprcs${queryString ? `?${queryString}` : ''}`); diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx index 6f76554d9..8779f0fa5 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx @@ -1,8 +1,10 @@ // 하수처리시설 +import React from 'react'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { Button, Input, Label } from '@common/components'; -import React from 'react'; +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; + interface SwagePrcsFcltProps { var1: string; @@ -10,36 +12,53 @@ interface SwagePrcsFcltProps { start2: string; end1: string; end2: string; + city: number; + sgg: number; yearFilter: { start: string; end: string }; setVar1: (value: string) => void; setStart1: (value: string) => void; setStart2: (value: string) => void; setEnd1: (value: string) => void; setEnd2: (value: string) => void; + setCity: (value: number) => void; + setCtpvName: (name: string) => void; + setSgg: (value: number) => void; + setSggName: (name: string) => void; setYearFilter: (filter: { start: string; end: string }) => void; handleSearch: () => void; } const SwagePrcsFclt: React.FC = ({ - var1, start1, - start2, end1, - end2, + city, + sgg, yearFilter, - setVar1, setStart1, - setStart2, setEnd1, - setEnd2, + setCity, + setCtpvName, + setSgg, + setSggName, setYearFilter, handleSearch, }) => { return (
- {/* 가동개시일자 */}
+ { + setCity(sidoId); + setSgg(0); + }} + onSigunguChange={setSgg} + setCtpvName={setCtpvName} + setSggName={setSggName} + /> + {/* 가동개시일자 */} = ({ } }} /> - {/* 처리구역 면적 */} - - { - // 숫자만 입력 허용 - const val = e.target.value.replace(/[^0-9]/g, ""); - setStart2(val); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> - ~ - { - // 숫자만 입력 허용 - const val = e.target.value.replace(/[^0-9]/g, ""); - setEnd2(val); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> - {/* 하수처리시설명 */} - - { - setVar1(e.target.value); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> {/* 조회 버튼 */}
diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx index a9065fb35..8cb304d2a 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx @@ -1,6 +1,6 @@ // 하수처리구역 -import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { Button, Input, Label } from '@common/components'; import React from 'react'; @@ -31,30 +31,15 @@ const swagePrcsZone: React.FC = ({ return (
- { + onSidoChange={(sidoId: number) => { setCity(sidoId); setSgg(0); }} onSigunguChange={setSgg} /> - - { - setVar1(e.target.value); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> = ({ } }} /> + + { + setVar1(e.target.value); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); // Enter 키 기본 동작 방지 + handleSearch(); + } + }} + /> {/* 조회 버튼 */}
diff --git a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx new file mode 100644 index 000000000..39e375a4b --- /dev/null +++ b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx @@ -0,0 +1,62 @@ +"use client"; + +import React, { useRef, useEffect, useState } from "react"; +import { Label } from "@common/components/ui/label"; + +export function WaterDepth({ onClose }: { onClose?: () => void }) { + const [isPlaying, setIsPlaying] = useState(false); + + return ( + <> +
+
+ + +
+
+
+
침 수 심
+
+ + + + + + +
+
+
+
+ + ); +} + +function LegendItem({ color, label }: { color: string; label: string }) { + return ( +
+ + {label} +
+ ); +} \ No newline at end of file diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index d825b34fa..5d5443db2 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -1,12 +1,9 @@ 'use client' -import { getData } from '@/components/lib/api' -import ContentLayout from '@/components/ui/contentLayout' -import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm' -import { DatePicker, DateMonthPicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui' +import { DateMonthPicker, Form, SelectBox } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { Button, Label } from '@common/components/ui' -import { useEffect, useState, type ReactNode } from 'react' -import LoadingOverlay from '@/components/ui/LoadingBar' +import { useState, type ReactNode } from 'react' +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox' type TitleName = { mainTitle: string @@ -40,37 +37,26 @@ type BaseLayout = { firstDivBtn?: boolean selectBox?: SelectBox showDatePicker?: boolean; - enoCd?: string | number - setEnoCd?: (enoCd: string) => void - ctpvCd?: string | number - setCtpvCd?: (ctpvCd: string) => void + ctpvCd: number; + sggCd: number; + sggName?: string; sigungu?: ApiSelectOption - setSigungu?: (sigungu: ApiSelectOption) => void - selectedSwageTreatmentPlant?: string | number; - onSwageTreatmentPlantChange?: (value: string | number) => void; - selectedSwageProcessingZone?: string | number; - onSwageProcessingZoneChange?: (value: string | number) => void; + selectedSwagePrcsFclt?: string | number; + onSwagePrcsFcltChange?: (value: string | number) => void; aplcnYmFrom?: string setAplcnYmFrom?: (aplcnYmFrom: string) => void; aplcnYmTo?: string setAplcnYmTo?: (aplcnYmTo: string) => void; - isLoading?: boolean; - // Excel 다운로드 관련 props 추가 + setCtpvCd: (ctpv: number) => void; + setSggCd: (sgg: number) => void; + setSggName?: (sggName: string) => void; + setIsLoading?: (isLoading: boolean) => void; onExcelDownload?: () => void; onDeleteFile?: () => void; onAnalysis?: () => void; selectedRowData?: any; } -// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ? -/** - * - * @param children - * @param titleName : 메인 타이틀, 첫번째 요소 타이틀, 두번째 요소 타이틀 - * @param form : zod 스키마 - * @param onSubmitEvent : 폼 이벤트 처리 - * @param graphBtn : 하단에 그래프 버튼 유무 처리 - * @returns - */ + const BaseLayout = ({ children, titleName, @@ -80,113 +66,25 @@ const BaseLayout = ({ analysisBtn, selectBox, showDatePicker = false, - enoCd, - setEnoCd, ctpvCd, - setCtpvCd, + sggCd, + sggName, sigungu, - setSigungu, - selectedSwageTreatmentPlant, - onSwageTreatmentPlantChange, - selectedSwageProcessingZone, - onSwageProcessingZoneChange, + selectedSwagePrcsFclt, + onSwagePrcsFcltChange, aplcnYmFrom, setAplcnYmFrom, aplcnYmTo, setAplcnYmTo, - isLoading, - // Excel 다운로드 관련 props 추가 + setCtpvCd, + setSggCd, + setSggName, + setIsLoading, onExcelDownload, onDeleteFile, onAnalysis, - selectedRowData }: BaseLayout) => { - const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; - - const [processingZoneOptions, setProcessingZoneOptions] = useState([]); - const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState([]); - const [isLoadingAreaData, setIsLoadingAreaData] = useState(isLoading || false); - const [atflId, setAtflId] = useState('') - const [fileNm, setFileNm] = useState('') - const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('') - - useEffect(() => { - const fetchData = async () => { - if (!sigungu || sigungu.val === '0' || !sigungu.val) { - console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val); - setProcessingZoneOptions([defaultOption]); - setTreatmentFacilityOptions([defaultOption]); - setIsLoadingAreaData(false); - return; - } - - setIsLoadingAreaData(true); - try { - // const queryParamsZone = new URLSearchParams({ - // lyrTypeNm: 'tb_ms_swage_prcs_zone', - // page: '0', - // size: '100000', - // sort: 'string', - // }); - // const cqlFiltersZone: string[] = []; - // if (sigungu.val && sigungu.val !== '0') { - // // console.log("시군구 코드", sigungu.val); - // cqlFiltersZone.push(`sgg_cd='${sigungu.val}'`); - // } - // if (cqlFiltersZone.length > 0) { - // queryParamsZone.append('cqlFiter', cqlFiltersZone.join(' and ')); - // } - - const queryParamsFacility = new URLSearchParams({ - lyrTypeNm: 'tb_il_swage_prcs_fclt', - page: '0', - size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 - sort: 'string', - }); - const cqlFiltersFacility: string[] = []; - if (sigungu.text && sigungu.val !== '0') { - cqlFiltersFacility.push(`sgg_nm ILIKE '%${sigungu.text}%'`); - } - if (cqlFiltersFacility.length > 0) { - queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); // API 명세에 따라 cqlFilter 또는 cqlFiter - } - - // console.log("BaseLayout API URL for Zones:", `v1/fclts?${queryParamsZone.toString()}`); - console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); - - // const [zoneRes, facilityRes] = await Promise.all([ - // getData(`v1/fclts?${queryParamsZone.toString()}`), - // getData(`v1/fclts?${queryParamsFacility.toString()}`) - // ]); - const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); - // const zones = (zoneRes.data || []).map((item: any) => ({ - // text: item.swtrmtZoneNm || item.name || `구역 ${item.id || item.gid || item.objectid}`, - // val: item.fid || item.id || item.gid || item.objectid - // })); - // setProcessingZoneOptions([defaultOption, ...zones]); - // console.log("BaseLayout Fetched Processing Zones:", [defaultOption, ...zones]); - - - const facilities = (facilityRes.data || []).map((item: any) => ({ - text: item.swtrmtFcltNm || item.name || `시설 ${item.id || item.gid || item.objectid}`, - val: item.fid || item.id || item.gid || item.objectid - })); - setTreatmentFacilityOptions([defaultOption, ...facilities]); - console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]); - - } catch (error) { - console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); - setProcessingZoneOptions([defaultOption]); - setTreatmentFacilityOptions([defaultOption]); - } finally { - setIsLoadingAreaData(false); - } - }; - - fetchData(); - }, [sigungu]); // sigungu 상태가 변경될 때마다 실행 - // 파일 다운로드 const isChckFile = () => { // Excel 다운로드 함수가 있으면 Excel 다운로드 실행 @@ -207,7 +105,6 @@ const BaseLayout = ({ return (
- {/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */} {selectBox?.title && ( @@ -215,10 +112,10 @@ const BaseLayout = ({

{selectBox.title}

)} -
+
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링 <> - + { // aplcnYm이 없으면 현재 연월을 기본값으로 설정 @@ -253,24 +150,20 @@ const BaseLayout = ({ /> )} - setEnoCd?.(val)} - setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)} - setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달 - > - - onSwageTreatmentPlantChange?.(val)} - selectContentClass="z-[101]" - /> - {/* onSwageProcessingZoneChange?.(val)} selectContentClass="z-[101]" /> */} - + { + setCtpvCd(sidoId); + setSggCd(0); + }} + onSigunguChange={setSggCd} + setSggName={setSggName} + selectedSwagePrcsFclt={selectedSwagePrcsFclt} + onSwagePrcsFcltChange={onSwagePrcsFcltChange} + setIsLoading={setIsLoading} + /> {selectBox?.searchChk && (
diff --git a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx index 3adf8c233..7bb508e50 100644 --- a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx @@ -47,15 +47,14 @@ interface ApiSelectOption { } const IrSetting = () => { - const [input1, setInput1] = useState(''); + const [input1, setInput1] = useState('0.8'); // 오수전환율 초기값 const [input2, setInput2] = useState(''); - const [select1, setSelect1] = useState('0'); // 환경청코드 - const [select2, setSelect2] = useState('0'); // 시도코드 - const [select3, setSelect3] = useState({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경 - const [select4, setSelect4] = useState('0'); // 하수처리장코드 - const [select5, setSelect5] = useState('0'); // 하수처리구역코드 + const [select1, setSelect1] = useState('0'); // 하수처리장코드 const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태 const [isLoadingResult, setIsLoadingResult] = useState(false); // 로딩 상태 + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 const { data: userInfo } = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.USERS], @@ -115,11 +114,7 @@ const IrSetting = () => { setSelectedDate(`${year}${month}`); setInput1(''); // 입력 필드 초기화 setInput2(''); // 입력 필드 초기화 - setSelect1('0'); // 선택 필드 초기화 - setSelect2('0'); - setSelect3({ val: '0', text: '전체' }); - setSelect4(0); - setSelect5(0); + setSelect1(0); setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경 }; @@ -164,7 +159,7 @@ const IrSetting = () => { setIsLoadingResult(true); // 로딩 상태 시작 // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) - if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, { + if (!validateBasicSelectionsWithAlert(select1, { requireEnv: true, requireSido: true, requireSigungu: true, @@ -299,15 +294,11 @@ const IrSetting = () => { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { - enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값) - ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값) - sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값) - swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0) - ? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 + ctpvCd: (ctpvCd ? String(ctpvCd) : null), + sggCd: (sggCd ? String(sggCd) : null), // 시군구코드 (선택값) + swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0) + ? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 : null, // 하수처리장코드 (선택값) - swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0) - ? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 - : null, // 하수처리구역코드 (선택값) aplcnYm: selectedDate, // 적용연월 drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환) anlsInfo: input2, // 분석정보 @@ -346,17 +337,15 @@ const IrSetting = () => { graphBtn={false} analysisBtn={'침입수분석 실행'} selectBox={{ title: '분석 대상지역 선택' }} - enoCd={select1} - setEnoCd={setSelect1} - ctpvCd={select2} - setCtpvCd={setSelect2} - sigungu={select3} - setSigungu={setSelect3} - selectedSwageTreatmentPlant={select4} - onSwageTreatmentPlantChange={setSelect4} - selectedSwageProcessingZone={select5} - onSwageProcessingZoneChange={setSelect5} - isLoading={isLoadingResult} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} + selectedSwagePrcsFclt={select1} + onSwagePrcsFcltChange={setSelect1} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} + setIsLoading={setIsLoadingResult} onAnalysis={handleAnalysisClick} > {[
@@ -387,10 +376,7 @@ const IrSetting = () => { accept={".xls, .xlsx, .xlsm, .xltx"} fileIdx={2} /> - + {/* 연월 선택 라이브러리로 수정 필요 */} { startYear={1900} // 시작 연도 설정 /> - + { @@ -434,10 +417,7 @@ const IrSetting = () => { placeholder="예: 0.75" /> - + setInput2(e.target.value)} diff --git a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx index 9bf381b97..a8b0a2b84 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx @@ -114,11 +114,10 @@ export default function RTKForm() { initialFileInfo, // 2: 강우자료 ]); - const [select1, setSelect1] = useState('0'); // 환경청코드 (enoCd) - const [select2, setSelect2] = useState('0'); // 시도코드 (ctpvCd) - const [select3, setSelect3] = useState({ val: '0', text: '전체' }); // 시군구 (sggCd) - const [select4, setSelect4] = useState('0'); // 하수처리장 (swageTmplntCd) - const [select5, setSelect5] = useState('0'); // 하수처리구역 (swtrmtZoneCd) + const [select1, setSelect1] = useState('0'); // 하수처리장 (swageTmplntCd) + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 const { data: userInfo } = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.USERS], @@ -142,10 +141,6 @@ export default function RTKForm() { ]); setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경 setSelect1('0'); - setSelect2('0'); - setSelect3({ val: '0', text: '전체' }); - setSelect4('0'); - setSelect5('0'); }; // FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수 @@ -182,7 +177,7 @@ export default function RTKForm() { setIsLoadingResult(true); // 로딩 상태 시작 // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) - if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, { + if (!validateBasicSelectionsWithAlert(select1, { requireEnv: true, requireSido: true, requireSigungu: true, @@ -295,16 +290,12 @@ export default function RTKForm() { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { - enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값) aplcnYm: formData.date, // 적용연월 (YYYYMM 형식) - ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값) - sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값) - swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0) - ? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 + ctpvCd: ctpvCd ? String(ctpvCd) : null, // 시도코드 (선택값) + sggCd: sggCd ? String(sggCd) : null, // 시군구코드 (선택값) + swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0) + ? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 : null, // 하수처리장코드 (선택값) - swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0) - ? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 - : null, // 하수처리구역코드 (선택값) atflCd: file1?.atflCd, // 첨부파일CD (오수관망도) atflId: file1?.atflId, // 첨부파일ID (오수관망도) atflInpNm: file1?.file?.name || "", // 오수관망도 파일명 @@ -395,7 +386,6 @@ export default function RTKForm() { } return ( - // { }} graphBtn={false} > { }} // BaseLayout의 form 태그에 연결될 onSubmit graphBtn={false} analysisBtn={'RDII분석 실행'} - enoCd={select1} - setEnoCd={setSelect1} - ctpvCd={select2} - setCtpvCd={setSelect2} - sigungu={select3} - setSigungu={setSelect3} - selectedSwageTreatmentPlant={select4} - onSwageTreatmentPlantChange={setSelect4} - selectedSwageProcessingZone={select5} - onSwageProcessingZoneChange={setSelect5} - isLoading={isLoadingResult} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} + selectedSwagePrcsFclt={select1} + onSwagePrcsFcltChange={setSelect1} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} + setIsLoading={setIsLoadingResult} onAnalysis={handleAnalysisClick} > {[
diff --git a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx index 08c7a8146..66b8095db 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -1,5 +1,5 @@ 'use client' -import { deleteData, getData, useCookieAuth } from '@/components/lib/api' +import { deleteData, getData } from '@/components/lib/api' import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState, useEffect } from 'react' @@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; const formSchema = z.object({ - enoCd: z.string().default('0'), // 환경청코드 - ctpvCd: z.string().default('0'), // 시도코드 - sggCd: z.object({ // 시군구 - val: z.string().default('0'), - text: z.string().default('전체'), - }).default({ val: '0', text: '전체' }), swageTmplntCd: z.string().default('0'), // 하수처리장 swtrmtZoneCd: z.string().default('0'), // 하수처리구역 aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 @@ -54,14 +48,10 @@ const tableColumn = [ ] const Result = () => { - const AUTH = useCookieAuth(); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - enoCd: '0', - ctpvCd: '0', - sggCd: { val: '0', text: '전체' }, swageTmplntCd: '0', swtrmtZoneCd: '0', aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정 @@ -88,6 +78,9 @@ const Result = () => { const [resetSelection, setResetSelection] = useState(false); const [isResetPage, setIsResetPage] = useState(false); const [curPage, setCurPage] = useState(1); // 현재 페이지 + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 useEffect(() => { // setCurPage(1); @@ -127,11 +120,9 @@ const Result = () => { let inftwtAnlsSearchDto = { "aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null, "aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null, - "enoCd": formValues.enoCd === '0' ? null : formValues.enoCd, - "ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd, - "sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val, + "ctpvCd": ctpvCd === 0 ? null : ctpvCd, + "sggCd": sggCd === 0 ? null : sggCd, "swageTmplntCd": processedSwageTmplntCd, - "auth": AUTH, }; console.log("Search DTO:", inftwtAnlsSearchDto); @@ -163,7 +154,8 @@ const Result = () => { rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 }; - }); + }) + .filter((item: any) => item.scsYn === 'Y'); // 선택된 행 초기화 setSelectedRowData(null); @@ -289,21 +281,19 @@ const Result = () => { onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit graphBtn={true} showDatePicker={true} - enoCd={form.watch("enoCd")} - setEnoCd={(val) => form.setValue("enoCd", val)} - ctpvCd={form.watch("ctpvCd")} - setCtpvCd={(val) => form.setValue("ctpvCd", val)} - sigungu={form.watch("sggCd")} - setSigungu={(val) => form.setValue("sggCd", val)} - selectedSwageTreatmentPlant={form.watch("swageTmplntCd")} - onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} - selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} - onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} + selectedSwagePrcsFclt={form.watch("swageTmplntCd")} + onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }} - isLoading={isLoadingResult} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} + setIsLoading={setIsLoadingResult} // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} onDeleteFile={handleFileDelete} diff --git a/apps/sn/app/(with-base-layout)/ii/result/page.tsx b/apps/sn/app/(with-base-layout)/ii/result/page.tsx index 974f6fa5f..7c7e4721a 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { deleteData, getData, useCookieAuth } from '@/components/lib/api' -import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' +import { deleteData, getData } from '@/components/lib/api' +import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState, useEffect } from 'react' import { useForm } from 'react-hook-form' @@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; const formSchema = z.object({ - enoCd: z.string().default('0'), // 환경청코드 - ctpvCd: z.string().default('0'), // 시도코드 - sggCd: z.object({ // 시군구 - val: z.string().default('0'), - text: z.string().default('전체'), - }).default({ val: '0', text: '전체' }), swageTmplntCd: z.string().default('0'), // 하수처리장 swtrmtZoneCd: z.string().default('0'), // 하수처리구역 aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 @@ -55,9 +49,6 @@ const Result = () => { const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - enoCd: '0', - ctpvCd: '0', - sggCd: { val: '0', text: '전체' }, swageTmplntCd: '0', swtrmtZoneCd: '0', aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정 @@ -76,8 +67,6 @@ const Result = () => { } }) - const AUTH = useCookieAuth(); - const [tableData, setTableData] = useState([]); // API 조회 결과 저장 상태 const [totalData, setTotalData] = useState(0) //총 데이터 수l const [isLoadingResult, setIsLoadingResult] = useState(false); // 로딩 상태 @@ -85,6 +74,9 @@ const Result = () => { const [resetSelection, setResetSelection] = useState(false); const [isResetPage, setIsResetPage] = useState(false); const [curPage, setCurPage] = useState(1); // 현재 페이지 + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 useEffect(() => { // setCurPage(1); @@ -124,12 +116,9 @@ const Result = () => { let inftwtAnlsSearchDto = { "aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null, "aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null, - "enoCd": formValues.enoCd === '0' ? null : formValues.enoCd, - "ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd, - "sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val, + "ctpvCd": ctpvCd === 0 ? null : ctpvCd, + "sggCd": sggCd === 0 ? null : sggCd, "swageTmplntCd": processedSwageTmplntCd, - // "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd, - "auth": AUTH, }; console.log("Search DTO:", inftwtAnlsSearchDto); @@ -162,7 +151,8 @@ const Result = () => { rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 }; - }); + }) + .filter((item: any) => item.scsYn === 'Y'); // 선택된 행 초기화 setSelectedRowData(null); @@ -289,21 +279,19 @@ const Result = () => { onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit graphBtn={true} showDatePicker={true} - enoCd={form.watch("enoCd")} - setEnoCd={(val) => form.setValue("enoCd", val)} - ctpvCd={form.watch("ctpvCd")} - setCtpvCd={(val) => form.setValue("ctpvCd", val)} - sigungu={form.watch("sggCd")} - setSigungu={(val) => form.setValue("sggCd", val)} - selectedSwageTreatmentPlant={form.watch("swageTmplntCd")} - onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} - selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} - onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} + selectedSwagePrcsFclt={form.watch("swageTmplntCd")} + onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }} - isLoading={isLoadingResult} // 로딩 상태 전달 + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} + setIsLoading={setIsLoadingResult} // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} onDeleteFile={handleFileDelete} diff --git a/apps/sn/app/(with-base-layout)/layout.tsx b/apps/sn/app/(with-base-layout)/layout.tsx index 3c1a527d7..f8e046717 100644 --- a/apps/sn/app/(with-base-layout)/layout.tsx +++ b/apps/sn/app/(with-base-layout)/layout.tsx @@ -1,11 +1,13 @@ "use client"; +import React, { useEffect} from 'react'; import { usePathname, useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { BaseLayout } from "@common/components"; import type { MenuItem } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; import { PageLayout } from "@/shared/ui/page-layout"; +import useStore from '../store/useStore'; export default function WithBaseLayout({ children, @@ -18,6 +20,14 @@ export default function WithBaseLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); + const { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); + + useEffect(() => { + fetchCtpvSggData(); + fetchBIZ_SE_CD(); + fetchBIZ_TY_CD(); + }, []); + const handleMenuClick = (item: MenuItem) => { setCurrentMenuInfo(item); router.push(item.urlPath); diff --git a/apps/sn/app/(with-base-layout)/selectionValidation.tsx b/apps/sn/app/(with-base-layout)/selectionValidation.tsx index 8cc506d0e..e5cdb77ad 100644 --- a/apps/sn/app/(with-base-layout)/selectionValidation.tsx +++ b/apps/sn/app/(with-base-layout)/selectionValidation.tsx @@ -87,10 +87,7 @@ export const validateMultipleSelectionsWithAlert = ( * @returns 검증 결과 */ export const validateBasicSelections = ( - select1: string, - select2: string, - select3: ApiSelectOption, - select4: string | number, + select1: string | number, options: { requireEnv?: boolean; requireSido?: boolean; @@ -108,21 +105,6 @@ export const validateBasicSelections = ( const validationConfigs: SelectionValidationConfig[] = [ { value: select1, - description: '환경청', - required: requireEnv - }, - { - value: select2, - description: '시도', - required: requireSido - }, - { - value: select3, - description: '시군구', - required: requireSigungu - }, - { - value: select4, description: '하수처리장', required: requireTreatmentPlant } @@ -133,18 +115,12 @@ export const validateBasicSelections = ( /** * 기본 선택 항목들 검증 및 alert 표시 - * @param select1 환경청코드 - * @param select2 시도코드 - * @param select3 시군구 객체 - * @param select4 하수처리장코드 + * @param select1 하수처리장코드 * @param options 각 항목의 필수 여부 설정 * @returns 검증 결과 */ export const validateBasicSelectionsWithAlert = ( - select1: string, - select2: string, - select3: ApiSelectOption, - select4: string | number, + select1: string | number, options: { requireEnv?: boolean; requireSido?: boolean; @@ -152,7 +128,7 @@ export const validateBasicSelectionsWithAlert = ( requireTreatmentPlant?: boolean; } = {} ): boolean => { - const result = validateBasicSelections(select1, select2, select3, select4, options); + const result = validateBasicSelections(select1, options); if (!result.isValid && result.errorMessage) { alert(result.errorMessage); return false; diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index ab8936329..df1d9f509 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -41,8 +41,6 @@ export const postData = async (url: string, data: object): Promise => { body: JSON.stringify(data), }; const res = await fetch(`${BASE_URL}/${url}`, options); - console.log('res', res); - console.log('res.status', res.status); if (res.ok) { const resData = await res.json(); return resData; @@ -85,7 +83,7 @@ export const deleteData = async (url: string, data?: object): Promise => { return res; }; -// RSONE DC API 호출 +// DC API 호출 export const getDcData = async (url: string, data?: object): Promise => { const res = await fetch('/dc-service' + url, { method: "GET", @@ -97,9 +95,8 @@ export const getDcData = async (url: string, data?: object): Promise => { throw new Error('Failed to fetch data'); }; -// RSONE DC API 호출 +// CMS API 호출 export const getCmsData = async (url: string, data?: object): Promise => { - console.log(url); const res = await fetch('/cms-service' + url, { method: "GET", }); @@ -110,6 +107,18 @@ export const getCmsData = async (url: string, data?: object): Promise => { throw new Error('Failed to fetch data'); }; +// GeoServer API 호출 +export const getGeoData = async (url: string, data?: object): Promise => { + const res = await fetch('/geoserver' + url, { + method: "GET", + }); + if (res.ok) { + const data = await res.json(); + return data; + } + throw new Error('Failed to fetch data'); +}; + export function getCookie(name: string) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); if (match && match[2]) { diff --git a/apps/sn/app/components/lib/utils/date.ts b/apps/sn/app/components/lib/utils/date.ts index a401ad738..30bea45a0 100644 --- a/apps/sn/app/components/lib/utils/date.ts +++ b/apps/sn/app/components/lib/utils/date.ts @@ -103,8 +103,10 @@ export const formatDateString = (dateStr: string): string => { } const year = cleanStr.substring(0, 4); - const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거 - const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거 + const month = cleanStr.substring(4, 6); // parseInt로 앞의 0 제거 + // const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거 + // const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거 + const day = cleanStr.substring(6, 8); // parseInt로 앞의 0 제거 - return `${year}년 ${month}월 ${day}일`; + return `${year}-${month}-${day}`; }; \ No newline at end of file diff --git a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx index a5bd847cf..55cdc278f 100644 --- a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx +++ b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx @@ -21,7 +21,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] if (/^\d{6}$/.test(dateStr)) { const year = dateStr.substring(0, 4); const month = parseInt(dateStr.substring(4, 6)); - formattedData[fieldId] = `${year}년 ${month}월`; + formattedData[fieldId] = `${year}-${month}`; } else { formattedData[fieldId] = String(value); } @@ -34,7 +34,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] // yyyy-mm-dd hh:mm:ss 형식 확인 const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/; const match = dateTimeStr.match(dateTimeRegex); - + if (match && match.length >= 6) { const year = match[1]; const month = match[2]; @@ -42,12 +42,13 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] const hour = match[4]; const minute = match[5]; const second = match[6]; - + // 🎯 undefined 체크 후 parseInt 적용 if (year && month && day && hour && minute) { - const formattedMonth = parseInt(month); - const formattedDay = parseInt(day); - formattedData[fieldId] = `${year}년 ${formattedMonth}월 ${formattedDay}일 ${hour}시 ${minute}분 ${second}초`; + // const formattedMonth = parseInt(month); + // const formattedDay = parseInt(day); + // formattedData[fieldId] = `${year}-${formattedMonth}-${formattedDay} ${hour}:${minute}:${second}`; + formattedData[fieldId] = `${year}-${month}-${day} ${hour}:${minute}:${second}`; } else { formattedData[fieldId] = String(value); } @@ -60,7 +61,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] // 🎯 기존 날짜 포맷팅 (yyyymmdd -> yyyy년 m월 d일) if (dateFields.includes(fieldId)) { const dateStr = String(value).trim(); - + // 숫자 타입이거나 숫자로만 구성된 문자열인지 확인 if (/^\d+$/.test(dateStr)) { formattedData[fieldId] = formatDateString(dateStr); @@ -74,7 +75,12 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] if (numberFields.includes(fieldId)) { const numValue = Number(value); if (!isNaN(numValue) && isFinite(numValue)) { - formattedData[fieldId] = numValue.toLocaleString('ko-KR'); + // 소수점이 있는 경우 4자리까지 표시 + if (!Number.isInteger(numValue)) { + formattedData[fieldId] = numValue.toLocaleString('ko-KR', { maximumFractionDigits: 4 }); + } else { + formattedData[fieldId] = numValue.toLocaleString('ko-KR'); + } } else { formattedData[fieldId] = String(value); } diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx deleted file mode 100644 index 00f8f1dbb..000000000 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import React, { useMemo } from "react"; -import { SelectBox } from '@common/business_components/ui' -import { Label } from '@common/components'; - -interface Option { - id: number; - name: string; -} - -interface SidoSigunguSelectBoxProps { - sido: number; - sigungu: number; - onSidoChange: (id: number) => void; - onSigunguChange: (id: number) => void; - className?: string; -} - -const sidoList: { id: number, name: string }[] = [ - { id: 0, name: '전체' }, - { id: 26, name: '부산광역시' }, - { id: 28, name: '인천광역시' }, - { id: 30, name: '대전광역시' }, - { id: 41, name: '경기도' }, - { id: 42, name: '강원도' }, - { id: 44, name: '충청남도' }, - { id: 45, name: '전라북도' }, - { id: 46, name: '전라남도' }, - { id: 47, name: '경상북도' }, - { id: 48, name: '경상남도' }, -]; -const sigunguMap: Record = { - 26: [ - { id: 0, name: '전체' }, - // ...부산산광역시의 시군구 추가 - ], - 28: [ - { id: 0, name: '전체' }, - // ...인천광역시의 시군구 추가 - ], - 30: [ - { id: 0, name: '전체' }, - // ...대전광역시의 시군구 추가 - ], - 41: [ - { id: 0, name: '전체' }, - { id: 41820, name: '가평군' }, - { id: 41550, name: '안성시' }, - { id: 41830, name: '양평군' }, - { id: 41670, name: '여주시' }, - { id: 41500, name: '이천시' }, - { id: 41430, name: '의왕시' }, - { id: 41150, name: '의정부시' }, - { id: 41480, name: '파주시' }, - { id: 41650, name: '포천시' }, - // ...경기도의 시군구 추가 - ], - 42: [ - { id: 0, name: '전체' }, - { id: 42170, name: '동해시' }, - { id: 42110, name: '춘천시' }, - { id: 42720, name: '홍천군' }, - // ...강원도의 시군구 추가 - ], - 44: [ - { id: 0, name: '전체' }, - { id: 44770, name: '서천군' }, - // ...충청남도의 시군구 추가 - ], - 45: [ - { id: 0, name: '전체' }, - { id: 45130, name: '군산시' }, - { id: 45190, name: '남원시' }, - { id: 45730, name: '무주군' }, - { id: 45800, name: '부안군' }, - { id: 45770, name: '순창군' }, - { id: 45140, name: '익산시' }, - { id: 45750, name: '임실군' }, - { id: 45710, name: '완주군' }, - { id: 45110, name: '전주시' }, - // ...전라북도의 시군구 추가 - ], - 46: [ - { id: 0, name: '전체' }, - { id: 46810, name: "강진군" }, - { id: 46770, name: "고흥군" }, - { id: 46720, name: "곡성군" }, - { id: 46730, name: "구례군" }, - { id: 46230, name: "광양시" }, - { id: 46170, name: "나주시" }, - { id: 46110, name: "목포시" }, - { id: 46840, name: "무안군" }, - { id: 46150, name: "순천시" }, - { id: 46910, name: "신안군" }, - { id: 46130, name: "여수시" }, - { id: 46890, name: "완도군" }, - // ...전라남도의 시군구 추가 - ], - 47: [ - { id: 0, name: '전체' }, - { id: 47130, name: "경주시" }, - { id: 47830, name: "고령군" }, - { id: 47190, name: "구미시" }, - { id: 47720, name: "군위군" }, - { id: 47150, name: "김천시" }, - { id: 47280, name: "문경시" }, - { id: 47920, name: "봉화군" }, - { id: 47250, name: "상주시" }, - { id: 47770, name: "영덕군" }, - { id: 47760, name: "영양군" }, - { id: 47210, name: "영주시" }, - { id: 47900, name: "예천군" }, - { id: 47930, name: "울진군" }, - { id: 47730, name: "의성군" }, - { id: 47750, name: "청송군" }, - { id: 47850, name: "칠곡군" }, - { id: 47110, name: "포항시" }, - // ...경상북도의 시군구 추가 - ], - 48: [ - { id: 0, name: '전체' }, - { id: 48310, name: "거제시" }, - { id: 48250, name: "김해시" }, - { id: 48270, name: "밀양시" }, - { id: 48220, name: "통영시" }, - // ...경상남도의 시군구 추가 - ], - // ...나머지 시도별 시군구 추가 -}; - -export function SidoSigunguSelectBox({ - sido, - sigungu, - onSidoChange, - onSigunguChange, - className = "", -}: SidoSigunguSelectBoxProps) { - // 선택된 시도에 따라 시군구 리스트를 가져옴 - const sigunguList = useMemo(() => sigunguMap[sido] || [{ id: 0, name: '전체' }], [sido]); - - return ( -
- - ({ val: opt.id, text: opt.name }))} - value={sido} - onSelectValue={val => onSidoChange(Number(val))} - selectContentClass="z-[101]" - /> - - ({ val: opt.id, text: opt.name }))} - value={sigungu} - onSelectValue={val => onSigunguChange(Number(val))} - selectContentClass="z-[101]" - /> -
- ); -} \ No newline at end of file diff --git a/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx new file mode 100644 index 000000000..7e401ef3a --- /dev/null +++ b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx @@ -0,0 +1,159 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { SelectBox } from '@common/business_components/ui' +import { Label } from '@common/components'; +import useStore from '../../store/useStore'; +import { getData } from '@/components/lib/api/index'; + + +interface ApiSelectOption { + text: string; + val: string; +} +interface CtpvSggPrcsFcltSelectBoxProps { + sido: number; + sigungu: number; + sggName?: string; + onSidoChange: (id: number) => void; + onSigunguChange: (id: number) => void; + setCtpvName?: (name: string) => void; + setSggName?: (name: string) => void; + className?: string; + selectedSwagePrcsFclt?: string | number; + onSwagePrcsFcltChange?: (value: string | number) => void; + setIsLoading?: (isLoading: boolean) => void; +} + +const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; + +export function CtpvSggPrcsFcltSelectBox({ + sido, + sigungu, + sggName, + onSidoChange, + setCtpvName, + onSigunguChange, + setSggName, + className = "", + selectedSwagePrcsFclt, + onSwagePrcsFcltChange, + setIsLoading, +}: CtpvSggPrcsFcltSelectBoxProps) { + + const [prcsFcltOptions, setPrcsFcltOptions] = useState([]); + const { ctpv, sgg } = useStore(); + + useEffect(() => { + (async () => { + onSidoChange(0); + onSigunguChange(0); + if (setCtpvName && setSggName) { + setCtpvName(""); + setSggName(""); + } + })(); + }, []); + + useEffect(() => { + onSwagePrcsFcltChange?.('0'); + + const fetchData = async () => { + if (!sggName) { + console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sggName); + setPrcsFcltOptions([]); + if (setIsLoading) setIsLoading(false); + return; + } + + if (setIsLoading) setIsLoading(true); + try { + + const queryParamsFacility = new URLSearchParams({ + lyrTypeNm: 'tb_il_swage_prcs_fclt', + page: '0', + size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 + sort: 'string', + }); + const cqlFiltersFacility: string[] = []; + if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`); + if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); + + console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); + + const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); + + console.log('facilityRes.data', facilityRes.data); + + const facilities = (facilityRes.data || []) + .map((item: any) => ({ + text: item.swtrmtFcltNm, + val: item.fid + })) + .sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko')); + setPrcsFcltOptions(facilities); + } catch (error) { + console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); + setPrcsFcltOptions([]); + } finally { + if (setIsLoading) setIsLoading(false); + } + }; + + fetchData(); + }, [sggName]); + + + return ( +
+ + ({ val: opt.id, text: opt.name })) + ] + } + value={sido} + onSelectValue={val => { + onSidoChange(Number(val)); + if (setCtpvName) setCtpvName(ctpv.find(opt => Number(opt.id) === Number(val))?.name || ""); + }} + selectContentClass="z-[101]" + /> + + String(opt.id).slice(0, 2) === String(sido)) + .map(opt => ({ val: opt.id, text: opt.name })) + ] + } + value={sigungu} + onSelectValue={val => { + onSigunguChange(Number(val)) + if (setSggName) setSggName(sgg.find(opt => Number(opt.id) === Number(val))?.name || ""); + }} + selectContentClass="z-[101]" + /> + {selectedSwagePrcsFclt && onSwagePrcsFcltChange && ( + <> + + onSwagePrcsFcltChange?.(val)} + selectContentClass="z-[101]" + /> + + )} +
+ ); +} \ No newline at end of file diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts new file mode 100644 index 000000000..40e094480 --- /dev/null +++ b/apps/sn/app/store/index.ts @@ -0,0 +1,56 @@ +import { getCmsData } from "@/components/lib/api"; + + +export interface Option { id: number, name: string } + +export type BIZ_CD_TYPE = { + lwpoCd: string, + upCd: string, + lwpoCdNm: string, + unit: string, + prntLwpoCd: null +}; + +export const fetchCtpvSggData = async (set: (state: any) => void) => { + try { + const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD/Y'); + const ctpvList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2) + .map((feature: any) => ({ + id: feature.lwpoCd, + name: feature.lwpoCdNm + })); + + const sggList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2) + .map((item: any) => ({ + id: item.lwpoCd, + name: item.lwpoCdNm + })); + + set({ ctpv: ctpvList, sgg: sggList }); + } catch (e: any) { + // alert("시도, 시군구 데이터 로드 실패: " + e.message); + console.error("CTPV 데이터 로드 실패:", e); + } +}; + +export const fetchBIZ_SE_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZSECD`); + set({ BIZ_SE_CD: res }); + } catch (e: any) { + // alert("BIZ_SE_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_SE_CD 데이터 로드 실패:", e); + } +}; + +export const fetchBIZ_TY_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`); + set({ BIZ_TY_CD: res }); + } catch (e: any) { + // alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_TY_CD 데이터 로드 실패:", e); + } +}; \ No newline at end of file diff --git a/apps/sn/app/store/useStore.ts b/apps/sn/app/store/useStore.ts new file mode 100644 index 000000000..b2ae014fa --- /dev/null +++ b/apps/sn/app/store/useStore.ts @@ -0,0 +1,35 @@ +import { create } from "zustand"; +import { getGeoData, getCmsData } from "@/components/lib/api"; +import { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } from "./index"; + +interface Option { id: number, name: string } + +export type BIZ_CD_TYPE = { + lwpoCd: string, + upCd: string, + lwpoCdNm: string, + unit: string, + prntLwpoCd: null +}; + +interface SnStoreState { + ctpv: Option[]; + sgg: Option[]; + BIZ_SE_CD: BIZ_CD_TYPE[]; + BIZ_TY_CD: BIZ_CD_TYPE[]; + fetchCtpvSggData: () => Promise; + fetchBIZ_SE_CD: () => Promise; + fetchBIZ_TY_CD: () => Promise; +} + +const useStore = create((set) => ({ + ctpv: [], + sgg: [], + BIZ_SE_CD: [], + BIZ_TY_CD: [], + fetchCtpvSggData: () => fetchCtpvSggData(set), + fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set), + fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set), +})); + +export default useStore; \ No newline at end of file diff --git a/apps/sn/public/images/map/layericon/BML_CBAS_PS copy.png b/apps/sn/public/images/map/layericon/BML_CBAS_PS copy.png new file mode 100644 index 000000000..0e70939ba Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_CBAS_PS copy.png differ diff --git a/apps/sn/public/images/map/layericon/BML_STMS_PS copy.png b/apps/sn/public/images/map/layericon/BML_STMS_PS copy.png new file mode 100644 index 000000000..bf4357181 Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_STMS_PS copy.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_ETCT_PS copy.png b/apps/sn/public/images/map/layericon/RDL_ETCT_PS copy.png new file mode 100644 index 000000000..e5a83f998 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_ETCT_PS copy.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_OCUP_PS copy.png b/apps/sn/public/images/map/layericon/RDL_OCUP_PS copy.png new file mode 100644 index 000000000..aeae3e266 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_OCUP_PS copy.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_SEPC_PS copy.png b/apps/sn/public/images/map/layericon/RDL_SEPC_PS copy.png new file mode 100644 index 000000000..b3db8e0a4 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_SEPC_PS copy.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_STAT_PS copy.png b/apps/sn/public/images/map/layericon/RDL_STAT_PS copy.png new file mode 100644 index 000000000..9a0b11673 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_STAT_PS copy.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS copy.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS copy.png new file mode 100644 index 000000000..3776f6c04 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TSCT_PS copy.png differ diff --git a/apps/sn/public/images/map/toolbar/pause_button.png b/apps/sn/public/images/map/toolbar/pause_button.png new file mode 100644 index 000000000..05f51ac13 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/pause_button.png differ diff --git a/apps/sn/public/images/map/toolbar/play_button_arrowhead.png b/apps/sn/public/images/map/toolbar/play_button_arrowhead.png new file mode 100644 index 000000000..7948b16e5 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/play_button_arrowhead.png differ diff --git a/apps/sn/src/shared/lib/index.ts b/apps/sn/src/shared/lib/index.ts index b8b4c52c0..061346946 100644 --- a/apps/sn/src/shared/lib/index.ts +++ b/apps/sn/src/shared/lib/index.ts @@ -2,5 +2,6 @@ export { useMutationWrapper } from "./use-mutation-wrapper"; export { useAlertStore } from "./use-alert-store"; export { useMenuStore } from "./use-menu-store"; export { useUserStore } from "./use-user-store"; +export { useDataStore } from "./use-data-store"; export { usePrompt } from "./use-prompt"; export { getCookie, setCookie, removeCookie } from "./cookie"; diff --git a/apps/sn/src/shared/lib/use-data-store.ts b/apps/sn/src/shared/lib/use-data-store.ts new file mode 100644 index 000000000..fc88ad3bb --- /dev/null +++ b/apps/sn/src/shared/lib/use-data-store.ts @@ -0,0 +1,14 @@ +import { useStore } from "zustand"; +import { useContext } from "react"; +import type { DataStore } from "../store/data-store"; +import { StoreContext } from "../providers/store-provider"; + +export const useDataStore = (selector: (store: DataStore) => T): T => { + const storeContext = useContext(StoreContext); + + if (!storeContext) { + throw new Error("useDataStore must be used within StoreProvider"); + } + + return useStore(storeContext.dataStore, selector); +}; diff --git a/apps/sn/src/shared/providers/store-provider.tsx b/apps/sn/src/shared/providers/store-provider.tsx index a0ec17b61..db01d81b4 100644 --- a/apps/sn/src/shared/providers/store-provider.tsx +++ b/apps/sn/src/shared/providers/store-provider.tsx @@ -6,6 +6,7 @@ import { createAttachmentStore, createMenuStore, createUserStore, + createDataStore, } from "../store"; type StoreContextType = { @@ -13,6 +14,7 @@ type StoreContextType = { attachmentStore: ReturnType; menuStore: ReturnType; userStore: ReturnType; + dataStore: ReturnType; }; export const StoreContext = createContext( @@ -27,6 +29,7 @@ export const StoreProvider = ({ children }: { children: React.ReactNode }) => { attachmentStore: createAttachmentStore(), menuStore: createMenuStore(), userStore: createUserStore(), + dataStore: createDataStore(), }; } diff --git a/apps/sn/src/shared/store/data-store.ts b/apps/sn/src/shared/store/data-store.ts new file mode 100644 index 000000000..fec870234 --- /dev/null +++ b/apps/sn/src/shared/store/data-store.ts @@ -0,0 +1,90 @@ +import { create } from "zustand"; +import { getCmsData } from "@/components/lib/api"; + +export interface Option { id: number, name: string } + +export type BIZ_CD_TYPE = { + lwpoCd: string, + upCd: string, + lwpoCdNm: string, + unit: string, + prntLwpoCd: null +}; + +export type DataState = { + ctpv: Option[]; + sgg: Option[]; + BIZ_SE_CD: BIZ_CD_TYPE[]; + BIZ_TY_CD: BIZ_CD_TYPE[]; +} + +export type FetchData = { + fetchCtpvSggData: () => Promise; + fetchBIZ_SE_CD: () => Promise; + fetchBIZ_TY_CD: () => Promise; +}; + +export type DataStore = DataState & FetchData; + +export const defaultInitState: DataState = { + ctpv: [], + sgg: [], + BIZ_SE_CD: [], + BIZ_TY_CD: [], +}; + +const fetchCtpvSggData = async (set: (state: any) => void) => { + try { + const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD'); + console.log("CTPV Data:", ctpvData); + const ctpvList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2) + .map((feature: any) => ({ + id: feature.lwpoCd, + name: feature.lwpoCdNm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + + const sggList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2) + .map((item: any) => ({ + id: item.lwpoCd, + name: item.lwpoCdNm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + + set({ ctpv: ctpvList, sgg: sggList }); + } catch (e: any) { + // alert("시도, 시군구 데이터 로드 실패: " + e.message); + console.error("CTPV 데이터 로드 실패:", e); + } +}; + +const fetchBIZ_SE_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZSECD`); + set({ BIZ_SE_CD: res }); + } catch (e: any) { + // alert("BIZ_SE_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_SE_CD 데이터 로드 실패:", e); + } +}; + +const fetchBIZ_TY_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`); + set({ BIZ_TY_CD: res }); + } catch (e: any) { + // alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_TY_CD 데이터 로드 실패:", e); + } +}; + +export const createDataStore = (initState: DataState = defaultInitState) => { + return create((set) => ({ + ...initState, + fetchCtpvSggData: () => fetchCtpvSggData(set), + fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set), + fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set), + })); +}; \ No newline at end of file diff --git a/apps/sn/src/shared/store/index.ts b/apps/sn/src/shared/store/index.ts index a41d6f928..8928b7269 100644 --- a/apps/sn/src/shared/store/index.ts +++ b/apps/sn/src/shared/store/index.ts @@ -2,3 +2,4 @@ export { createAlertStore } from "./alert-store"; export { createAttachmentStore } from "./attachment-store"; export { createMenuStore, type BbsType } from "./menu-store"; export { createUserStore, type IUserInfo } from "./user-store"; +export { createDataStore, type DataStore } from "./data-store"; \ No newline at end of file diff --git a/apps/wr/.env.local b/apps/wr/.env.local index 2543a96ad..4d00d1cab 100644 --- a/apps/wr/.env.local +++ b/apps/wr/.env.local @@ -10,6 +10,8 @@ NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token +NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver diff --git a/apps/wr/app/map/components/layerPopup/hasuFacility.tsx b/apps/wr/app/map/components/layerPopup/hasuFacility.tsx new file mode 100644 index 000000000..5a10ce3ed --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/hasuFacility.tsx @@ -0,0 +1,89 @@ +import { useWrStore } from "@/app/store"; +import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import { tableData } from "@/components/lib/func/commom"; +import { Title, UiTable } from "@common/business_components/ui"; +import { useEffect, useState } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + isRuseReg: { data: any | undefined }; +} +const HasuFacility = ({ seletectedLayerNm, viewMode, onClose, onFeatureSelect, isRuseReg }: Props) => { + const { + code, + pbnst, + pubHasuInit, + } = useWrStore((state) => state); + const { pubFacHeadList, pbnstHasuRes, pbnstHasuResCount } = pbnst; + const [hasuTable, setHasuTable] = useState(); + const [hasuPage, setHasuPage] = useState(1); + useEffect(() => { + if (code.sggCd.length && pbnstHasuRes.length > 0) { + setHasuTable(tableData(pbnstHasuRes, { code })); + } + }, [code, pbnstHasuRes]); + useEffect(() => { + if (seletectedLayerNm !== '하수처리시설' || viewMode != 'list') return; + if (!pbnst.isInit) { + pubHasuInit("1", "0"); + } + }, [pbnst.isInit, pubHasuInit]); + useEffect(() => { + if (seletectedLayerNm !== '하수처리시설' || viewMode != 'list') return; + pubHasuInit(String(hasuPage), "0"); + }, [hasuPage, pubHasuInit]); + + const hasuCellClick = (index: number | undefined) => { + if (index !== undefined && pbnstHasuRes !== undefined) { + onFeatureSelect(pbnstHasuRes[index]); + onClose(); + } + }; + return (viewMode == 'list' ? ( +
+
+ + </div> + {/* 높이 조정 */} + <UiTable + tableData={hasuTable || []} + headlist={pubFacHeadList} + pageSize={11} + total={pbnstHasuResCount || 0} + headName={""} + cellClick={hasuCellClick} + onPageChange={setHasuPage} + /> + </div> + ) : + ( + <div style={{ overflowX: 'hidden' }}> + <div className="flex justify-between items-center mb-4"> + <Title title="하수처리시설 상세정보" /> + </div> + {/* 높이 조정 */} + <UiTable + tableData={isRuseReg?.data || []} + headlist={pubFacHeadList.slice(0, 4)} + pageSize={1} + total={0} + headName={"위치정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + <UiTable + tableData={isRuseReg?.data || []} + headlist={pubFacHeadList.slice(4)} + pageSize={1} + total={0} + headName={"기본정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + </div> + ) + ) +} +export default HasuFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/htdflFacility.tsx b/apps/wr/app/map/components/layerPopup/htdflFacility.tsx new file mode 100644 index 000000000..fe4bef25a --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/htdflFacility.tsx @@ -0,0 +1,83 @@ +import { useWrStore } from '@/app/store'; +import type { customHtdflBdst } from '@/app/store/ruseFcltMng/fcltReg/htdflReg'; +import { tableData } from '@/components/lib/func/commom'; +import SttnDetailModal from '@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal'; +import { Title, UiTable } from '@common/business_components/ui'; +import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan'; +import React, { useEffect, useState } from 'react' +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + htdflDetailModal: { isOpen: boolean; data: customHtdflBdst | undefined }; +} +const HtdflFacility = ({ seletectedLayerNm, viewMode, onFeatureSelect, onClose, htdflDetailModal }: Props) => { + const { code, htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state); + const { htdflSttnRes, htdflSttnResCount } = htdflSttn; + const [htdflTable, setHtdflTable] = useState<customHtdflBdst[]>(); + const [htdflPage, setHtdflPage] = useState(1) + // 온배수시설 cellClick 이벤트 (상세정보) + const htdflCellClick = (index: number | undefined) => { + if (index !== undefined && htdflTable !== undefined) { + onFeatureSelect(htdflTable[index]); + onClose(); + } + }; + useEffect(() => { + if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return; + if (!htdflSttn.isInit) { + initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd); + } + }, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]); + + useEffect(() => { + if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return; + const data = tableData(htdflSttnRes, { code }); + const chData = data + .map((item: any) => ({ + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0].htdflObj, + chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", + })) + .filter((item: any) => item.htdflObj !== undefined); + setHtdflTable(chData); + }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); + + useEffect(() => { + if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return; + htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd); + }, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]); + return ( + viewMode == 'detail' ? ( + <SttnDetailModal type={"HTB"} formObj={htdflDetailModal.data as customHtdflBdst} /> + ) : ( + <div style={{ overflowY: 'auto' }}> + <div className="flex justify-between items-center"> + <Title title="온배수시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={htdflTable || []} + headlist={htdflSttn.htdflSttHeadList} + pageSize={20} + total={htdflSttnResCount || 0} + cellClick={(index: number | undefined) => { + htdflCellClick(index ?? undefined); + }} + onPageChange={setHtdflPage} + > + <HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} /> + </UiTable> + </div> + ) + ) +} + +export default HtdflFacility \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/rainFacility.tsx b/apps/wr/app/map/components/layerPopup/rainFacility.tsx new file mode 100644 index 000000000..54b51e8a6 --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/rainFacility.tsx @@ -0,0 +1,105 @@ +import { useWrStore } from "@/app/store"; +import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { tableData } from "@/components/lib/func/commom"; +import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; +import { Title, UiTable } from "@common/business_components/ui"; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { useEffect, useState } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + rainDetailModal: { isOpen: boolean; data: customRainBdst | undefined }; +} +const RainFacility = ({seletectedLayerNm,viewMode,onFeatureSelect,onClose,rainDetailModal}:Props) => { + // 빗물재이용시설 변수 + const { code, rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( + (state) => state + ); + const { rainSttnRes, rainSttnResCount } = rainSttn; + const [rainTable, setRainTable] = useState<customRainBdst[]>(); + const [rainPage, setRainPage] = useState(1) + + // 빗물재이용시설 cellClick 이벤트 (상세정보) + const rainCellClick = (index: number | undefined, headId: string) => { + if ( + index !== undefined && + rainTable !== undefined && + headId !== 'chBuildingYn') { + onFeatureSelect(rainTable[index]); + onClose(); + } + } + useEffect(() => { + if (seletectedLayerNm !== '빗물이용시설' || viewMode != 'list') return; + console.log('빗물이용시설 초기값 호출', viewMode) + if (!rainSttn.isInit) { + rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); + } + }, [code.cityCd, code.sggCd, rainSttn.isInit, rainSttnInit]); + + useEffect(() => { + const data = tableData(rainSttnRes, { code }); + const chData = data + .map((item: any) => { + return { + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0].rainyObj, + dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.dtfcltCpct, + instlCst: item.reuseBuilding[0].instlCst === -1 + ? "확인불가" + : item.reuseBuilding[0].instlCst, + instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1 + ? "확인불가" + : item.reuseBuilding[0].instlCstSprtAmt, + chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", + pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.pntfColArea, + filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct, + archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea, + }; + }) + .filter((item: any) => item.rainyObj !== undefined); + setRainTable(chData); + }, [code, rainSttnRes, rainSttn.rainSttHeadList]); + + useEffect(() => { + if (seletectedLayerNm !== '빗물이용시설' || viewMode != 'list') return; + rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd); + }, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]); + return (viewMode == 'detail' ? + (<SttnDetailModal type={"RNB"} formObj={rainDetailModal.data as customRainBdst} />) + : ( + <> + <div className="flex justify-between items-center"> + <Title title="빗물이용시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={rainTable || []} + headlist={rainSttn.rainSttHeadList} + pageSize={20} + total={rainSttnResCount} + cellClick={(index: number | undefined, headId: string) => { + rainCellClick(index ?? undefined, headId); + }} + onPageChange={setRainPage} + > + <HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} /> + </UiTable> + </> + )) +} +export default RainFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/reuseFacility.tsx b/apps/wr/app/map/components/layerPopup/reuseFacility.tsx new file mode 100644 index 000000000..152d37999 --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/reuseFacility.tsx @@ -0,0 +1,146 @@ +import { useWrStore } from "@/app/store"; +import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; +import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal"; +import { Title, UITab, UiTable } from "@common/business_components/ui"; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { useState, useEffect, useCallback } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + setFcltPk: (pk: string) => void; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + actTabData: { id: string; name: string; data: any } | undefined; + tabData: { id: string; name: string; data: any }[]; +} +const ResueFacility = ({ viewMode, seletectedLayerNm, tabData, actTabData, setFcltPk, onFeatureSelect, onClose}: Props) => { + const [activeTab, setActiveTab] = useState<string>(''); // 현재 활성화된 탭 + // 하수재이용시설 변수 + const { + pbnstSttn, + pbnstSttnSearch, + pbnSttnInit, + getFlcts + } = useWrStore((state) => state); + const { fclts, pbnstSttnResCount } = pbnstSttn + const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>() + // 재이용시설 cellClick 이벤트 (상세정보) + const pbnstCellClick = (index: number | undefined) => { + if ( + index !== undefined && + pbnstSttn.pbnstSttnRes && + pbnstSttn.pbnstSttnRes[index] + ) { + setFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltId); + onFeatureSelect(pbnstSttn.pbnstSttnRes[index]); + onClose() + } + }; + + // 재이용 현황 초기값 + useEffect(() => { + if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return; + if (!pbnstSttn.isInit) { + getFlcts() + } + }, [seletectedLayerNm, pbnstSttn.isInit]); + useEffect(() => { + if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return; + pbnSttnInit("1", "20", { + partValue: "A", + sidoValue: '0', + sigunValue: "0", + regionValue: "", + unitWaterShedValue: "", + middleRegionValue: "", + envPartValue: "", + sidoText: "", + sigunText: "" + }, String(new Date().getFullYear()), pbnstSttn.fclts); + }, [fclts, pbnSttnInit, pbnstSttn.fclts, seletectedLayerNm]) + useEffect(() => { + setBdstTable(pbnstSttn.pbnstSttnRes); + }, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]); + + // 재이용시설 조회 + // 재이용시설 페이징처리 관련 함수 + const onPageChangeHandler = useCallback((page: number) => { + if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return; + pbnstSttnSearch( + false, + String(page), + String(20), + { + partValue: "A", + sidoValue: "0", + sigunValue: "0", + regionValue: "", + unitWaterShedValue: "", + middleRegionValue: "", + envPartValue: "", + sidoText: "", + sigunText: "", + }, + "", + String(new Date().getFullYear()), fclts + ); + }, [fclts, pbnstSttnSearch]); + // FIXME: 윤선화차장님 요구사항으로 엑셀 다운로드 삭제. + // const excelClick = useCallback(() => { + // // if (codeData) pbnstSttnSearch(1, "10000", codeData); + // excelDownload( + // [[]], + // "하수재이용시설 현황", + // "sample_pbnstSttn.xlsx", + // "A1:U2" + // ); + // }, [pbnstSttnSearch]); + return ( + viewMode == 'detail' ? + // 재이용시설 상세 + <> + <div className="flex justify-between items-center mb-4"> + <Title title="하수재이용시설 상세정보" /> + </div> + <div className="flex justify-between items-center mb-8"> + <UITab + defaultValue={activeTab} + tabList={tabData.map((tab) => ({ + value: tab.id, + label: tab.name, + onClick: () => setActiveTab(tab.id), + }))} + /> + </div> + <PbnstSttnModal + data={actTabData?.data[0] || ({} as customPbnst)} + titleVisible={false} + /> + </> : + <div style={{ overflowY: 'auto' }}> + <div className="flex justify-between items-center"> + <Title title="하수재이용시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={bdstTable || []} + headlist={pbnstSttn.pbnstSttHeadList} + pageSize={20} + total={pbnstSttnResCount || 0} + cellClick={(index: number | undefined) => { + pbnstCellClick(index); + }} + onPageChange={onPageChangeHandler} + > + <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> + </UiTable> + </div> + ) +} +export default ResueFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx b/apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx new file mode 100644 index 000000000..9030493ff --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx @@ -0,0 +1,103 @@ +import { useWrStore } from '@/app/store'; +import { tableData, formatPhoneNumber } from '@/components/lib/func/commom'; +import { Title, UiTable } from '@common/business_components/ui'; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; +import moment from 'moment'; +import React, { useEffect, useState } from 'react' +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + isDmdModal: { data: any | undefined }; +} +const ReuseMngFacility = ({seletectedLayerNm, viewMode, onFeatureSelect, onClose, isDmdModal}:Props) => { + const { code, + coSttn, + coSttnInit, + coSttnSearch, + } = useWrStore((state) => state); + const [mngTable, setMngTable] = useState<DemandCompType[]>(); + const [mngPage, setMngPage] = useState(1); + const { coSttnRes, coSttnResCount, coSttHeadList } = coSttn; + useEffect(() => { + if (seletectedLayerNm !== '수요처' || viewMode != 'list') return; + if (!coSttn.isInit) { + coSttnInit("1", "20", "0"); + } + }, [coSttn.isInit, coSttnInit]); + const coMngCellClick = (index: number | undefined) => { + if (index !== undefined && mngTable !== undefined) { + onFeatureSelect(mngTable[index]); + onClose(); + } + }; + useEffect(() => { + if (seletectedLayerNm !== '수요처' || viewMode!='list') return; + if (coSttnRes) { + if (coSttnRes?.length > 0 && code.cityCd.length > 0) { + const data = tableData(coSttnRes, { code }); + const chData = data.map((item: any) => ({ + ...item, + telno: formatPhoneNumber(item.telno || undefined), + regDt: moment(item.regDt).format("YYYY-MM-DD"), + dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}` + })); + // .filter((item: any) => item !== undefined); + setMngTable(chData); + } + } + }, [coSttnRes, code]); + useEffect(() => { + if (seletectedLayerNm !== '수요처' || viewMode!='list') return; + coSttnSearch(String(mngPage), "20", String(0)); + }, [coSttnSearch, mngPage]); + return ( + viewMode == 'detail' + ? ( + <div style={{ overflowX: 'hidden' }}> + <div className="flex justify-between items-center mb-4"> + <Title title="수요처 상세정보" /> + </div> + {/* 높이 조정 */} + <UiTable + tableData={isDmdModal?.data || []} + headlist={coSttHeadList.slice(0, 3)} + pageSize={1} + total={0} + headName={"위치정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + <UiTable + tableData={isDmdModal?.data || []} + headlist={coSttHeadList.slice(3)} + pageSize={1} + total={0} + headName={"기본정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + </div> + ) : (<div> + <div className="flex justify-between items-center"> + <Title title="수요처 현황" /> + </div> + <UiTable + headName={[]} + tableData={mngTable || []} + headlist={coSttHeadList} + pageSize={20} + total={coSttnResCount + 10 || 0} + cellClick={(index: number | undefined) => { + coMngCellClick(index); + // setUpdateData(coSttnRes[index as number]); + }} + onPageChange={(page) => { + setMngPage(page); + }} + ></UiTable></div>) + ) +} + +export default ReuseMngFacility \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/rswtFacility.tsx b/apps/wr/app/map/components/layerPopup/rswtFacility.tsx new file mode 100644 index 000000000..dc2537c95 --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/rswtFacility.tsx @@ -0,0 +1,107 @@ +import { useWrStore } from "@/app/store"; +import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; +import { tableData } from "@/components/lib/func/commom"; +import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; +import { Title, UiTable } from "@common/business_components/ui"; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { useEffect, useState } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + rswtrDetailModal: { isOpen: boolean; data: customRswtBdst | undefined }; +} +const RswtFacility = ({ seletectedLayerNm, viewMode, onFeatureSelect, onClose, rswtrDetailModal }: Props) => { + const { code, rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore( + (state) => state + ); + const { rswtSttnRes, rswtSttnResCount } = rswtSttn; + const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>() + const [rswtrPage, setRswtrPage] = useState(1); + // 중수도시설 cellClick 이벤트 (상세정보) + const rswtCellClick = (index: number | undefined, headId: string) => { + if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) { + // setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] }); + // setViewMode('detail') + onFeatureSelect(rswtrTable[index]); + onClose(); + } + } + useEffect(() => { + if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return; + if (!rswtSttn.isInit) { + rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); + } + }, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]); + + useEffect(() => { + if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return; + const data = tableData(rswtSttnRes, { code }); + const chData = data + .map((item: customRswtBdst) => { + return { + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0]?.rswtrObj, + chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N', + instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst, + instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt, + archArea: item?.archArea.split('.')[0] !== '-1' ? item?.archArea.split('.')[0] : '확인불가' + } + }) + .filter( + (item: customRswtBdst) => + item.reuseBuilding[0]?.rswtrObj !== undefined + ); + setRswtrTable(chData); + }, [code, rswtSttnRes]); + // FIXME: 윤선화 차장님 요구사항으로 엑셀 다운로드 삭제. + // const rswtSttnExcelDownload = useCallback(() => { + // if (rswtSttn.excelList.length > 0) { + // if (rswtSttn.excelList.length === 0) return; + // excelDownload( + // rswtSttn.excelList, + // "중수도시설 현황", + // "sample_rswtSttn.xlsx", + // "A1:W3", + // ); + // } + // }, [rswtSttn.excelList]) + + useEffect(() => { + if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return; + rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd); + }, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]); + return ( + viewMode == 'detail' ? + // 중수도시설 상세 + ( + <SttnDetailModal type={"RST"} formObj={rswtrDetailModal.data as customRswtBdst} /> + ) : ( + <div style={{ overflowY: 'auto' }}> + <div className="flex justify-between items-center"> + <Title title="중수도시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={rswtSttnExcelDownload}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={rswtrTable || []} + headlist={rswtSttn.rswtSttHeadList} + pageSize={20} + total={rswtSttnResCount} + cellClick={(index: number | undefined, headId: string) => { + rswtCellClick(index, headId); + }} + onPageChange={setRswtrPage} + > + <HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} /> + </UiTable> + </div> + )) +} +export default RswtFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/newWindowPopup.tsx b/apps/wr/app/map/components/newWindowPopup.tsx new file mode 100644 index 000000000..21d31b984 --- /dev/null +++ b/apps/wr/app/map/components/newWindowPopup.tsx @@ -0,0 +1,183 @@ +"use client" +import NewWindowPortal from '@/app/map/components/newWindowPortal'; +import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; +import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; +import { useGisContext } from "@common/business_components/ui/map/Gis"; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import ResueFacility from './layerPopup/reuseFacility'; +import { detainSttn, fetchHasuDetail, fetchpbnstDetail } from '../util'; +import HasuFacility from './layerPopup/hasuFacility'; +import RainFacility from './layerPopup/rainFacility'; +import RswtFacility from './layerPopup/rswtFacility'; +import HtdflFacility from './layerPopup/htdflFacility'; +import ReuseMngFacility from './layerPopup/reuseMngFacility'; + +interface NewWindowPopupProps { + featureData: any; + seletectedLayerNm?: string + onFeatureSelect: (data: any) => void; + onClose: () => void; +} + +const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ + featureData, + seletectedLayerNm, + onFeatureSelect, + onClose +}) => { + const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; + const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]); + const [activeTab, setActiveTab] = useState<string>('0'); + const [viewMode, setViewMode] = useState<'list' | 'detail' | 'nothing'>('list'); + const [fcltPk, setFcltPk] = useState<string>(''); + + + // onClose 함수를 useCallback으로 메모이제이션 + const memoizedOnClose = useCallback(() => { + onClose(); + }, [onClose]); + + // 빗물재이용시설 변수 + const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }); + const [rainList, setRainList] = useState<customRainBdst[]>([]); + + // 중수도시설 변수 + const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }); + const [rswtrList, setRswtrList] = useState<customRswtBdst[]>([]); + + // 온배수시설 변수 + const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false }); + const [htdflList, setHtdflList] = useState<customHtdflBdst[]>([]); + + // 하수처리시설 변수 + const [hasuList, setHasuList] = useState<pubHasuType[]>([]); + const [isRuseReg, setIsRuseReg] = useState<{ + data: any | undefined; + }>({ data: undefined }); + + // 수요처관리 변수 + const [isDmdModal, setIsDmdModal] = useState<{ + isMoal: boolean; + data: any | undefined; + }>({ isMoal: false, data: undefined }); + const [mngList, setMngList] = useState<DemandCompType[]>([]); + + ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// + const getSelectedLayers = () => { + console.trace('호출확인', 1) + let result = null; + const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 + let featureLayer: { get: (key: string) => any } = { get: () => null }; + layers.forEach((layer: any) => { + const source = layer.getSource(); + if (source && source.hasFeature && featureData + && typeof featureData.getId === 'function' && source.hasFeature(featureData)) { + featureLayer = layer; // 피처가 속한 레이어 찾기) + } + }); + result = featureLayer.get('lyrNm'); + return result; + }; + // 상세 목록 데이터 상태 처리 + useEffect(() => { + if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] }); + }, [hasuList]) + useEffect(() => { + if (rainList.length > 0) setRainDetailModal({ isOpen: true, data: rainList[0] }); + }, [rainList]) + useEffect(() => { + if (rswtrList.length > 0) setRswtrDetailModal({ isOpen: true, data: rswtrList[0] }); + }, [rswtrList]) + useEffect(() => { + if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] }); + }, [htdflList]) + useEffect(() => { + if (mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); + }, [mngList]) + + const lastCalledIdRef = useRef<string | null>(null); + const [layerId, setLayerId] = useState<string | null>(); + + useEffect(() => { + const layers = getSelectedLayers(); + setLayerId(layers); + }, []); // 최초 렌더링 시 호출 + + // 피쳐 데이터가 변경될 때마다 호출 + useEffect(() => { + if (!featureData || typeof featureData.getProperties !== "function" || featureData.getProperties() == '') return; + const selectedProps = featureData.getProperties(); + const currentId = featureData.getId?.() || selectedProps?.id || selectedProps?.fid; + // 유효하지 않은 ID는 아예 무시 + if (!currentId || typeof currentId !== 'string') return; + // 이전과 동일하면 무시 + if (!currentId || currentId === lastCalledIdRef.current) return; + lastCalledIdRef.current = currentId // 새로운 피처일 때만 API 호출 + const layerId = getSelectedLayers?.(); + if (!layerId || typeof layerId !== 'string') return; + const featureCd = + layerId === '하수재이용시설' ? selectedProps?.swtrmt_fclt_cd : + ['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm : + layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : + null; + if (!featureCd || featureCd === fcltPk) return; + if (layerId === '하수재이용시설') { + fetchpbnstDetail(featureCd, setTabData); + } else if (layerId === '하수처리시설') { + fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd).then((data) => { setHasuList(data) }) + } else { + detainSttn(featureCd, layerId).then((res) => { + if (layerId === '빗물이용시설') setRainList(res); + else if (layerId === '중수도시설') setRswtrList(res); + else if (layerId === '온배수시설') { setHtdflList(res); setHtdflDetailModal({ isOpen: true, data: res[0] }); } + else if (layerId === '수요처') setMngList(res); + }); + } + setFcltPk(featureCd); + setActiveTab(featureCd); + setViewMode("detail"); + }, [featureData]); + ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// + + // 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수 + const renderConditionalContent = () => { + const actTabData = tabData.find((tab) => tab.id === activeTab); + if (layerId === null || layerId === undefined) { + setLayerId(seletectedLayerNm || ''); + } + switch (layerId) { + case '하수재이용시설': + return (<ResueFacility viewMode={viewMode} seletectedLayerNm={layerId} tabData={tabData} actTabData={actTabData} setFcltPk={setFcltPk} onFeatureSelect={onFeatureSelect} onClose={onClose} />) + case '하수처리시설': + return (<HasuFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isRuseReg={isRuseReg} />) + case '빗물이용시설': + return (<RainFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} rainDetailModal={rainDetailModal} />) + case '중수도시설': + return (<RswtFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} rswtrDetailModal={rswtrDetailModal} />) + case '온배수시설': + return (<HtdflFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} htdflDetailModal={htdflDetailModal} />) + case '수요처': + return(<ReuseMngFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isDmdModal={isDmdModal} />) + default: + break; + } + return null; // 조건에 맞는 콘텐츠가 없을 경우 + }; + return ( + viewMode == 'nothing' ? <></> : + <NewWindowPortal + onClose={memoizedOnClose} + title="시설 상세 정보" + scrollbars='yes' + viewMode={viewMode} + resizable='no'> + {/* 체크된 레이어에 따른 조건부 렌더링 */} + {renderConditionalContent()} + </NewWindowPortal> + ); +}; + +export default NewWindowPopup; \ No newline at end of file diff --git a/apps/wr/app/map/newWindowPortal.tsx b/apps/wr/app/map/components/newWindowPortal.tsx similarity index 84% rename from apps/wr/app/map/newWindowPortal.tsx rename to apps/wr/app/map/components/newWindowPortal.tsx index b8da17d7b..119e3a52b 100644 --- a/apps/wr/app/map/newWindowPortal.tsx +++ b/apps/wr/app/map/components/newWindowPortal.tsx @@ -31,9 +31,10 @@ interface NewWindowPortalProps { height?: number; resizable?: string; scrollbars?: string; + viewMode?: 'list' | 'detail' | 'nothing'; } -const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "", width = 900, height = 800, resizable = "yes", scrollbars = "yes" }) => { +const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "", resizable = "yes", scrollbars = "yes", viewMode }) => { const [container, setContainer] = useState<HTMLElement | null>(null); const newWindowRef = useRef<Window | null>(null); const onCloseRef = useRef(onClose); @@ -46,7 +47,15 @@ const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, ti const handleUnload = useCallback(() => { onCloseRef.current(); }, []); - + useEffect(() => { + if (newWindowRef.current) { + if (viewMode == 'detail') { + newWindowRef.current.resizeTo(1100, 700); + } else { + newWindowRef.current.resizeTo(screen.availWidth, screen.availHeight); + } + } + }, [viewMode]); useEffect(() => { // 이미 창이 열려있으면 새로 열지 않음 if (newWindowRef.current && !newWindowRef.current.closed) return; @@ -57,11 +66,11 @@ const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, ti const screenHeight = window.innerHeight ?? document.documentElement.clientHeight; // 중앙 위치 계산 - const left = screenLeft + (screenWidth - width) / 2; - const top = screenTop + (screenHeight - height) / 2; + const left = screenLeft + (screenWidth - window.screen.width) / 2; + const top = screenTop + (screenHeight - window.screen.height) / 2; // 1. 새 창을 열고 참조를 저장합니다. - const popupOptions = `width=${width},height=${height},left=${left}, top=${top}, resizable=${resizable},scrollbars=${scrollbars}`; + const popupOptions = `width=${window.screen.width},height=${window.screen.height},left=${left}, top=${top}, resizable=${resizable},scrollbars=${scrollbars}`; newWindowRef.current = window.open('', title, popupOptions); if (newWindowRef.current) { diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 1e9afd74b..7b1d46c88 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -3,15 +3,16 @@ import Gis from "@common/business_components/ui/map/Gis"; import { useEffect, useRef, useState, useCallback } from "react"; import { mapService } from "../service"; -import NewWindowPopup from "../newWindowPopup"; +import NewWindowPopup from "../components/newWindowPopup"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; +import { SearchBar } from "../searchBar/searchBar"; const Map = () => { const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const gisMaps = useRef<any | null>(null); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 - const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const [featureData, setFeatureData] = useState<any>(null); // 선택된 피쳐 데이터 const [checkedLayers, setCheckedLayers] = useState<string[]>([]); const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]); @@ -63,25 +64,26 @@ const Map = () => { let foundFeature: any = null; let featureId = data.fid; const layerName = data.layerNm; - if (layerName === "tb_wr_bdst_ruse_fclt") { + console.log("선택된 레이어 이름:", data, layerName); + if (layerName === "하수재이용시설") { // 재이용시설 featureId = `${layerName}.${data.searchPk}`; - } else if ( //빗물이용시설, 중수도시설 - layerName === "tb_wr_rnwtr_utztn_fclt" || - layerName == "tb_wr_rswtr_fclt" + } else if ( + //빗물이용시설, 중수도시설 + layerName === "빗물이용시설" || + layerName == "중수도시설" ) { featureId = `${layerName}.${data.bdstInfoSn}`; - } else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설 + } else if (layerName === "하수처리시설") { //하수처리시설 featureId = `${layerName}.${data.bscFctCd}`; - } else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설 + } else if (layerName === "온배수시설") { //온배수시설 featureId = `${layerName}.${data.bdstRuseFcltSn}`; - } else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처 + } else if (layerName == "수요처") { //수요처 featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`; } else { console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName); featureId = `${layerName}.${data.fid || data.id || data.ftrIdn}`; } - map .getLayers() .getArray() @@ -111,7 +113,9 @@ const Map = () => { } else { console.warn("OpenLayers Feature를 찾을 수 없습니다:", data); } - + const currentId = data?.fid || data?.id || data?.ftrIdn; + const lastId = featureData?.fid || featureData?.id || featureData?.ftrIdn; + if (featureData && currentId === lastId) return; // 이전과 같으면 무시 setFeatureData(data); setIsOpen(true); setIsSearchModalOpen(false); @@ -127,7 +131,18 @@ const Map = () => { // /> // ); // }; - + const renderDetailView = useCallback( + (props: any) => ( + <NewWindowPopup + key={featureData?.fid || featureData?.id || featureData?.ftrIdn} + onClose={handlePopupClose} + checkedLayers={checkedLayers} + checkedLayersData={checkedLayersData} + {...props} + /> + ), + [checkedLayers, checkedLayersData] // 필요 시 의존성 넣기 + ); return ( <div className="bg-container"> <Gis @@ -136,24 +151,7 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - // searchView={(props: any) => ( - // <MapSearchBar - // searchType="공급처 후보지 조회" - // showApplyButton={false} - // checkedLayers={checkedLayers} - // checkedLayersData={checkedLayersData} - // {...props} - // /> - // ) - // } - detailView={(props: any) => ( - <NewWindowPopup - onClose={handlePopupClose} // 메모이제이션된 함수 사용 - checkedLayers={checkedLayers} - checkedLayersData={checkedLayersData} - {...props} - /> - )} + detailView={renderDetailView} onFeatureSelect={onFeatureSelect} setIsOpen={setIsOpen} setFeatureData={setFeatureData} @@ -163,6 +161,24 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} + searchBar={() => { + const facilityTypes = [ + { text: "하수처리시설", val: "tb_il_swage_prcs_fclt" }, + { text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" }, + { text: "빗물이용시설", val: "tb_wr_rnwtr_utztn_fclt" }, + { text: "중수도시설", val: "tb_wr_rswtr_fclt" }, + { text: "온배수시설", val: "tb_wr_htdfl_fclt" }, + { text: "수요처", val: "tb_wr_ruse_fclt_dmd_co" }, + { text: "잠재수요처", val: "tb_wr_tmpr_dmd_co" }, + ]; + return ( + <SearchBar + onFeatureSelect={onFeatureSelect} + facilityTypes={facilityTypes} + /> + ); + }} + waterDepth={undefined} /> </div> ); diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx deleted file mode 100644 index 973ad6aee..000000000 --- a/apps/wr/app/map/newWindowPopup.tsx +++ /dev/null @@ -1,795 +0,0 @@ -"use client" -import NewWindowPortal from '@/app/map/newWindowPortal'; -import { useWrStore } from "@/app/store"; -import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; -import type { customPbnst, pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; -import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; -import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; -import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; -import { fetchType, getFetch, postFectch } from "@/components/lib/api"; -import { formatPhoneNumber, tableData } from "@/components/lib/func/commom"; -import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal"; -import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; -import { Title, UITab, UiTable } from '@common/business_components'; -import { useGisContext } from "@common/business_components/ui/map/Gis"; -import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; -import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; -import moment from 'moment'; -import React, { useCallback, useEffect, useState } from 'react'; - -interface NewWindowPopupProps { - featureData: any; - seletectedLayerNm?: string - viewMode?: 'list' | 'detail' | 'nothing' - onFeatureSelect: (data: any) => void; - onClose: () => void; -} - -const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ - featureData, - seletectedLayerNm, - onFeatureSelect, - onClose -}) => { - const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; - const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]); - const [activeTab, setActiveTab] = useState<string>('0'); - const [viewMode, setViewMode] = useState<'list' | 'detail' | 'nothing'>('list'); - const [fcltPk, setFcltPk] = useState<string>(''); - - - // onClose 함수를 useCallback으로 메모이제이션 - const memoizedOnClose = useCallback(() => { - onClose(); - }, [onClose]); - - // 하수재이용시설 변수 - const { - code, - pbnstSttn, - pbnstSttnSearch, - pbnSttnInit, - getFlcts - } = useWrStore((state) => state); - const { fclts, pbnstSttnResCount } = pbnstSttn - const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>() - - // 빗물재이용시설 변수 - const { rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( - (state) => state - ); - const { rainSttnRes, rainSttnResCount } = rainSttn; - const [rainTable, setRainTable] = useState<customRainBdst[]>(); - const [rainPage, setRainPage] = useState(1) - const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }); - const [rainList, setRainList] = useState<customRainBdst[]>(rainSttnRes || []); - - // 중수도시설 변수 - const { rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore( - (state) => state - ); - const { rswtSttnRes, rswtSttnResCount } = rswtSttn; - const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>(); - const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }); - const [rswtrPage, setRswtrPage] = useState(1); - const [rswtrList, setRswtrList] = useState<customRswtBdst[]>(rswtSttnRes || []); - - // 온배수시설 변수 - const { htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state); - const { htdflSttnRes, htdflSttnResCount } = htdflSttn; - const [htdflTable, setHtdflTable] = useState<customHtdflBdst[]>(); - const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false }); - const [htdflPage, setHtdflPage] = useState(1) - const [htdflList, setHtdflList] = useState<customHtdflBdst[]>(htdflSttnRes || []); - - //하수처리시설 변수 - const { - pbnst, - pubHasuInit, - } = useWrStore((state) => state); - const { pubFacHeadList, pbnstHasuRes } = pbnst; - const [hasuList, setHasuList] = useState<pubHasuType[]>(pbnstHasuRes || []); - const [isRuseReg, setIsRuseReg] = useState<{ - data: any | undefined; - }>({ data: undefined }); - const [hasuTable, setHasuTable] = useState<pubHasuType[]>(); - const [hasuPage, setHasuPage] = useState<number>(1); - - // 수요처관리 변수 - const { - coSttn, - coSttnInit, - coSttnSearch, - // getDemandCompList, - // regDemandCompany, - // updateDemandCompany, - // coSttnTxtSearch, - // demandCompList, - } = useWrStore((state) => state); - const [mngTable, setMngTable] = useState<DemandCompType[]>(); - const [mngPage, setMngPage] = useState(1); - const { coSttnRes, coSttnResCount, coSttHeadList } = coSttn; - const [isDmdModal, setIsDmdModal] = useState<{ - isMoal: boolean; - data: any | undefined; - }>({ isMoal: false, data: undefined}); - const [mngList, setMngList] = useState<DemandCompType[]>(coSttnRes || []); - ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// - const getSelectedLayers = () => { - let result = null; - const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 - let featureLayer: { get: (key: string) => any } = { get: () => null }; - layers.forEach((layer: any) => { - const source = layer.getSource(); - if (source && source.hasFeature && featureData - && typeof featureData.getId === 'function' && source.hasFeature(featureData)) { - featureLayer = layer; // 피처가 속한 레이어 찾기) - } - }); - result = featureLayer.get('lyrNm'); - - return result; - }; - const fetchpbnstDetail = async (featureCd: string) => { - try { - const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`); - // 중복 데이터 추가 방지 - setTabDataFunc(response, featureCd); - } catch (error) { - console.error("Error fetching data:", error); - } - }; - const fetchHasuDetail = async (featureCd: string, data: string) => { - try { - const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/10/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON) - setHasuList( - rainRes.data.map((item: any) => { - return { - ...item, - fctCpc: Number(item.fctCpc) || 0, - ruse: item.ruse === false ? "N" : "Y", - }; - }) - ) - } catch (error) { - console.error("Error fetching data:", error); - } - }; - const detainSttn = async (featureCd: string, layerNm: string) => { - if (layerNm == 'tb_wr_rnwtr_utztn_fclt') { //빗물이용시설 - const res = await getFetch(fetchType.WR, `/reuseFc/rainyInfoList/1/20/0/${featureCd}`) - setRainList(res) - } - else if (layerNm == 'tb_wr_rswtr_fclt') { //중수도 시설 - const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`) - setRswtrList(res) - } - else if(layerNm =='tb_wr_ruse_fclt_dmd_co'){ //수요처 - const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`) - setMngList(res) - } - else { - const res = await getFetch(fetchType.WR, `/drain/facilityList/1/20/0/${featureCd}`) - setHtdflList(res) - setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] }); - } - } - // 상세 목록 데이터 상태 처리 - useEffect(() => { - if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] }); - }, [hasuList]) - useEffect(() => { - if (rainList.length > 0) setRainDetailModal({ isOpen: true, data: rainList[0] }); - }, [rainList]) - useEffect(() => { - if (rswtrList.length > 0) setRswtrDetailModal({ isOpen: true, data: rswtrList[0] }); - }, [rswtrList]) - useEffect(() => { - if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] }); - }, [htdflList]) - useEffect(() => { - if(mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); - },[mngList]) - - const layerId = getSelectedLayers(); - if (featureData && typeof featureData.getProperties === "function") { - const selectedVal = featureData.getProperties(); - let featureCd = layerId == '하수재이용시설' ? selectedVal?.swtrmt_fclt_cd : layerId == '빗물이용시설' || layerId == '중수도시설' || layerId == '온배수시설' || layerId == '수요처' ? selectedVal?.bdst_nm : layerId == '하수처리시설' ? selectedVal?.swtrmt_fclt_nm : null; - if (featureCd && featureCd !== fcltPk) { - if (layerId == '하수재이용시설') { - featureCd = selectedVal?.swtrmt_fclt_cd; - setFcltPk(featureCd); // 현재 호출된 featureCd 저장 - setActiveTab(featureCd) - fetchpbnstDetail(featureCd); // 상세정보 - } - else if (layerId == '하수처리시설') { - featureCd = selectedVal?.swtrmt_fclt_nm; - setFcltPk(featureCd); // 현재 호출된 featureCd 저장 - fetchHasuDetail(featureCd, selectedVal?.swtrmt_fclt_cd); - } - else { - featureCd = selectedVal?.bdst_nm; - setFcltPk(featureCd) - if (layerId) { - detainSttn(featureCd, layerId); - } - } - setViewMode("detail") - } - } else if (layerId === null && seletectedLayerNm) { setViewMode("list") } - ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// - - ///////////////////////////////////////////// 하수재이용시설 ////////////////////////////////////////////////// - // 재이용시설 cellClick 이벤트 (상세정보) - const pbnstCellClick = (index: number | undefined) => { - if ( - index !== undefined && - pbnstSttn.pbnstSttnRes && - pbnstSttn.pbnstSttnRes[index] - ) { - setFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltId); - onFeatureSelect(pbnstSttn.pbnstSttnRes[index]); - onClose() - } - }; - - // 재이용 현황 초기값 - useEffect(() => { - if (!pbnstSttn.isInit) { - getFlcts() - } - }, [seletectedLayerNm, pbnstSttn.isInit, getFlcts]); - useEffect(() => { - pbnSttnInit("1", "15", { - partValue: "A", - sidoValue: '0', - sigunValue: "0", - regionValue: "", - unitWaterShedValue: "", - middleRegionValue: "", - envPartValue: "", - sidoText: "", - sigunText: "" - }, String(new Date().getFullYear()), pbnstSttn.fclts); - }, [fclts, pbnSttnInit, pbnstSttn.fclts]) - useEffect(() => { - setBdstTable(pbnstSttn.pbnstSttnRes); - }, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]); - - // 재이용시설 상세정보 - const setTabDataFunc = (res: any, pk: string) => { - setTabData((prev) => { - if (prev.some((tab) => tab.id === pk)) { - return prev; // 이미 존재하는 탭이면 추가하지 않음 - } - return [...prev, { id: pk, name: `재이용시설 ${prev.length + 1}`, data: res }]; - }); - } - // 재이용시설 조회 - // 재이용시설 페이징처리 관련 함수 - const onPageChangeHandler = useCallback((page: number) => { - pbnstSttnSearch( - false, - String(page), - String(13), - { - partValue: "A", - sidoValue: "0", - sigunValue: "0", - regionValue: "", - unitWaterShedValue: "", - middleRegionValue: "", - envPartValue: "", - sidoText: "", - sigunText: "", - }, - "", - String(new Date().getFullYear()), fclts - ); - }, [fclts, pbnstSttnSearch]); - // FIXME: 윤선화차장님 요구사항으로 엑셀 다운로드 삭제. - // const excelClick = useCallback(() => { - // // if (codeData) pbnstSttnSearch(1, "10000", codeData); - // excelDownload( - // [[]], - // "하수재이용시설 현황", - // "sample_pbnstSttn.xlsx", - // "A1:U2" - // ); - // }, [pbnstSttnSearch]); - ///////////////////////////////////////////// 하수재이용시설 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// - // 빗물재이용시설 cellClick 이벤트 (상세정보) - const rainCellClick = (index: number | undefined, headId: string) => { - if ( - index !== undefined && - rainTable !== undefined && - headId !== 'chBuildingYn') { - // setRainDetailModal({ isOpen: true, data: rainTable[index] }) - // setViewMode('detail') - onFeatureSelect(rainTable[index]); - onClose(); - } - } - - - useEffect(() => { - if (!rainSttn.isInit) { - rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); - } - }, [code.cityCd, code.sggCd, rainSttn.isInit, rainSttnInit]); - - useEffect(() => { - const data = tableData(rainSttnRes, { code }); - const chData = data - .map((item: any) => { - return { - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0].rainyObj, - dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1 - ? "확인불가" - : item.reuseBuilding[0].rainyObj.dtfcltCpct, - instlCst: item.reuseBuilding[0].instlCst === -1 - ? "확인불가" - : item.reuseBuilding[0].instlCst, - instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1 - ? "확인불가" - : item.reuseBuilding[0].instlCstSprtAmt, - chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", - pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1 - ? "확인불가" - : item.reuseBuilding[0].rainyObj.pntfColArea, - filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1 - ? "확인불가" - : item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct, - archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea, - }; - }) - .filter((item: any) => item.rainyObj !== undefined); - setRainTable(chData); - }, [code, rainSttnRes, rainSttn.rainSttHeadList]); - - useEffect(() => { - rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd); - }, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]); - ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 중수도 시설 ////////////////////////////////////////////////// - // 중수도시설 cellClick 이벤트 (상세정보) - const rswtCellClick = (index: number | undefined, headId: string) => { - if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) { - // setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] }); - // setViewMode('detail') - onFeatureSelect(rswtrTable[index]); - onClose(); - } - } - useEffect(() => { - if (!rswtSttn.isInit) { - rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); - } - }, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]); - - useEffect(() => { - const data = tableData(rswtSttnRes, { code }); - const chData = data - .map((item: customRswtBdst) => { - return { - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0]?.rswtrObj, - chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N', - instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst, - instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt, - archArea: item?.archArea.split('.')[0] !== '-1' ? item?.archArea.split('.')[0] : '확인불가' - } - }) - .filter( - (item: customRswtBdst) => - item.reuseBuilding[0]?.rswtrObj !== undefined - ); - setRswtrTable(chData); - }, [code, rswtSttnRes]); - // FIXME: 윤선화 차장님 요구사항으로 엑셀 다운로드 삭제. - // const rswtSttnExcelDownload = useCallback(() => { - // if (rswtSttn.excelList.length > 0) { - // if (rswtSttn.excelList.length === 0) return; - // excelDownload( - // rswtSttn.excelList, - // "중수도시설 현황", - // "sample_rswtSttn.xlsx", - // "A1:W3", - // ); - // } - // }, [rswtSttn.excelList]) - - useEffect(() => { - rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd); - }, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]); - ///////////////////////////////////////////// 중수도 시설 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 온배수 시설 ////////////////////////////////////////////////// - // 온배수시설 cellClick 이벤트 (상세정보) - const htdflCellClick = (index: number | undefined) => { - if (index !== undefined && htdflTable !== undefined) { - onFeatureSelect(htdflTable[index]); - onClose(); - } - }; - useEffect(() => { - if (!htdflSttn.isInit) { - initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd); - } - }, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]); - - useEffect(() => { - const data = tableData(htdflSttnRes, { code }); - const chData = data - .map((item: any) => ({ - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0].htdflObj, - chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", - })) - .filter((item: any) => item.htdflObj !== undefined); - setHtdflTable(chData); - }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); - - useEffect(() => { - htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd); - }, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]); - ///////////////////////////////////////////// 온배수 시설 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 하수처리 시설 ////////////////////////////////////////////////// - useEffect(() => { - if (code.sggCd.length && pbnstHasuRes.length > 0) { - setHasuTable(tableData(pbnstHasuRes, { code })); - } - }, [code, pbnstHasuRes]); - useEffect(() => { - if (!pbnst.isInit) { - pubHasuInit("1", "0"); - } - }, [pbnst.isInit, pubHasuInit]); - useEffect(() => { - pubHasuInit(String(hasuPage), "0"); - }, [hasuPage, pubHasuInit]); - - const hasuCellClick = (index: number | undefined) => { - if (index !== undefined && pbnstHasuRes !== undefined) { - onFeatureSelect(pbnstHasuRes[index]); - onClose(); - } - }; - ///////////////////////////////////////////// 하수처리 시설 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// - useEffect(() => { - if (!coSttn.isInit) { - coSttnInit("1", "13", "0"); - } - }, [coSttn.isInit, coSttnInit]); - const coMngCellClick = (index: number | undefined) => { - if (index !== undefined && mngTable !== undefined) { - onFeatureSelect(mngTable[index]); - onClose(); - } - }; - useEffect(() => { - if (coSttnRes) { - if (coSttnRes?.length > 0 && code.cityCd.length > 0) { - const data = tableData(coSttnRes, { code }); - const chData = data.map((item: any) => ({ - ...item, - telno: formatPhoneNumber(item.telno || undefined), - regDt: moment(item.regDt).format("YYYY-MM-DD"), - dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}` - })); - // .filter((item: any) => item !== undefined); - setMngTable(chData); - } - } - }, [coSttnRes, code]); - useEffect(() => { - coSttnSearch(String(mngPage), "13", String(0)); - }, [coSttnSearch, mngPage]); - ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// - - // 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수 - const renderConditionalContent = () => { - const actTabData = tabData.find((tab) => tab.id === activeTab); - let layerId: string | undefined | null = getSelectedLayers(); - if (layerId === null || layerId === undefined) { - layerId = seletectedLayerNm || ''; - } - //하수처리시설 - const isPrcsFclt = layerId == 'tb_il_swage_prcs_fclt' - // 재이용시설 - const isPbnst = layerId == 'tb_wr_bdst_ruse_fclt'; - // 빗물이용시설 - const isRain = layerId == 'tb_wr_rnwtr_utztn_fclt'; - // 중수도시설 - const isRswt = layerId == 'tb_wr_rswtr_fclt'; - // 온배수시설 - const isHtdfl = layerId == 'tb_wr_htdfl_fclt'; - // 수요처관리 - const isReuseMng = layerId == 'tb_wr_ruse_fclt_dmd_co'; - if (isPrcsFclt) { - if (viewMode == 'list') { - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="하수처리시설 현황" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={hasuTable || []} - headlist={pubFacHeadList} - pageSize={10} - total={5000} - headName={""} - cellClick={hasuCellClick} - onPageChange={setHasuPage} - /> - </div> - ) - } else { - return ( - <div style={{ overflowX: 'hidden' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="하수처리시설 상세정보" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={isRuseReg?.data || []} - headlist={pubFacHeadList.slice(0, 4)} - pageSize={1} - total={0} - headName={"위치정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - <UiTable - tableData={isRuseReg?.data || []} - headlist={pubFacHeadList.slice(4)} - pageSize={1} - total={0} - headName={"기본정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - </div> - ) - } - } - if (isPbnst) { - if (viewMode == 'detail') { - // 재이용시설 상세 - return ( - <> - <div className="flex justify-between items-center mb-4"> - <Title title="하수재이용시설 상세정보" /> - </div> - <div className="flex justify-between items-center mb-8"> - <UITab - defaultValue={activeTab} - tabList={tabData.map((tab) => ({ - value: tab.id, - label: tab.name, - onClick: () => setActiveTab(tab.id), - }))} - /> - </div> - <PbnstSttnModal - data={actTabData?.data[0] || ({} as customPbnst)} - titleVisible={false} - /> - </> - ); - } else { - // 재이용시설 목록 - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center"> - <Title title="하수재이용시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={excelClick}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={bdstTable || []} - headlist={pbnstSttn.pbnstSttHeadList} - pageSize={20} - total={pbnstSttnResCount || 0} - cellClick={(index: number | undefined) => { - pbnstCellClick(index); - }} - onPageChange={onPageChangeHandler} - > - <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> - </UiTable> - </div> - ); - } - } - - if (isRain) { - if (viewMode == 'detail') { - // 빗물이용시설 상세 - return ( - <SttnDetailModal type={"RNB"} formObj={rainDetailModal.data as customRainBdst} /> - ); - } else { - // 빗물이용시설 목록 - return ( - <> - <div className="flex justify-between items-center"> - <Title title="빗물이용시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={excelClick}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={rainTable || []} - headlist={rainSttn.rainSttHeadList} - pageSize={20} - total={rainSttnResCount} - cellClick={(index: number | undefined, headId: string) => { - rainCellClick(index ?? undefined, headId); - }} - onPageChange={setRainPage} - > - <HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} /> - </UiTable> - </> - ); - } - } - - if (isRswt) { - if (viewMode == 'detail') { - // 중수도시설 상세 - return ( - <SttnDetailModal type={"RST"} formObj={rswtrDetailModal.data as customRswtBdst} /> - ); - } else { - // 중수도시설 목록 - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center"> - <Title title="중수도시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={rswtSttnExcelDownload}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={rswtrTable || []} - headlist={rswtSttn.rswtSttHeadList} - pageSize={20} - total={rswtSttnResCount} - cellClick={(index: number | undefined, headId: string) => { - rswtCellClick(index, headId); - }} - onPageChange={setRswtrPage} - > - <HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} /> - </UiTable> - </div> - ); - } - } - - if (isHtdfl) { - if (viewMode == 'detail') { - // 온배수시설 상세 - return ( - <SttnDetailModal type={"HTB"} formObj={htdflDetailModal.data as customHtdflBdst} /> - ); - } else { - // 온배수시설 목록 - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center"> - <Title title="온배수시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={excelClick}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={htdflTable || []} - headlist={htdflSttn.htdflSttHeadList} - pageSize={20} - total={htdflSttnResCount || 0} - cellClick={(index: number | undefined) => { - htdflCellClick(index ?? undefined); - }} - onPageChange={setHtdflPage} - > - <HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} /> - </UiTable> - </div> - ); - } - } - if (isReuseMng) { - if (viewMode == 'detail') { - return ( - <div style={{ overflowX: 'hidden' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="수요처 상세정보" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={isDmdModal?.data || []} - headlist={coSttHeadList.slice(0, 3)} - pageSize={1} - total={0} - headName={"위치정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - <UiTable - tableData={isDmdModal?.data || []} - headlist={coSttHeadList.slice(3)} - pageSize={1} - total={0} - headName={"기본정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - </div> - ) - } else if(viewMode == 'list') { - return ( <div> - <div className="flex justify-between items-center"> - <Title title="수요처 현황" /> - </div> - <UiTable - headName={[]} - tableData={mngTable || []} - headlist={coSttHeadList} - pageSize={20} - total={coSttnResCount + 10 || 0} - cellClick={(index: number | undefined) => { - coMngCellClick(index); - // setUpdateData(coSttnRes[index as number]); - }} - onPageChange={(page) => { - setMngPage(page); - }} - ></UiTable></div>) - } - } - return null; // 조건에 맞는 콘텐츠가 없을 경우 - }; - - return ( - viewMode == 'nothing' ? <></> : - <NewWindowPortal - onClose={memoizedOnClose} - title="시설 상세 정보" - width={viewMode == 'list' ? window.screen.availWidth : 1100} - height={viewMode == 'list' ? window.screen.availHeight : layerId == '하수처리시설' && viewMode == 'detail' ? 450 : 900} - scrollbars='yes' - resizable='no'> - {/* 체크된 레이어에 따른 조건부 렌더링 */} - {renderConditionalContent()} - </NewWindowPortal> - ); -}; - -export default NewWindowPopup; \ No newline at end of file diff --git a/apps/wr/app/map/popup/page.tsx b/apps/wr/app/map/popup/page.tsx index 80830aa90..c7e091bb4 100644 --- a/apps/wr/app/map/popup/page.tsx +++ b/apps/wr/app/map/popup/page.tsx @@ -3,7 +3,7 @@ import Gis from "@common/business_components/ui/map/Gis"; import { useEffect, useRef, useState, useCallback } from "react"; import { mapService } from "../service"; -import NewWindowPopup from "../newWindowPopup"; +import NewWindowPopup from "../components/newWindowPopup"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; const Map = () => { @@ -123,16 +123,6 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - // searchView={(props: any) => ( - // <MapSearchBar - // searchType="공급처 후보지 조회" - // showApplyButton={false} - // checkedLayers={checkedLayers} - // checkedLayersData={checkedLayersData} - // {...props} - // /> - // ) - // } detailView={(props: any) =>( <NewWindowPopup onClose={handlePopupClose} // 메모이제이션된 함수 사용 @@ -150,6 +140,8 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} + searchBar={undefined} + waterDepth={undefined} /> </div> ); diff --git a/apps/wr/app/map/searchBar/SearchInputText.tsx b/apps/wr/app/map/searchBar/SearchInputText.tsx new file mode 100644 index 000000000..caf4c9f2a --- /dev/null +++ b/apps/wr/app/map/searchBar/SearchInputText.tsx @@ -0,0 +1,122 @@ +// SearchInputText.tsx +"use client"; +import { Input } from "@common/components/ui"; +import { useEffect, useRef, useState } from "react"; + +interface SearchInputTextProps { + initValue?: string; + setValue: (val: string) => void; + setRtnData: (val: { bscFctNm: string; bscFctCd: string }) => void; + searchList: { bscFctNm: string; bscFctCd: string }[]; + onEnter?: () => void; + className?: string; +} + +export default function SearchInputText({ + initValue, + setValue, + setRtnData, + searchList, + onEnter, + className, +}: SearchInputTextProps) { + const [showResults, setShowResults] = useState(false); + const [text, setText] = useState(initValue ?? ""); + const [highlightIndex, setHighlightIndex] = useState(-1); + const wrapperRef = useRef(null); + + useEffect(() => { + setText(initValue ?? ""); + }, [initValue]); + + useEffect(() => { + if (searchList.length > 0) { + setShowResults(true); + } else { + setShowResults(false); + } + setHighlightIndex(-1); + }, [searchList]); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + wrapperRef.current && + !(wrapperRef.current as any).contains(event.target) + ) { + setShowResults(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [wrapperRef]); + + const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { + setText(e.target.value); + setValue(e.target.value); + setHighlightIndex(-1); + }; + + const handleSelect = (item: { bscFctNm: string; bscFctCd: string }) => { + setText(item.bscFctNm); + setValue(item.bscFctNm); + setRtnData(item); + setShowResults(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (e.key === "Enter") { + if (highlightIndex >= 0 && highlightIndex < searchList.length) { + const selected = searchList[highlightIndex]; + if (selected) handleSelect(selected); + } else if (onEnter) { + onEnter(); + } + } else if (e.key === "ArrowDown") { + e.preventDefault(); + setHighlightIndex((prev) => + prev < searchList.length - 1 ? prev + 1 : prev + ); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setHighlightIndex((prev) => (prev > 0 ? prev - 1 : -1)); + } + }; + + return ( + <div className="relative" ref={wrapperRef}> + <Input + value={text} + onChange={handleChange} + onKeyDown={handleKeyDown} + placeholder="시설명을 입력하세요" + className={ + className + " rounded-lg border px-2 py-1 text-sm h-[29.5px]" + } + /> + {showResults && ( + <div className="absolute z-10 mt-1 max-h-200 w-full overflow-auto rounded border bg-white shadow"> + {searchList.length > 0 ? ( + searchList.map((item, index) => ( + <div + key={index} + className={`cursor-pointer px-3 py-1 hover:bg-gray-100 ${ + highlightIndex === index ? "bg-blue-100" : "" + }`} + onClick={() => handleSelect(item)} + > + {item.bscFctNm} + </div> + )) + ) : ( + <div className="px-3 py-2 text-sm text-gray-500"> + 검색 결과가 없습니다. + </div> + )} + </div> + )} + </div> + ); +} diff --git a/apps/wr/app/map/searchBar/comboSearchBar.tsx b/apps/wr/app/map/searchBar/comboSearchBar.tsx new file mode 100644 index 000000000..15736d710 --- /dev/null +++ b/apps/wr/app/map/searchBar/comboSearchBar.tsx @@ -0,0 +1,298 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; + +const geoserverUrl = + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; + +const FACILITY_TYPE_URL_MAP: Record<string, string> = { + tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설 + tb_wr_bdst_ruse_fclt: + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post + tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get + tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get + tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get + tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get + tb_wr_tmpr_dmd_co: + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get +}; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject<any>; + facilityTypes: { text: string; val: string }[]; +} + +export function SearchBar({ + onFeatureSelect, + gisMaps, + facilityTypes, +}: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + + const [facilityData, setFacilityData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [facilityValue, setFacilityValue] = useState("0"); + + const [facilityType, setFacilityType] = useState( + facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" + ); + + //시도, 시군구 조회 + const fetchSido = async () => { + const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`; + const res = await fetch(url); + const json = await res.json(); + const sidoList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length === 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSidoData([{ text: "선택", val: "0" }, ...sidoList]); + + const sigunList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSigunData(sigunList); + }; + + //시설명 콤보박스 조회 + const fetchFacilitiesBySggNm = async ( + sggCd: string, + sggNm: string, + sidoNm: string, + currentFacilityType?: string + ) => { + const type = currentFacilityType ?? facilityType; + if (!type || type === "0") return; + + const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; + if (!baseUrlTemplate) return; + + const year = new Date().getFullYear().toString(); + let url = baseUrlTemplate + .replace("{year}", year) + .replace("{sggCd}", sggCd) + .replace("{sidoNm}", encodeURIComponent(sidoNm)) + .replace("{sggNm}", encodeURIComponent(sggNm)); + + let list: SelectDataType[] = []; + + if ( + type === "tb_il_swage_prcs_fclt" //하수처리시설 + ) { + const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`; + url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`; + const res = await fetch(url); + const json = await res.json(); + list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, + })); + } else if ( + type === "tb_wr_bdst_ruse_fclt" // 재이용시설 + ) { + //재용에서 참조하는 하수처리시설 목록 + const paramsRes = await fetch("/reuse-service/reuseFc/pubFc"); + const paramsJson = await paramsRes.json(); + const params = paramsJson.data; + + const res = await fetch(url, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + }); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.ruseUtztnFcltNm ?? "(이름없음)", + val: item.pbnstRuseFcltId, + })); + } else if ( + type === "tb_wr_ruse_fclt_dmd_co" //수요처 + ) { + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.dmdInfoId, + })); + } else if ( + type === "tb_wr_tmpr_dmd_co" //잠재수요처 + ) { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.fctryMngSn, + })); + } else { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.bdstNm ?? "(이름없음)", + val: item.bdstInfoSn, + })); + } + + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + //시도, 시군구 초기 데이터 조회 + useEffect(() => { + fetchSido(); + }, []); + + //시군구 데이터 초기화 + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + //콤보박스 선택시 연관 콤보박스 처리 + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + setFacilityValue("0"); + } else if (type === "sggCd") { + setSigunValue(val); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + const sggCd = sigunData.find((item) => item.val === val)?.val || ""; + if (sggNm && sidoNm) + fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType); + } else if (type === "facilityType") { + setFacilityType(val); + setFacilityValue("0"); + setFacilityData([{ text: "선택", val: "0" }]); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = + sigunData.find((item) => item.val === sigunValue)?.text || ""; + const sggCd = + sigunData.find((item) => item.val === sigunValue)?.val || ""; + if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData, sigunValue, sidoData, sidoValue] + ); + + //조회 버튼 클릭 + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + {facilityTypes.length > 1 && ( + <SelectBox + labelClass="w-[120px]" + className="mr-10 w-[190px]" + selectClass="rounded-lg bg-white" + label="시설물유형" + selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]} + onSelectValue={(val) => + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + <SelectBox + labelClass="w-[40px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + labelClass="w-[60px]" + className="mr-3 w-[180px]" + selectClass="rounded-lg bg-white" + label="시설명" + selectArray={facilityData} + onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1 ml-5" + size={"sm"} + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/wr/app/map/searchBar/procSearchBar.tsx b/apps/wr/app/map/searchBar/procSearchBar.tsx new file mode 100644 index 000000000..15736d710 --- /dev/null +++ b/apps/wr/app/map/searchBar/procSearchBar.tsx @@ -0,0 +1,298 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; + +const geoserverUrl = + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; + +const FACILITY_TYPE_URL_MAP: Record<string, string> = { + tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설 + tb_wr_bdst_ruse_fclt: + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post + tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get + tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get + tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get + tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get + tb_wr_tmpr_dmd_co: + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get +}; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject<any>; + facilityTypes: { text: string; val: string }[]; +} + +export function SearchBar({ + onFeatureSelect, + gisMaps, + facilityTypes, +}: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + + const [facilityData, setFacilityData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [facilityValue, setFacilityValue] = useState("0"); + + const [facilityType, setFacilityType] = useState( + facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" + ); + + //시도, 시군구 조회 + const fetchSido = async () => { + const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`; + const res = await fetch(url); + const json = await res.json(); + const sidoList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length === 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSidoData([{ text: "선택", val: "0" }, ...sidoList]); + + const sigunList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSigunData(sigunList); + }; + + //시설명 콤보박스 조회 + const fetchFacilitiesBySggNm = async ( + sggCd: string, + sggNm: string, + sidoNm: string, + currentFacilityType?: string + ) => { + const type = currentFacilityType ?? facilityType; + if (!type || type === "0") return; + + const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; + if (!baseUrlTemplate) return; + + const year = new Date().getFullYear().toString(); + let url = baseUrlTemplate + .replace("{year}", year) + .replace("{sggCd}", sggCd) + .replace("{sidoNm}", encodeURIComponent(sidoNm)) + .replace("{sggNm}", encodeURIComponent(sggNm)); + + let list: SelectDataType[] = []; + + if ( + type === "tb_il_swage_prcs_fclt" //하수처리시설 + ) { + const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`; + url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`; + const res = await fetch(url); + const json = await res.json(); + list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, + })); + } else if ( + type === "tb_wr_bdst_ruse_fclt" // 재이용시설 + ) { + //재용에서 참조하는 하수처리시설 목록 + const paramsRes = await fetch("/reuse-service/reuseFc/pubFc"); + const paramsJson = await paramsRes.json(); + const params = paramsJson.data; + + const res = await fetch(url, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + }); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.ruseUtztnFcltNm ?? "(이름없음)", + val: item.pbnstRuseFcltId, + })); + } else if ( + type === "tb_wr_ruse_fclt_dmd_co" //수요처 + ) { + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.dmdInfoId, + })); + } else if ( + type === "tb_wr_tmpr_dmd_co" //잠재수요처 + ) { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.fctryMngSn, + })); + } else { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.bdstNm ?? "(이름없음)", + val: item.bdstInfoSn, + })); + } + + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + //시도, 시군구 초기 데이터 조회 + useEffect(() => { + fetchSido(); + }, []); + + //시군구 데이터 초기화 + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + //콤보박스 선택시 연관 콤보박스 처리 + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + setFacilityValue("0"); + } else if (type === "sggCd") { + setSigunValue(val); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + const sggCd = sigunData.find((item) => item.val === val)?.val || ""; + if (sggNm && sidoNm) + fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType); + } else if (type === "facilityType") { + setFacilityType(val); + setFacilityValue("0"); + setFacilityData([{ text: "선택", val: "0" }]); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = + sigunData.find((item) => item.val === sigunValue)?.text || ""; + const sggCd = + sigunData.find((item) => item.val === sigunValue)?.val || ""; + if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData, sigunValue, sidoData, sidoValue] + ); + + //조회 버튼 클릭 + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + {facilityTypes.length > 1 && ( + <SelectBox + labelClass="w-[120px]" + className="mr-10 w-[190px]" + selectClass="rounded-lg bg-white" + label="시설물유형" + selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]} + onSelectValue={(val) => + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + <SelectBox + labelClass="w-[40px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + labelClass="w-[60px]" + className="mr-3 w-[180px]" + selectClass="rounded-lg bg-white" + label="시설명" + selectArray={facilityData} + onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1 ml-5" + size={"sm"} + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/wr/app/map/searchBar/searchBar.tsx b/apps/wr/app/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..7760ee0b8 --- /dev/null +++ b/apps/wr/app/map/searchBar/searchBar.tsx @@ -0,0 +1,302 @@ +// SearchBar.tsx +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import SearchInputText from "./SearchInputText"; + +const FACILITY_TYPE_URL_MAP: Record<string, string> = { + tb_il_swage_prcs_fclt: + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&propertyName=swtrmt_fclt_cd,swtrmt_fclt_nm&cql_filter=", + tb_wr_bdst_ruse_fclt: + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000/", + tb_wr_rnwtr_utztn_fclt: + "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}/{fcltyNm}", + tb_wr_rswtr_fclt: + "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}/{fcltyNm}", + tb_wr_htdfl_fclt: + "/reuse-service/drain/facilityList/1/1000/{sggCd}/{fcltyNm}", + tb_wr_ruse_fclt_dmd_co: + "/reuse-service/reuseFc/request/1/1000/{sggCd}/{fcltyNm}", + tb_wr_tmpr_dmd_co: + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}/{fcltyNm}", +}; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + // gisMaps: React.RefObject<any>; + facilityTypes: { text: string; val: string }[]; +} + +export function SearchBar({ + onFeatureSelect, + // gisMaps, + facilityTypes, +}: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [facilityType, setFacilityType] = useState( + facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" + ); + const [fcltyNm, setFcltyNm] = useState(""); + const [searchList, setSearchList] = useState< + { bscFctNm: string; bscFctCd: string }[] + >([]); + + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "시도", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "시군구", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "시군구", val: "0" }, + ]); + + const fetchSido = async () => { + const res = await fetch("/cms-service/api/v1/code/cache/ADSTDSTRCD/Y"); + const json = await res.json(); + const sidoList = json + .filter((item: any) => item.lwpoCd?.length === 2) + .map((item: any) => ({ val: item.lwpoCd, text: item.lwpoCdNm })); + const sigunList = json + .filter((item: any) => item.lwpoCd?.length > 2) + .map((item: any) => ({ val: item.lwpoCd, text: item.lwpoCdNm })); + setSidoData([{ text: "시도", val: "0" }, ...sidoList]); + setSigunData(sigunList); + }; + + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "시도", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "시군구", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + useEffect(() => { + fetchSido(); + }, []); + + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setSearchList([]); + setFcltyNm(""); + } else if (type === "sggCd") { + setSigunValue(val); + setSearchList([]); + setFcltyNm(""); + } else if (type === "facilityType") { + setFacilityType(val); + setSearchList([]); + setFcltyNm(""); + } + }, + [initializeSigunData] + ); + + const handleSearch = async () => { + if (!fcltyNm) return alert("시설명을 입력하세요."); + + const sidoNm = sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === sigunValue)?.text || ""; + + const type = facilityType; + const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; + if (!baseUrlTemplate) return; + + let url = baseUrlTemplate; + const year = new Date().getFullYear().toString(); + url = url + .replace("{year}", year) + .replace("{sggCd}", sigunValue) + .replace("{sidoNm}", encodeURIComponent(sidoNm)) + .replace("{sggNm}", encodeURIComponent(sggNm)) + .replace("{fcltyNm}", encodeURIComponent(fcltyNm)); + + let list: SelectDataType[] = []; + + if (type === "tb_il_swage_prcs_fclt") { + const cqlFilter = `ctpv_nm='${sidoNm}' and sgg_nm='${sggNm}' and swtrmt_fclt_nm like '%${fcltyNm}%'`; + url += encodeURIComponent(cqlFilter); + const res = await fetch(url); + const json = await res.json(); + list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, + })); + } else if ( + type === "tb_wr_bdst_ruse_fclt" // 재이용시설 + ) { + //재이용에서 참조하는 하수처리시설 목록 + const procRes = await fetch("/reuse-service/reuseFc/pubFc"); + const procJson = await procRes.json(); + const procData = procJson.data; + const procResult = procData + .filter((code: string) => code.slice(0, 5) === sigunValue) + .map((code: string) => { + return code; + }); + + //처리시설명으로 처리시설코드 조회 + let procUrl2 = + "/operation-service/prcsFclt/ruseInfo/1/1000/A/{sggCd}/0/1/SW/{fcltyNm}"; + procUrl2 = procUrl2 + .replace("{sggCd}", sigunValue) + .replace("{fcltyNm}", encodeURIComponent(fcltyNm)); + const procRes2 = await fetch(procUrl2, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(procResult), + }); + const procJson2 = await procRes2.json(); + const procResult2 = (procJson2.data || []).map( + (item: any) => item.bscFctCd + ); + + //최종 처리시설코드로 재이용시설조회 + const res = await fetch(url, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(procResult2), + }); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.ruseUtztnFcltNm ?? "(이름없음)", + val: item.pbnstRuseFcltId, + })); + } else if ( + type === "tb_wr_ruse_fclt_dmd_co" //수요처 + ) { + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.dmdInfoId, + })); + } else if ( + type === "tb_wr_tmpr_dmd_co" //잠재수요처 + ) { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.fctryMngSn, + })); + } else { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.bdstNm ?? "(이름없음)", + val: item.bdstInfoSn, + })); + } + + if (list.length === 0) { + alert("검색 결과가 없습니다."); + setSearchList([]); + setFcltyNm(""); + return; + } + + const newList = list.map((item: any) => { + const code = item.val; + const name = item.text; + return { bscFctNm: name, bscFctCd: code }; + }); + setSearchList(newList); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + {facilityTypes.length > 1 && ( + <SelectBox + labelClass="w-[120px]" + className="mr-5 w-[150px]" + selectClass="rounded-lg bg-white" + label="" + selectArray={[ + { text: "시설 구분", val: "0" }, + ...facilityTypes, + ]} + onSelectValue={(val) => + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + <SelectBox + labelClass="w-[40px]" + className="mr-5 w-[150px]" + selectClass="rounded-lg bg-white" + label="" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-5 w-[150px]" + selectClass="rounded-lg bg-white" + label="" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SearchInputText + initValue={fcltyNm} + setValue={(val: string = "") => { + setFcltyNm(val); + }} + setRtnData={(selected: { + bscFctNm: string; + bscFctCd: string; + }) => { + // 선택된 항목이 있을 경우 onFeatureSelect 실행 + const type = facilityType; + onFeatureSelect({ + layerNm: type, + ...selected, + }); + }} + searchList={searchList} + onEnter={handleSearch} + className="mr-5 w-[150px]" + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1" + size="sm" + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/wr/app/map/util.ts b/apps/wr/app/map/util.ts new file mode 100644 index 000000000..e3428b300 --- /dev/null +++ b/apps/wr/app/map/util.ts @@ -0,0 +1,58 @@ +import { getFetch, postFectch, fetchType } from "@/components/lib/api"; + +const fetchpbnstDetail = async (featureCd: string, setTabData: any) => { + try { + const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`); + // 중복 데이터 추가 방지 + setTabDataFunc(response, featureCd, setTabData); + } catch (error) { + console.error("Error fetching data:", error); + } +}; +const fetchHasuDetail = async (featureCd: string, data: string) => { + try { + const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/20/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON) + // setHasuList + const filteredData = rainRes.data.map((item: any) => { + return { + ...item, + fctCpc: Number(item.fctCpc) || 0, + ruse: item.ruse === false ? "N" : "Y", + }; + }) + return filteredData + } catch (error) { + console.error("Error fetching data:", error); + } +}; +// 재이용시설 상세정보 +const setTabDataFunc = (res: any, pk: string, setTabData: any) => { + setTabData((prev) => { + if (prev.some((tab) => tab.id === pk)) { + return prev; // 이미 존재하는 탭이면 추가하지 않음 + } + return [...prev, { id: pk, name: `재이용시설 ${prev.length + 1}`, data: res }]; + }); +} + +const detainSttn = async (featureCd: string, layerNm: string) => { + if (layerNm == '빗물이용시설') { //빗물이용시설 + const res = await getFetch(fetchType.WR, `/reuseFc/rainyInfoList/1/20/0/${featureCd}`) + return res + } + else if (layerNm == '중수도시설') { //중수도 시설 + const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`) + return res + } + else if (layerNm == '수요처') { //수요처 + const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`) + return res + } + else { + const res = await getFetch(fetchType.WR, `/drain/facilityList/1/20/0/${featureCd}`) + return res + // setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] }); + } +} + +export { fetchHasuDetail, fetchpbnstDetail, detainSttn }; \ No newline at end of file diff --git a/apps/wr/app/matMng/coMat/coMatRvw/page.tsx b/apps/wr/app/matMng/coMat/coMatRvw/page.tsx index ff35b95c5..996b624d8 100644 --- a/apps/wr/app/matMng/coMat/coMatRvw/page.tsx +++ b/apps/wr/app/matMng/coMat/coMatRvw/page.tsx @@ -376,87 +376,116 @@ export default function CoMatRvw() { <div className="col-span-12 w-full"> <h1 className={formH1Class}>요구수질</h1> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="grssClfg" render={({ field }) => ( - <FormUi label="총대장균군수 (개/100ml)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label={"총대장균군수(개/mg)"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + value={ + field.value != -1 + ? `${field.value}` + : "불검출" + } + readOnly + /> + {/* TODO: 총대장균군수 확인 필요*/} + {field.value != -1 ? ( + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + ) : ( + <></> + )} + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="combResdch" render={({ field }) => ( - <FormUi label="결합잔류염소(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label={"결합잔류염소(mg/L)"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + value={field.value ?? ""} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이상 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="trbt" render={({ field }) => ( <FormUi label="탁도(NTU)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + value={field.value ?? 0} + readOnly + /> + <span className=" text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="blgcOxygDmndQnt" render={({ field }) => ( <FormUi label="생물학적 산소요구량(BOD)(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + step="0.1" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? ""} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="smll" @@ -464,138 +493,172 @@ export default function CoMatRvw() { <FormUi label="냄새"> <Input placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} + type="text" + value={field.value ?? ""} readOnly /> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="crmty" render={({ field }) => ( <FormUi label="색도(도)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + step="0.1" + value={field.value ?? ""} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="tn" render={({ field }) => ( - <FormUi label="총질소 (T-N)(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label="총질소(T-N)(mg/L)"> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="tph" render={({ field }) => ( <FormUi label="총인(T-P)(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="ph" render={({ field }) => ( - <FormUi label="수소이온농도(pH)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label="수소이온농도(PH)"> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="clIonQnt" render={({ field }) => ( <FormUi label="염화물(mgCL/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="cdtnv" render={({ field }) => ( <FormUi label="전기전도도(㎲/cm)"> - <Input - placeholder="" - type="number" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="rmrk" @@ -648,7 +711,7 @@ export default function CoMatRvw() { form={form} type={"mat"} selectionVersion={selectionVersion} - readonly={regAuthrtYn ==="N"} + readonly={regAuthrtYn === "N"} /> </ListDiv> {/* </ScrollArea> */} diff --git a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx index e45a0a0f2..813b4e23f 100644 --- a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx @@ -17,35 +17,45 @@ import { UiModal, UiTable, type SelectDataType, + type SheetParameter, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useEffect, useState } from "react"; -import { Toaster } from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; +import { useTranslation } from "react-i18next"; export default function RuseCaseMng() { - const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state); - const { ruseCaseMngList, status, count } = ruseCaseMng; + const { code, ruseCaseMng, selectRuseCaseMng, ruseCaseMngInit } = useWrStore((state) => state); + const { ruseCaseMngList, status, count, excelList } = ruseCaseMng; const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([]); const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); - + const [sidoCd, setSidoCd] = useState<string>(""); const [sigunguCd, setSigunguCd] = useState<string>(""); - const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]); + // const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]); + const [selectUtz, setSelectUtz] = useState<string>(""); const [name, setName] = useState<string>(""); + const [resetPage, isResetPage] = useState(false); + const { t } = useTranslation('base'); // 물재이용사례 조회 - const searchRuseCaseMng = async (pageNo: number, pageSize: number) => { + const searchRuseCaseMng = async (isExcel: boolean, pageNo: number, pageSize: number) => { await selectRuseCaseMng( + isExcel, pageNo, pageSize, - sidoCd ?? "", - sigunguCd ?? "", - selectUtz[0] ?? "", + sidoCd ?? "", + sigunguCd ?? "", + selectUtz ?? "", name ?? "" ); } + useEffect(() => { + ruseCaseMngInit(); + }, []) + // 시도 선택 const getSelCity = (val: string | number) => { const cityValue = String(val); @@ -69,91 +79,97 @@ export default function RuseCaseMng() { isModal: boolean; }>({ isModal: false }); - // 다운로드 - const download = () => { - searchRuseCaseMng(1, 10000);; - + + useEffect(() => { + + if (excelList !== undefined && excelList.length === 0) { + toast.error(t('excelDownErrMsg')); + return; + } fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => { - if (!response.ok) { - throw Error(response.statusText); - } - // 받은 파일 바인너리 처리 - const blobRet = await response.arrayBuffer(); - - const excelData1 = ruseCaseMngList.map((item) => { - return [ - item.ruseBizNm, - item.bizBgngDate, - item.bizEndDate, - item.splyFcltKnd, - item.splyFcltNm, - item.splyFcltCpct, - item.dmdFcltNm, - item.dmdFcltAddr, - item.ruseFcltCpct, - item.ruseFcltPplLen, - item.ruseFcltUsg, - item.ruseFcltSplyCpct, - item.ruseFcltPrcsMsrc, - item.grnteWqUsg, - item.grnteWqGrssClfg, - item.grnteWqCombResdch, - item.grnteWqTrbt, - item.grnteWqBod, - item.grnteWqSmll, - item.grnteWqCrmty, - item.grnteWqTn, - item.grnteWqTp, - item.grnteWqPh, - item.grnteWqClIonQnt, - item.grnteWqCndctStat, - item.grnteWqRmrk, - item.reqBizGrssCst, - item.reqBizReuseFcltCst, - item.reqBizPplCst, - item.reqBizArchCst, - item.reqBizMchnCst, - item.reqBizMtrngCst, - item.reqBizGisCst, - item.reqBizCctvCst, - item.reqBizCmcnCst, - item.reqBizWstCst, - item.reqBizDesignCst, - item.reqBizSpvsCst, - item.reqBizSbsdCst, - item.reqBizEtc, - item.prcmPlanGrssCst, - item.prcmPlanNlep, - item.prcmPlanLcep, - item.prcmPlanBrdnCst, - item.prcmPlanEcqrRt, - item.clclnGrssCst, - item.clclnNlep, - item.clclnLcep, - item.clclnBrdnCst, - item.clclnEcqrRt, - ]; - }); - - const sheetParameter = [ - { - data: excelData1, - headerNum: 2, - headerRange: 'A1:AX1', - sheetName: '물재이용사례', - startCol: 'A' + if (!response.ok) { + throw Error(response.statusText); + } + // 받은 파일 바인너리 처리 + const blobRet = await response.arrayBuffer(); + if(excelList){ + const excelData1 = excelList.map((item) => { + return [ + item.ruseBizNm, + item.bizBgngDate, + item.bizEndDate, + item.splyFcltKnd, + item.splyFcltNm, + item.splyFcltCpct, + item.dmdFcltNm, + item.dmdFcltAddr, + item.ruseFcltCpct, + item.ruseFcltPplLen, + item.ruseFcltUsg, + item.ruseFcltSplyCpct, + item.ruseFcltPrcsMsrc, + item.grnteWqUsg, + item.grnteWqGrssClfg, + item.grnteWqCombResdch, + item.grnteWqTrbt, + item.grnteWqBod, + item.grnteWqSmll, + item.grnteWqCrmty, + item.grnteWqTn, + item.grnteWqTp, + item.grnteWqPh, + item.grnteWqClIonQnt, + item.grnteWqCndctStat, + item.grnteWqRmrk, + item.reqBizGrssCst, + item.reqBizReuseFcltCst, + item.reqBizPplCst, + item.reqBizArchCst, + item.reqBizMchnCst, + item.reqBizMtrngCst, + item.reqBizGisCst, + item.reqBizCctvCst, + item.reqBizCmcnCst, + item.reqBizWstCst, + item.reqBizDesignCst, + item.reqBizSpvsCst, + item.reqBizSbsdCst, + item.reqBizEtc, + item.prcmPlanGrssCst, + item.prcmPlanNlep, + item.prcmPlanLcep, + item.prcmPlanBrdnCst, + item.prcmPlanEcqrRt, + item.clclnGrssCst, + item.clclnNlep, + item.clclnLcep, + item.clclnBrdnCst, + item.clclnEcqrRt, + ]; + }); + + const sheetParameter: SheetParameter[] = [ + { + data: excelData1, + headerNum: 2, + headerRange: 'A1:AX1', + sheetName: '물재이용사례', + startCol: 'A' + } + ]; + // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 + await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter); } - ]; - // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 - await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter); }); - } + + + }, [excelList]) // 양식받기 const sampleDownLoad = () => { excelDownload( [[]], - "물재이용사례 관리", + "물재이용사례 관리 등록양식", "sample_ruseCaseMng.xlsx", "A1:AX1", 2 @@ -161,7 +177,7 @@ export default function RuseCaseMng() { } useEffect(() => { - if(status === 200){ + if (status === 200) { setIsExcelOpen({ isModal: false }); } }, [status]); @@ -174,7 +190,7 @@ export default function RuseCaseMng() { <Title title={"물재이용사례 관리"} /> <SearchDiv> <SearchFormLeft> - <div className="col-span-8 flex flex-row"> + <div className="col-span-8 flex flex-row items-center"> <SelectBox className="col-span-2 w-1/3 h-full text-2xl" label="시도" @@ -183,24 +199,28 @@ export default function RuseCaseMng() { onSelectValue={getSelCity} /> <SelectBox - className="col-span-2 w-1/3 h-full text-2xl" + className="col-span-2 w-1/3 h-full text-2xl ml-4" label="시군구" selectClass={""} selectArray={selectSgg || []} onSelectValue={getSgg} /> <SelectBox - className="col-span-2 w-1/3 h-full text-2xl" + className="col-span-2 w-1/3 h-full text-2xl ml-4" label="용도" selectClass={""} selectArray={ruseUtzType} onSelectValue={(val) => { - if (val) setSelectUtz([String(val)]); + if (val) setSelectUtz(String(val)); }} + value={selectUtz || ruseUtzType[0]?.val} /> + <Label className="w-28 text-xl ml-4 mr-6"> + {"수요처명"} + </Label> <SearchInput - className={"text-2xl"} - label="수요처명" + className={"col-span-2 w-1/3 text-2xl"} + label="" textValue={name} setText={(e) => { setName(e); @@ -211,13 +231,29 @@ export default function RuseCaseMng() { <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2"> <div className="space-y"> <div className="flex flex-row space-x-2"> - <Button size={"sm"} onClick={() => { searchRuseCaseMng(1, 20) }}> + <Button size={"sm"} onClick={() => { + searchRuseCaseMng(false, 1, 10); + isResetPage(true); + }}> 조회 </Button> <Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}> 엑셀등록 </Button> - <Button size={"sm"} onClick={() => { download() }}> + <Button size={"sm"} onClick={() => { + selectRuseCaseMng( + true, + 1, + count, + sidoCd ?? "", + sigunguCd ?? "", + selectUtz ?? "", + name ?? "" + ); + // download() + + + }} className={"green"}> 다운로드 </Button> <Button size={"sm"} onClick={() => { sampleDownLoad() }}> @@ -239,10 +275,12 @@ export default function RuseCaseMng() { headlist={ruseCaseMng.ruseHeadList} pageSize={20} total={count} - cellClick={() => {}} + cellClick={undefined} onPageChange={(pageNo: number) => { - searchRuseCaseMng(pageNo, 10); + searchRuseCaseMng(false, pageNo, 10); + isResetPage(false) }} + resetPageNo={resetPage} /> </div> </ListDiv> @@ -254,9 +292,9 @@ export default function RuseCaseMng() { <UiModal open={isExcelOpen.isModal} onOpenChange={() => { - setIsExcelOpen({ isModal: !isExcelOpen.isModal }) + setIsExcelOpen({ isModal: !isExcelOpen.isModal }) }} - bodyClass={"w-[40vw] max-w-none"} + bodyClass={"w-[35vw] max-w-none"} body={<RuseCaseMngExcel />} /> </> diff --git a/apps/wr/app/matMng/coMng/coSttn/page.tsx b/apps/wr/app/matMng/coMng/coSttn/page.tsx index 8e2705450..e2f600b85 100644 --- a/apps/wr/app/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/coSttn/page.tsx @@ -5,6 +5,7 @@ import { btnClass } from "@/app/store/common/code"; import { excelCoSttnParsing } from "@/app/store/matDmd/coSttn/excel"; import { fetchType, getFetch } from "@/components/lib/api"; import { + confirmToast, excelDownload, formatPhoneNumber, getCity, @@ -30,6 +31,7 @@ import { Button } from "@common/components/ui"; import moment from "moment"; import { useCallback, useEffect, useState } from "react"; import toast, { Toaster } from "react-hot-toast"; +import { useTranslation } from "react-i18next"; export default function CoSttn() { const { @@ -43,6 +45,7 @@ export default function CoSttn() { updateDemandCompany, coSttnTxtSearch, demandCompList, + coSttnPwInit, } = useWrStore((state) => state); const { regAuthrtYn, mdfcnAuthrtYn } = auth; @@ -103,9 +106,8 @@ export default function CoSttn() { ...item, telno: formatPhoneNumber(item.telno || undefined), regDt: moment(item.regDt).format("YYYY-MM-DD"), - dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5,10)}-${item.dmdInfoId.slice(10)}` - })); - console.log("chData:", chData) + dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}` + })); // .filter((item: any) => item !== undefined); setBdstTable(chData); } else if (coSttnRes?.length == 0) { @@ -127,7 +129,7 @@ export default function CoSttn() { coExcel as [], "수요처 관리", "sample_coSttn.xlsx", - "A1:H1", + "A1:G1", 2 ); } @@ -156,6 +158,19 @@ export default function CoSttn() { } } }; + const { t } = useTranslation('base'); + const initPassword = () => { + if (upadateData) { + const dmdInfoId = upadateData.dmdInfoId; + const dmdInfoSliceId = `${dmdInfoId.slice(0, 3)}-${dmdInfoId.slice(3, 5)}-${dmdInfoId.slice(5, 10)}-${dmdInfoId.slice(10)}` + const handleConfirm = () => { + coSttnPwInit(dmdInfoId); + }; + confirmToast( t('isInitPw', { dmdInfoId: dmdInfoSliceId }), handleConfirm, 'text-2xl'); + } else { + toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.") + } + } return ( <> @@ -167,7 +182,7 @@ export default function CoSttn() { <SearchFormLeft> <div className="col-span-10 flex flex-row"> <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" + className="col-span-3 w-1/6 h-full text-2xl" label="시도" selectClass={""} selectArray={code.cityCd || []} @@ -178,7 +193,7 @@ export default function CoSttn() { value={objSearch.city} /> <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" + className="col-span-3 w-1/6 h-full text-2xl ml-4" label="시군구" selectClass={""} selectArray={selectSgg || []} @@ -186,7 +201,7 @@ export default function CoSttn() { value={objSearch.sgg} /> <SearchInput - className={"!text-3xl"} + className={"!text-3xl ml-4"} label="회사명" textValue={seartchTxt ?? ""} setText={(e) => { @@ -195,7 +210,7 @@ export default function CoSttn() { /> </div> </SearchFormLeft> - <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> + <SearchFormRight className="w-3/12 mx-5 flex flex-col items-center justify-center space-x-2 "> <div className="flex flex-row space-x-2"> <Button className="mt-2" size={"sm"} onClick={searchClick}> 조회 @@ -236,6 +251,13 @@ export default function CoSttn() { > 다운로드 </Button> + <Button + size={"sm"} + className={`${btnClass}`} + onClick={(initPassword)} + > + 비밀번호 초기화 + </Button> </div> </SearchFormRight> </SearchDiv> @@ -267,7 +289,7 @@ export default function CoSttn() { </div> <UiModal bodyClass={ - "w-[40vw] max-w-none h-[80vh] " // +upadateData?.dmdInfoId?'h-[60vh]':'h-[90vh]' + "w-[40vw] max-w-none h-[83vh] " // +upadateData?.dmdInfoId?'h-[60vh]':'h-[90vh]' } open={isDmdModal.isMoal} onOpenChange={() => { @@ -286,9 +308,7 @@ export default function CoSttn() { objSearch.sgg && objSearch.sgg !== "0" ? objSearch.sgg : objSearch.city; - console.log("data ", data); if (upadateData?.dmdInfoId) { - console.log("data ", data); await updateDemandCompany(data as DemandCompType); coSttnInit("1", "13", String(con)); setIsDmdModal({ diff --git a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx index 934946b74..d08d6f06e 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx @@ -71,7 +71,7 @@ export default function PublicReuseFaciltyInsert() { // defaultValues: { items: pbnstRegArrayZod.parse([]) }, mode: "onChange", }); - const { isDirty } = form.formState; + // const { isDirty } = form.formState; const { fields: items, @@ -90,10 +90,10 @@ export default function PublicReuseFaciltyInsert() { }, [pbnst.isInit, pubHasuInit]); const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => { - if (!isDirty) { - toast.error("변경사항이 없습니다."); // 변경사항이 없으면 메시지 표시 - return; - } + // if (!isDirty) { + // toast.error("변경사항이 없습니다."); // 변경사항이 없으면 메시지 표시 + // return; + // } const data = writeRgtrAndMdfr(valuse.items); let bizs = [] as fcBizType[]; data.forEach((fclt) => { diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index eb7a398b1..21f72f449 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useWrStore } from "@/app/store"; import { btnClass } from "@/app/store/common/code"; +import { prfRegPageSize } from "@/app/store/prfmncReg/pbnstPrfmncReg"; import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; import { excelDownload, yearOptions } from "@/components/lib/func/commom"; @@ -20,6 +21,8 @@ import ListDiv from "@common/business_components/ui/list_div"; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; import { Button, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; +import toast, { Toaster } from "react-hot-toast"; +import { useTranslation } from "react-i18next"; export default function PbnstSttn() { const { @@ -33,6 +36,8 @@ export default function PbnstSttn() { } = useWrStore((state) => state); const { pbnstSttnRes, fclts, pbnstSttnResCount } = pbnstSttn; + + const [isReset, setIsReset] = useState(false); const ref = useRef<ReturnType>({} as ReturnType); const [searchYear, setSearchYear] = useState<string>( String(new Date().getFullYear()) @@ -43,10 +48,11 @@ export default function PbnstSttn() { isModal: boolean; data: customPbnst; }>({ isModal: false, data: {} as customPbnst }); - + const isDownloadTriggered = useRef(false); const [pbnRuseNm, setPbnRuseNm] = useState<string>(""); - + const { t } = useTranslation('base'); const excelClick = useCallback(() => { + isDownloadTriggered.current = true; pbnstSttnSearch( true, String(1), @@ -67,24 +73,16 @@ export default function PbnstSttn() { }, [getPbnStnDetail, pbnstSttnRes] ); - useEffect(() => { - if (pbnstSttn.excelList.length >= 0) { - excelDownload( - pbnstSttn.excelList, - "하수재이용시설 현황", - "sample_pbnstSttn.xlsx", - "A1:U2", - 3 - ); - } - }, [pbnstSttn.excelList]); - useEffect(() => { - if (!pbnstSttn.isInit) { getFlcts(); } - }, [pbnstSttn.isInit, pbnSttnInit, ref.current]); + }, [pbnstSttn.isInit]); + useEffect(() => { + + pbnSttnInit("1", "15", ref.current, searchYear, fclts); + }, [fclts]); + useEffect(() => { if (pbnstSttnRes && pbnstSttnRes.length > 0) { setBdstTable(pbnstSttnRes); @@ -93,9 +91,25 @@ export default function PbnstSttn() { } }, [pbnstSttnRes]); + useEffect(() => { - pbnSttnInit("1", "15", ref.current, searchYear, fclts); - }, [fclts]); + if (!isDownloadTriggered.current) { + return; + } + if (pbnstSttn.excelList.length > 0) { + excelDownload( + pbnstSttn.excelList, + "하수재이용시설 현황", + "sample_pbnstSttn.xlsx", + "A1:U2", + 3 + ); + } else { + toast.error(t('excelDownErrMsg')); + } + isDownloadTriggered.current = false; + }, [pbnstSttn.excelList]); + useEffect(() => { if (pbnstSttn.pbnstDetail.pbnstRuseFcltId) @@ -157,16 +171,11 @@ export default function PbnstSttn() { /> </div> } - queryClick={function (returnType: ReturnType): void { - pbnstSttnSearch( - false, - "1", - "15", - returnType, - pbnRuseNm, - searchYear, - fclts - ); + queryClick={() => { + // pbnSearch(returnType); + + //setTableIndex(0) + setIsReset(true); }} excelClick={excelClick} initClick={function (): void { @@ -194,13 +203,16 @@ export default function PbnstSttn() { pbnstSttnSearch( false, String(page), - String(15), + String(prfRegPageSize), ref.current, pbnRuseNm, searchYear, fclts ); + setIsReset(false); }} + + resetPageNo={isReset} > <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> </UiTable> @@ -239,6 +251,7 @@ export default function PbnstSttn() { }} /> </ContentsDiv> + <Toaster position="top-center" /> </> ); } diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx index fbab00284..4fd866d75 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx @@ -38,11 +38,12 @@ import { useTranslation } from "react-i18next"; import { headMakeprfmnc } from "../chkgdnReg/TableHead"; export default function PrfmncPrfmncReg() { - const { auth, prfmnc, prfmncHasuInit, prfmnRegData, prfmncHasuNmSearch } = useWrStore( + const { auth, prfmnc, prfmncHasuInit, prfmnRegData, prfmncHasuNmSearch,getPrfFmcRes } = useWrStore( (state) => state ); - const { pbnstPrfmncHeadList, monthHeadList, prfmncRes, prfmncResCount } = prfmnc; + const { prfPbnFclts, pbnstPrfmncHeadList, monthHeadList, prfmncRes, prfmncResCount } = prfmnc; const { regAuthrtYn, mdfcnAuthrtYn } = auth; + const [isReset, setIsReset] = useState(false); const [bdstTable, setbdstTable] = useState<FcPrfType[]>(); const [searchTxt, setSearchTxt] = useState<string>(); const [isExcelOpen, setIsExcelOPen] = useState<{ @@ -51,7 +52,6 @@ export default function PrfmncPrfmncReg() { }>({ isModal: false, data: {} as FcPrfType }); const [inputData, setInputData] = useState<any[]>(); const [pbnstRuseFcltId, setpbnstRuseFcltId] = useState<string>(); - const [pageNation, setPageNation] = useState<number>(1); const [cellNum, setCellNum] = useState<number>(1); const [searchYear, setSearchYear] = useState<string>( String(new Date().getFullYear()) @@ -65,20 +65,6 @@ export default function PrfmncPrfmncReg() { "A1:O1" ); }, [prfmnc.excelList]); - /* - const onSave = useCallback( - async() => { - if (pbnstRuseFcltId) { - await prfmnRegData(pageNation,'41', - excelToPbnPrfData( - inputData as unknown as JSON[], - searchYear, - pbnstRuseFcltId - ) as unknown as PrfType - ); - // cellClick(cellNum) - } - }, [inputData, searchYear, pbnstRuseFcltId, pageNation,cellNum]);*/ const { t } = useTranslation('base'); const onSave = useCallback(() => { @@ -86,19 +72,20 @@ export default function PrfmncPrfmncReg() { if (!pbnstRuseFcltId) return; prfmnRegData( - pageNation, + 1, '41', excelToPbnPrfData( inputData as unknown as JSON[], searchYear, pbnstRuseFcltId ) as unknown as PrfType - , searchYear + , searchYear, + prfPbnFclts ); }; confirmToast(t('isUpdateMsg'), handleConfirm); - }, [inputData, searchYear, pbnstRuseFcltId, pageNation, cellNum]); + }, [inputData, searchYear, pbnstRuseFcltId, cellNum,prfPbnFclts]); const cellClick = useCallback( (index: number | undefined) => { @@ -194,10 +181,15 @@ export default function PrfmncPrfmncReg() { useEffect(() => { if (!prfmnc.isInit) { - const year = new Date().getFullYear() - prfmncHasuInit(1, '41', String(year)); + getPrfFmcRes() } - }, [prfmnc.isInit, prfmncHasuInit]); + }, [prfmnc.isInit, getPrfFmcRes]); + + + useEffect(() =>{ + const year = new Date().getFullYear() + prfmncHasuInit(1, '41',String(year) ,prfPbnFclts); + },[prfPbnFclts]) useEffect(() => { @@ -254,14 +246,8 @@ export default function PrfmncPrfmncReg() { <div className="flex flex-row space-x-2"> <Button size={"sm"} onClick={() => { - if (searchTxt && searchTxt?.length > 0) { - prfmncHasuNmSearch(1, '41', searchYear, searchTxt) - setPageNation(1) - } else { - prfmncHasuInit(1, '41', searchYear) - setPageNation(1) - } - + + setIsReset(true) }}> 조회 </Button> @@ -314,10 +300,15 @@ export default function PrfmncPrfmncReg() { setCellNum(index ?? 0); }} onPageChange={ - (page) => { - prfmncHasuInit(page, '41', searchYear); - setPageNation(page) + (page:number) => { + if (searchTxt && searchTxt?.length > 0) { + prfmncHasuNmSearch(page, '41', searchYear, searchTxt,prfPbnFclts) + } else { + prfmncHasuInit(page, '41', searchYear,prfPbnFclts) + } + setIsReset(false); }} + resetPageNo={isReset} /> </ScrollArea> </ListDiv> @@ -350,9 +341,10 @@ export default function PrfmncPrfmncReg() { setIsExcelOPen({ ...isExcelOpen, isModal: open }) }} setModalExcelData={async (excelData) => { - await prfmnRegData(pageNation, '41', + await prfmnRegData(1, '41', excelData, searchYear + ,prfPbnFclts ); // 데이터 저장 후 UI 업데이트는 useEffect가 감지하여 처리하므로 여기서는 호출하지 않습니다. } diff --git a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx index 8035d2875..ac77639a4 100644 --- a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx @@ -150,9 +150,9 @@ export default function PbnstPrfmncSttn() { } const tableData = [...inUsageData, ...outUsageData, usageTotalObj]; setPrfData(tableData); - setExcelData(excelPrfmncSttnParsing(prfData ?? [])) - // }); + setExcelData(excelPrfmncSttnParsing(tableData ?? [])) } else { + setExcelData(excelPrfmncSttnParsing([])) setPrfData([]); } } @@ -605,7 +605,11 @@ export default function PbnstPrfmncSttn() { isResetPage(true) }} excelClick={excelClick} - initClick={function (): void { }} + initClick={function (): void { + setPbnRuseNm(""); + setFromDate(String(new Date().getFullYear())); + setToDate(String(new Date().getFullYear())) + }} btnClass={btnClass} ref={ref} isAdmin={false} /> </div> diff --git a/apps/wr/app/store/matDmd/coMatRvw/type.tsx b/apps/wr/app/store/matDmd/coMatRvw/type.tsx index 34990c6b3..58d539538 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/type.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/type.tsx @@ -98,7 +98,7 @@ export const DmdCoReqSchema = z.object({ z.object({ ruseUtztnFcltNm: z.string(), ruseUtztnFcltCd: z.string(), - }) + }).nullish() ).nullish(), totalConstCost: z.number().nullish(), }); diff --git a/apps/wr/app/store/matDmd/coSttn/index.tsx b/apps/wr/app/store/matDmd/coSttn/index.tsx index cc1db4566..1f7b2c54b 100644 --- a/apps/wr/app/store/matDmd/coSttn/index.tsx +++ b/apps/wr/app/store/matDmd/coSttn/index.tsx @@ -1,7 +1,8 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; -import { fetchType, getFetchWithCount } from "@/components/lib/api"; +import { fetchType, getFetchWithCount, putFectch } from "@/components/lib/api"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { coSttHeadListExport, coSttNodeListExport } from "./headList"; +import toast from "react-hot-toast"; // 테이블 헤드 리스트 const coSttNodeList = coSttNodeListExport; @@ -21,9 +22,10 @@ export type coSttnState = { }; type coSttnActions = { - coSttnInit:(pageNo:string, pageCnt:string, con: string) => void; - coSttnSearch: (pageNo:string, pageCnt:string, con: string) => void; - coSttnTxtSearch:(pageNo:string, pageCnt:string, con: string,txt:string) => void; + coSttnInit: (pageNo: string, pageCnt: string, con: string) => void; + coSttnSearch: (pageNo: string, pageCnt: string, con: string) => void; + coSttnTxtSearch: (pageNo: string, pageCnt: string, con: string, txt: string) => void; + coSttnPwInit: (dmdInfoId: string) => void; }; export type coSttnType = coSttnState & coSttnActions; @@ -44,7 +46,7 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( set: any ) => { return { - coSttnInit: async (pageNo:string, pageCnt:string, con: string) => { + coSttnInit: async (pageNo: string, pageCnt: string, con: string) => { try { const coSttnRes = await getFetchWithCount( @@ -52,8 +54,6 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( `/reuseFc/request/${pageNo}/${pageCnt}/${con}` ); - console.log('coSttnRes ', coSttnRes) - set((state: coSttnState) => { return { ...state, @@ -69,56 +69,81 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( console.log(e) } }, - coSttnSearch: async (pageNo:string, pageCnt:string, con: string) => { - try { - const coSttnRes = await getFetchWithCount( - fetchType.WR, - `/reuseFc/request/${pageNo}/${pageCnt}/${con}` - ); - if (Number(pageCnt) >= 10000) { - set((state: coSttnState) => { - return { - ...state, - coSttn: { - ...state.coSttn, - excelList: coSttnRes.data, - //isInit: false, - }, - }; - }); - } else - set((state: coSttnState) => { - return { - ...state, - coSttn: { - ...state.coSttn, - coSttnRes: coSttnRes.data, - isInit: false, - coSttnResCount : coSttnRes.count, - }, - }; - }); - } catch (erTxt) { - console.log("error", erTxt); - } - }, - coSttnTxtSearch: async (pageNo:string, pageCnt:string, con: string, txt: string) => { - try { - const coSttnRes = await getFetchWithCount( - fetchType.WR, - `/reuseFc/request/${pageNo}/${pageCnt}/${con}/${txt}`) - set((state: coSttnState) => { - return { - ...state, - coSttn: { - ...state.coSttn, - coSttnRes: coSttnRes.data, - coSttnResCount: coSttnRes.count, - isInit: false, - }}}) - } catch (erTxt) { - console.log("error", erTxt); + coSttnSearch: async (pageNo: string, pageCnt: string, con: string) => { + try { + const coSttnRes = await getFetchWithCount( + fetchType.WR, + `/reuseFc/request/${pageNo}/${pageCnt}/${con}` + ); + if (Number(pageCnt) >= 10000) { + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + excelList: coSttnRes.data, + //isInit: false, + }, + }; + }); + } else + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + coSttnRes: coSttnRes.data, + isInit: false, + coSttnResCount: coSttnRes.count, + }, + }; + }); + } catch (erTxt) { + console.log("error", erTxt); + } + }, + coSttnTxtSearch: async (pageNo: string, pageCnt: string, con: string, txt: string) => { + try { + const coSttnRes = await getFetchWithCount( + fetchType.WR, + `/reuseFc/request/${pageNo}/${pageCnt}/${con}/${txt}`) + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + coSttnRes: coSttnRes.data, + coSttnResCount: coSttnRes.count, + isInit: false, + } } + }) + } catch (erTxt) { + console.log("error", erTxt); + } + }, + coSttnPwInit: async (dmdInfoId: string) => { + try { + const response = await putFectch(`/reuseFc/request/chgPassword/${dmdInfoId}`,[]) + if (response.status === 200) { + toast.success('비밀번호가 초기화 되었습니다.') + } else { + toast.error('비밀번호 초기화에 실패하였습니다') } + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + isInit: false, + } + } + }); + } catch (err) { + console.log(err); + toast.error('비밀번호 초기화에 실패하였습니다.') + + } + } } } \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx index 8c3a232e7..bc71fbf81 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx @@ -1,7 +1,6 @@ import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../../common/type/header"; -//★☆★☆ 여기서부터 수정하기 ★☆★☆ export const ruseCaseHeadListExport: TableUpperProps[] = [ { id: "ruseBizNm", @@ -358,6 +357,7 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ ]; export const ruseUtzType: SelectDataType[] = [ + { text: "전체", val: "0" }, { text: "조경용수", val: "조경용수" }, { text: "청소화장실용수", val: "청소화장실용수" }, { text: "세척살수용수", val: "세척살수용수" }, diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx index 5ae2e67b4..160e1f7f3 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx @@ -4,90 +4,93 @@ import type { TableUpperProps } from "../../common/type/header"; import { ruseCaseHeadListExport } from "./headList"; export type ruseCaseState = { ruseCaseMng: { - ruseHeadList:TableUpperProps[]; - ruseCaseMngList:ruseCaseMngExcelProps[]; + ruseHeadList: TableUpperProps[]; + ruseCaseMngList: ruseCaseMngExcelProps[]; status: number; count: number; + excelList?: ruseCaseMngExcelProps[]; }; }; export interface ruseCaseMngExcelProps { - ruseBizNm: string, - bizBgngDate: string, - bizEndDate: string, - splyFcltKnd: string, - splyFcltNm: string, - splyFcltCpct: number | 0 |'', - dmdFcltNm: string, - dmdFcltAddr: string, - ruseFcltCpct: number | 0 |'', - ruseFcltPplLen: string, - ruseFcltUsg: string, - ruseFcltSplyCpct: number | 0 |'', - ruseFcltPrcsMsrc: string, - grnteWqUsg: string, - grnteWqGrssClfg: number | 0 |'', - grnteWqCombResdch: number | 0 |'', - grnteWqTrbt: string, - grnteWqBod: string, - grnteWqSmll: string, - grnteWqCrmty: string, - grnteWqTn: string, - grnteWqTp: string, - grnteWqPh: string, - grnteWqClIonQnt: string, - grnteWqCndctStat: string, - grnteWqRmrk: string, - reqBizReuseFcltCst: number | 0 |'', - reqBizPplCst: number | 0 |'', - reqBizArchCst: number | 0 |'', - reqBizMchnCst: number | 0 |'', - reqBizMtrngCst: number | 0 |'', - reqBizGisCst: number | 0 |'', - reqBizCctvCst: number | 0 |'', - reqBizCmcnCst: number | 0 |'', - reqBizWstCst: number | 0 |'', - reqBizDesignCst: number | 0 |'', - reqBizSpvsCst: number | 0 |'', - reqBizSbsdCst: number | 0 |'', - reqBizEtc: string, - reqBizGrssCst: number | 0 |'', - prcmPlanNlep: number | 0 |'', - prcmPlanLcep: number | 0 |'', - prcmPlanBrdnCst: number | 0 |'', - prcmPlanEcqrRt: number | 0 |'', - prcmPlanGrssCst: number | 0 |'', - clclnNlep: number | 0 |'', - clclnLcep: number | 0 |'', - clclnBrdnCst: number | 0 |'', - clclnEcqrRt: number | 0 |'', - clclnGrssCst: number | 0 |'', + ruseBizNm: string, + bizBgngDate: string, + bizEndDate: string, + splyFcltKnd: string, + splyFcltNm: string, + splyFcltCpct: number | 0 | '', + dmdFcltNm: string, + dmdFcltAddr: string, + ruseFcltCpct: number | 0 | '', + ruseFcltPplLen: string, + ruseFcltUsg: string, + ruseFcltSplyCpct: number | 0 | '', + ruseFcltPrcsMsrc: string, + grnteWqUsg: string, + grnteWqGrssClfg: number | 0 | '', + grnteWqCombResdch: number | 0 | '', + grnteWqTrbt: string, + grnteWqBod: string, + grnteWqSmll: string, + grnteWqCrmty: string, + grnteWqTn: string, + grnteWqTp: string, + grnteWqPh: string, + grnteWqClIonQnt: string, + grnteWqCndctStat: string, + grnteWqRmrk: string, + reqBizReuseFcltCst: number | 0 | '', + reqBizPplCst: number | 0 | '', + reqBizArchCst: number | 0 | '', + reqBizMchnCst: number | 0 | '', + reqBizMtrngCst: number | 0 | '', + reqBizGisCst: number | 0 | '', + reqBizCctvCst: number | 0 | '', + reqBizCmcnCst: number | 0 | '', + reqBizWstCst: number | 0 | '', + reqBizDesignCst: number | 0 | '', + reqBizSpvsCst: number | 0 | '', + reqBizSbsdCst: number | 0 | '', + reqBizEtc: string, + reqBizGrssCst: number | 0 | '', + prcmPlanNlep: number | 0 | '', + prcmPlanLcep: number | 0 | '', + prcmPlanBrdnCst: number | 0 | '', + prcmPlanEcqrRt: number | 0 | '', + prcmPlanGrssCst: number | 0 | '', + clclnNlep: number | 0 | '', + clclnLcep: number | 0 | '', + clclnBrdnCst: number | 0 | '', + clclnEcqrRt: number | 0 | '', + clclnGrssCst: number | 0 | '', } type ruseCaseActions = { - selectRuseCaseMng: (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>; + ruseCaseMngInit: () => void; + selectRuseCaseMng: (isExcel: boolean, pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => Promise<void>; insertRuseCaseMng: (data: any) => Promise<void>; }; -export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { - const tailUrl = '/'+pageNo+'/'+pageSize+ ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : ''); - const resp = await getFetchWithCount(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`); - return {data:resp.data, count:resp.count}; +export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => { + const tailUrl = '/' + pageNo + '/' + pageSize + ((sigunguCd !== '전체' && sigunguCd !== '') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd ? `/${usageCd}` : '/0') + (name ? `/${name}` : ''); + const resp = await getFetchWithCount(fetchType.WR, `/reuseFc/pubFcRuseCase${tailUrl}`); + return { data: resp.data, count: resp.count }; }; export const fetchInsertRuseCaseMng = async (data: any) => { const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR); - return {data:response.data, status:response.status}; + return { data: response.data, status: response.status }; }; export type ruseCaseType = ruseCaseState & ruseCaseActions; export const ruseCaseStateExport: ruseCaseState = { ruseCaseMng: { - ruseHeadList:ruseCaseHeadListExport, - ruseCaseMngList:[], + ruseHeadList: ruseCaseHeadListExport, + ruseCaseMngList: [], status: 0, count: 0, + excelList: undefined, }, }; @@ -95,8 +98,8 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( set: any ) => { return { - selectRuseCaseMng: async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { - const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name); + ruseCaseMngInit: async () => { + const res = await getFetchWithCount(fetchType.WR, `/reuseFc/pubFcRuseCase/1/10/0/0`); set((state: ruseCaseState) => ({ ruseCaseMng: { ...state.ruseCaseMng, @@ -105,12 +108,35 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( }, })); }, + selectRuseCaseMng: async (isExcel: boolean, pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => { + const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name); + if (isExcel) { + set((state: ruseCaseState) => { + return { + ...state, + ruseCaseMng: { + ...state.ruseCaseMng, + count: res.count, + excelList: res.data + }, + }; + }); + } else { + set((state: ruseCaseState) => ({ + ruseCaseMng: { + ...state.ruseCaseMng, + ruseCaseMngList: res.data, + count: res.count, + }, + })); + } + }, insertRuseCaseMng: async (data: any) => { const response = await fetchInsertRuseCaseMng(data); - if(response.status === 200){ + if (response.status === 200) { toast.success("물재이용사례 등록이 완료되었습니다. "); - }else{ + } else { toast.error("물재이용사례 등록에 실패하였습니다. "); } set((state: ruseCaseState) => ({ diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx index 6d6ef18a6..a484f87c2 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx @@ -6,6 +6,7 @@ import { isInvalidNumber, parseRawValue, } from "@/components/lib/func/excelFunc"; +import toast from "react-hot-toast"; export const useTypes = [ "조경용수", "청소화장실용수", @@ -16,6 +17,18 @@ export const useTypes = [ "기타", ]; + +export const chkMngType =[ + '위탁', + '자체' +] +export const chkSpwtPositnType = [ + '유량계', + '수위계', + '펌프적산전력계', + '기타' +] + export type Buildingtype = { bdstInfoSn: number; ctpvCd: 1; @@ -143,43 +156,56 @@ export const excelToRainChkData = (excelData: any, year: string) => { }) .filter((el: any) => el !== undefined); +console.log("rainChkgdData", rainChkgdData); if (rainChkgdData.length > 0) { rainChkgdData.forEach((rain: any) => { const defaultMessage = `[시설명: ${rain.bdstNm}, 점검일: ${rain.chckYmd}, 이용용도: ${rain.guideDetails[0]?.utztnUsgCd}] 항목에서 `; if (rain.chckYmd && isInvalidDate(rain.chckYmd)) { issues.push( - defaultMessage + `점검일: 날짜 형식 아님 (${rain.chckYmd})` + defaultMessage + `점검일: 날짜 형식 아님 입력값 : (${rain.chckYmd})` ); } if (rain.anlOperCst.length > 2 && isInvalidNumber(rain.anlOperCst)) { issues.push( defaultMessage + - ` 연간운영비(원/년): 숫자 형식 아님 (${rain.anlOperCst})` + ` 연간운영비(원/년): 숫자 형식 아님 /입력값:${rain.anlOperCst}` ); } + if (rain.oilqMeasMthd.length > 1 && !chkMngType.includes(rain.oilqMeasMthd)) { + issues.push( + defaultMessage + `유량측정방법: 위탁 혹은 자체 만 허용) /입력값:${rain.oilqMeasMthd}` + ); + } + if (rain.spwtPositn.length > 1 && !chkSpwtPositnType.includes(rain.spwtPositn)) { + issues.push( + defaultMessage + `측정위치: 유량계, 수위계, 펌프적산전력계, 기타 중 하나만 허용) /입력값:${rain.spwtPositn}` + ); + } + + rain.rainyDetails?.forEach((key) => { if (key.ph?.length > 1 && isInvalidNumber(key.ph)) { - issues.push(defaultMessage + `PH: 숫자 형식 아님 (${key.ph})`); + issues.push(defaultMessage + `PH: 숫자 형식 아님 /입력값:${key.ph}`); } if (key.trbt?.length > 1 && isInvalidNumber(key.trbt)) { - issues.push(defaultMessage + `탁도: 숫자 형식 아님 (${key.trbt})`); + issues.push(defaultMessage + `탁도: 숫자 형식 아님 /입력값:${key.trbt}`); } if (key.grssClfg?.length > 1 && isInvalidNumber(key.grssClfg)) { issues.push( - defaultMessage + `총대장균군수: 숫자 형식 아님 (${key.grssClfg})` + defaultMessage + `총대장균군수: 숫자 형식 아님 /입력값:${key.grssClfg}` ); } if (key.levyAmt?.length > 1 && isInvalidNumber(key.levyAmt)) { issues.push( defaultMessage + - `과태료부과금액(천원): 숫자 형식 아님 (${key.levyAmt})` + `과태료부과금액(천원): 숫자 형식 아님 /입력값:${key.levyAmt}` ); } if (key.vltnDt?.length > 2 && isInvalidDate(key.vltnDt)) { issues.push( defaultMessage + - `과태료 부과일(위반일): 날짜 형식 아님 (${key.vltnDt})` + `과태료 부과일(위반일): 날짜 형식 아님 /입력값:${key.vltnDt}` ); } }); @@ -188,17 +214,19 @@ export const excelToRainChkData = (excelData: any, year: string) => { if (isInvalidNumber(guide.splyUswtrUtztnQnt)) { issues.push( defaultMessage + - `대체_${mm}: 숫자 형식 아님 (${guide.splyUswtrUtztnQnt})` + `대체_${mm}: 숫자 형식 아님 /입력값:${guide.splyUswtrUtztnQnt}` ); } if (isInvalidNumber(guide.ruseFcltUtztnQnt)) { issues.push( defaultMessage + - `빗물_${mm}: 숫자 형식 아님 (${guide.ruseFcltUtztnQnt})` + `빗물_${mm}: 숫자 형식 아님 /입력값:${guide.ruseFcltUtztnQnt}` ); } }); }); + }else{ + toast.error("데이터가 없습니다. 점검 데이터를 확인해주세요."); } if (issues.length > 0) { diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx index 262b1cb30..aa550dcd1 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx @@ -1,5 +1,10 @@ import type { SelectDataType } from "@common/business_components/ui"; -import { useTypes, type Buildingtype } from "../rainChkgdn/excel"; +import { + chkMngType, + chkSpwtPositnType, + useTypes, + type Buildingtype, +} from "../rainChkgdn/excel"; import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import type { rswtChkZodType } from "."; import { @@ -171,14 +176,14 @@ export const excelToRswtGuideData = ( .filter((el: any) => el !== undefined); if (rswtChkgdData.length > 0) { - rswtExcelDataCheck(rswtChkgdData,issues) + rswtExcelDataCheck(rswtChkgdData, issues); } if (issues.length > 0) { return { data: [], issues }; } - return { data:rswtChkgdData, issues: [] }; + return { data: rswtChkgdData, issues: [] }; }; export const excelToRswtPolution = (excelData: any, issues: string[]) => { @@ -248,25 +253,25 @@ export const excelToRswtPolution = (excelData: any, issues: string[]) => { }) .filter((el: any) => el !== undefined); if (rswtChkgdData.length > 0) { - rswtExcelDataCheck(rswtChkgdData,issues) + rswtExcelDataCheck(rswtChkgdData, issues); } if (issues.length > 0) { return { data: [], issues }; } - return { data:rswtChkgdData, issues: [] }; + return { data: rswtChkgdData, issues: [] }; }; - -const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ +const rswtExcelDataCheck = ( + rswtChkgdData: rswtChkZodType[], + issues: string[] +) => { rswtChkgdData.forEach((rswt: any) => { const defaultMessage = `*중수도 수질 [NO: ${rswt.bdstRuseFcltSn}, 점검일: ${rswt.chckYmd}, 이용용도: ${rswt.utztnUsgCd}] 항목에서 `; if (rswt.chckYmd && isInvalidDate(rswt.chckYmd)) { - issues.push( - defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})` - ); + issues.push(defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})`); } if (rswt.anlOperCst.length > 2 && isInvalidNumber(rswt.anlOperCst)) { issues.push( @@ -279,6 +284,25 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ defaultMessage + ` 처리용량(m³): 숫자 형식 아님 (${rswt.prcsCpct})` ); } + if ( + rswt.oilqMeasMthd.length > 1 && + !chkMngType.includes(rswt.oilqMeasMthd) + ) { + issues.push( + defaultMessage + + `유량측정방법: 위탁 혹은 자체 만 허용) /입력값:${rswt.oilqMeasMthd}` + ); + } + if ( + rswt.spwtPositn.length > 1 && + !chkSpwtPositnType.includes(rswt.spwtPositn) + ) { + issues.push( + defaultMessage + + `측정위치: 유량계, 수위계, 펌프적산전력계, 기타 중 하나만 허용) /입력값:${rswt.spwtPositn}` + ); + } + if (rswt?.rswtrPollutions && rswt?.rswtrPollutions.length > 0) { rswt.rswtrPollutions.forEach((pol) => { if (isInvalidNumber(pol.grssClfgUswtr)) { @@ -301,8 +325,7 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ } if (isInvalidNumber(pol.bod)) { issues.push( - defaultMessage + - `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})` + defaultMessage + `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})` ); } if (isInvalidNumber(pol.smll)) { @@ -319,8 +342,7 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ } if (isInvalidNumber(pol.tph)) { issues.push( - defaultMessage + - `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})` + defaultMessage + `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})` ); } if (isInvalidNumber(pol.tn)) { @@ -358,7 +380,5 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ } }); } - }) - - -} \ No newline at end of file + }); +}; diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx index fbdd4fba6..6c2564655 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx @@ -7,16 +7,17 @@ import toast from "react-hot-toast"; const pbnstPrfmncHeadList = pbnstPrfmncHeadListExport; const monthHeadList = monthHeadListExport; - +export const prfRegPageSize = 10; export type pbnstPrfmncState = { prfmnc: { isInit: boolean; pbnstPrfmncHeadList: TableUpperProps[]; monthHeadList: TableUpperProps[]; prfmncRes: FcPrfType[]; - prfmncResCount:number; + prfmncResCount: number; excelList: number[]; - pageKey:number + pageKey: number; + prfPbnFclts: string[]; }; }; @@ -26,68 +27,126 @@ export const pbnstPrfmncStateExport: pbnstPrfmncState = { pbnstPrfmncHeadList: pbnstPrfmncHeadList, monthHeadList: monthHeadList, prfmncRes: [], - prfmncResCount:1, + prfPbnFclts: [], + prfmncResCount: 1, excelList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - pageKey:1 + pageKey: 1, }, }; export type pbnstPrfmncType = pbnstPrfmncState & pbnstPrfmncActions; type pbnstPrfmncActions = { - prfmncHasuInit: (page: number, sggOrCity: string ,year:string) => void; - prfmncHasuNmSearch: (page: number, sggOrCity: string , txt:string ,year:string) => void; - prfmnRegData: (page:number,sggOrCity:string, data: PrfType,year:string) => void; + prfmncHasuInit: ( + page: number, + sggOrCity: string, + year: string, + prfPbnFclts: string[] + ) => void; + prfmncHasuNmSearch: ( + page: number, + sggOrCity: string, + year: string, + searchTxt: string, + prfPbnFclts: string[] + ) => void; + prfmnRegData: ( + page: number, + sggOrCity: string, + data: PrfType, + year: string, + prfPbnFclts: string[] + ) => void; + getPrfFmcRes: () => void; }; -const checkWrRegList = async (year:string) => { - const url =`/reuseFc/pubDetailList` - if(year&& year.length>0){ - // url+=`/${year}` +const hasuListFetch = async ( + page: number, + sggOrCity: string, + year: string, + prfPbnFclts: string[], + searchTxt?: string +) => { + let filteredFclt = prfPbnFclts; + if (sggOrCity !== "0") { + filteredFclt = prfPbnFclts?.filter((fclt: string) => { + return sggOrCity.length == 2 + ? fclt.slice(0, 2) === sggOrCity + : fclt.slice(0, 5) === sggOrCity; + }); } - const wrRegList = await getFetch("wr", url); - return wrRegList; -}; -const hasuListFetch = async (page: number, sggOrCity: string , year:string,searchNm?:string ) => { - const checkWrRegListRes = await checkWrRegList(year); - const rainRes = await postFectch( - `/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`, - checkWrRegListRes?.fcltCds, - fetchType.COMMON + + let url = `/prcsFclt/ruseInfo/1/${filteredFclt.length + 1}/A/${sggOrCity}/0/1/SW`; + if (searchTxt && searchTxt?.length > 0) { + url += `/${searchTxt}`; + } + const rainRes = await postFectch(url, filteredFclt, fetchType.COMMON); + + const checkRuseFcltList = + searchTxt && searchTxt?.length > 0 + ? rainRes.data.map((item) => item.bscFctCd) + : filteredFclt; + + const pubRuseList = await postFectch( + `/reuseFc/pubDetailList/${year}/${sggOrCity}/${page}/${prfRegPageSize}`, + checkRuseFcltList, + fetchType.WR ); - let findData = rainRes.data.map((item: pubHasuType) => { - const data = checkWrRegListRes?.fcPrfList.find( - (fclt: FcPrfType) => - fclt.pbnstRuseFcltId.substring(0, 11) === item?.bscFctCd - ); - if(data !==undefined){ - return( { + const rusePrfList = pubRuseList.data + .map((item: FcPrfType) => { + const findData = rainRes.data?.find( + (fclt: pubHasuType) => + item.pbnstRuseFcltId.substring(0, 11) === fclt?.bscFctCd + ); + if (findData !== undefined) { + return { + sido: findData.sido, + gugun: findData.gugun, + bscFctNm: findData.bscFctNm, + bscFctCd: findData.bscFctCd, + ruseUtztnFcltNm: item.ruseUtztnFcltNm, + pbnstRuseFcltId: item.pbnstRuseFcltId, + prfList: item.prfList, + }; + } + }) + .filter(Boolean); // undefined 값을 제거합니다. - sido: item.sido, - gugun: item.gugun, - bscFctNm: item.bscFctNm, - bscFctCd: item.bscFctCd, - ruseUtztnFcltNm: data.ruseUtztnFcltNm, - pbnstRuseFcltId: data.pbnstRuseFcltId, - prfList: data.prfList, - }); - } - }).filter(Boolean); // undefined 값을 제거합니다. - - if(searchNm){ - - findData= findData.filter((ruse:any) => ruse.ruseUtztnFcltNm.includes(searchNm)) - } - - return {data:findData, count:findData.length}; + return { data: rusePrfList, count: pubRuseList.count }; }; export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( set: any ) => { return { - prfmncHasuInit: async (page: number, sggOrCity: string,year:string) => { + getPrfFmcRes: async () => { try { - const prfmncRes = await hasuListFetch(page, sggOrCity, year); + const pbnPrfRes = await getFetch(fetchType.WR, "/reuseFc/pubFc"); + console.log("pbnPrfRes ", pbnPrfRes); + set((state: pbnstPrfmncState) => ({ + ...state, + prfmnc: { + ...state.prfmnc, + prfPbnFclts: pbnPrfRes, + }, + })); + } catch (error) { + console.log(error); + } + }, + prfmncHasuInit: async ( + page: number, + sggOrCity: string, + year: string, + prfPbnFclts: string[] + ) => { + try { + console.log("prfPbnFclts ", prfPbnFclts); + const prfmncRes = await hasuListFetch( + page, + sggOrCity, + year, + prfPbnFclts + ); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { @@ -101,9 +160,21 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( console.log(errorHasu); } }, - prfmncHasuNmSearch: async (page: number, sggOrCity: string,fcltNm:string ,year:string)=> { + prfmncHasuNmSearch: async ( + page: number, + sggOrCity: string, + year: string, + searchTxt: string, + prfPbnFclts: string[] + ) => { try { - const prfmncRes = await hasuListFetch(page, sggOrCity,fcltNm ,year); + const prfmncRes = await hasuListFetch( + page, + sggOrCity, + year, + prfPbnFclts, + searchTxt + ); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { @@ -117,7 +188,13 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( console.log(errorHasu); } }, - prfmnRegData: async (page:number,sggOrCity:string,data: PrfType,year:string) => { + prfmnRegData: async ( + page: number, + sggOrCity: string, + data: PrfType, + year: string, + prfPbnFclts: string[] + ) => { try { const ressponse = await postFectch( "/reuseFc/pubFcPrf", @@ -125,11 +202,14 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( fetchType.WR ); - if (ressponse.status === 200) { toast.success("하수재이용 시설 실적 등록이 완료되었습니다."); - const prfmncRes = await hasuListFetch(page, sggOrCity,year) ; - + const prfmncRes = await hasuListFetch( + page, + sggOrCity, + year, + prfPbnFclts + ); return set((state: pbnstPrfmncState) => ({ ...state, @@ -138,10 +218,9 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( prfmncRes: prfmncRes.data, prfmncResCount: prfmncRes.count, isInit: false, - pageKey: state.prfmnc.pageKey + 1 + pageKey: state.prfmnc.pageKey + 1, }, })); - } } catch (e) { console.log(e); diff --git a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx index 13093f98f..1a833313d 100644 --- a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx +++ b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx @@ -50,42 +50,56 @@ const hasuListFetch = async ( page: number, returnType: ReturnType, searchNm: string | undefined, - year: string, + fromDate: string, + toDate: string, flcts: string[] ) => { - let url = `/prcsFclt/ruseInfo/`; + let url = ''; + let filteredFclt = flcts; + const filterData = returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue + if (filterData !== '0') + filteredFclt = flcts.filter((fclt: string) => { + return filterData.length == 2 ? fclt.slice(0, 2) === filterData : fclt.slice(0, 5) === filterData + }) if (returnType.partValue === "A") { - url += `${page}/5/${returnType.partValue}/${ + url += `${returnType.partValue}/${ // returnType.sidoValue ? returnType.sidoValue : returnType.sigunValue returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue }/0/1/SW` + + if (filterData !== '0') + filteredFclt = flcts.filter((fclt: string) => { + return filterData.length == 2 ? fclt.slice(0, 2) === filterData : fclt.slice(0, 5) === filterData + }) } else if (returnType.partValue === "R") { - url += `${page}/5/${returnType.partValue}/${returnType.regionValue + url += `${returnType.partValue}/${returnType.regionValue ? returnType.regionValue : returnType.unitWaterShedValue ? returnType.unitWaterShedValue : returnType.middleRegionValue }/0/1/SW` } else if (returnType.partValue === 'G') { - url += `${page}/5/${returnType.envPartValue}/${returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue}/0/1/SW` - + url += `${returnType.envPartValue}/${returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue}/0/1/SW` + if (filterData !== '0') + filteredFclt = flcts.filter((fclt: string) => { + return filterData.length == 2 ? fclt.slice(0, 2) === filterData : fclt.slice(0, 5) === filterData + }) } else { - url += `1/2/A/0/0/1/SW` + url += `A/0/0/1/SW` } - if (searchNm && searchNm?.length > 0) { url += "/" + searchNm } - - const pbnList = await postFectch(url, flcts, fetchType.COMMON); + const pbnList = await postFectch(`/prcsFclt/ruseInfo/1/${filteredFclt.length}/` + url, filteredFclt, fetchType.COMMON); + const checkRuseFcltList = searchNm && searchNm?.length > 0 ? pbnList.data.map((item) => item.bscFctCd) : filteredFclt const getRuseList = await postFectch( - `/reuseFc/pubDetailList/${year}`, - flcts, + `/reuseFc/pubDetailPrfList/${fromDate}/${toDate}/${filterData}/${page}/5`, + checkRuseFcltList, fetchType.WR ); - let count = 0; const checkWrRegListRes = getRuseList.data; + const data: WaterReuseFacility[] = checkWrRegListRes.map( (item: FcPrfType) => { if (!item.prfList || item.prfList.length === 0) { @@ -96,7 +110,6 @@ const hasuListFetch = async ( fclt.bscFctCd === item.pbnstRuseFcltId.substring(0, 11) ); if (findData) { - count++; return { sido: findData?.sido || '-', gugun: findData?.gugun || '-', @@ -104,12 +117,12 @@ const hasuListFetch = async ( ruseUtztnFcltNm: item?.ruseUtztnFcltNm || '-', pbnstRuseFcltId: item?.pbnstRuseFcltId || '-', bscFctCd: findData?.bscFctCd || '-', - count + prfList: item.prfList, } } } ).filter(Boolean); - return data; + return {data: data, count: getRuseList.count}; } export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions = ( @@ -175,29 +188,17 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions page, returnType, fcltNm, + fromDate, toDate, fclts); - // const bscFctCdList = prfmncRes.map((item: any) => { - // return item.bscFctCd; - // }) - const bscFctCdList = prfmncRes - .map((item: any) => item.bscFctCd) - .filter((value, index, self) => self.indexOf(value) === index); - // const bscFctCdList = [...new Set(prfmncRes.map((item: any) => item.bscFctCd))]; - - const res = await postFectch( - `/reuseFc/pubDetailList/${fromDate}/${toDate}`, - bscFctCdList, - `wr`, - ) let inUsageSum = 0; let outUsageSum = 0; let year = ""; const yearsData = [] as any - res.data.forEach((item: any) => { - if (item.prfList.length === 0) { + prfmncRes.data.forEach((item: any) => { + if (!item.prfList || item.prfList.length === 0) { const year: string = ""; // 연도 정보가 없으면 빈 문자열, 필요시 item에서 추출. const yearVal: { [key: string]: any } = { year: year, pbnstRuseFcltId: item.pbnstRuseFcltId ?? "", ruseUtztnFcltNm: item.ruseUtztnFcltNm ?? "" }; yearVal[`${year}inUsage`] = 0; @@ -205,7 +206,7 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions yearVal[`${year}total`] = 0; yearsData.push(yearVal); } - item.prfList.forEach((acc) => { + item.prfList?.forEach((acc: any) => { year = acc.ym?.substring(0, 4) ?? ""; inUsageSum = (acc.wshwtr ?? 0) + //조경용수 @@ -273,7 +274,7 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions // prfmncRes 정보 merge const resData = Object.values(mergedMap).map((item: any) => { - const match = prfmncRes.find((res) => res.pbnstRuseFcltId === item.pbnstRuseFcltId); + const match = prfmncRes.data.find((res) => res.pbnstRuseFcltId === item.pbnstRuseFcltId); return match ? { ...item, ...match } : item; }); @@ -282,10 +283,10 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions pbnstPrfmncSttn: { ...state.pbnstPrfmncSttn, // prfmncRes: prfmncRes, - prfmncResCount: fclts.length - 5, + prfmncResCount: prfmncRes.count, isInit: false, prfmncRes: resData, - rawData: res.data + rawData: prfmncRes.data }, })); } catch (errorHasu) { diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx index 542622d34..7f905dd49 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx @@ -95,43 +95,59 @@ const hasuListFetch = async ( year: string, flcts: string[] ) => { - let url = `/prcsFclt/ruseInfo/`; + let url = ``; + let filteredFclt = flcts; + const filterData = reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue + + if(filterData !== '0') + filteredFclt= flcts.filter((fclt: string) => { + return filterData.length==2 ?fclt.slice(0, 2) === filterData:fclt.slice(0, 5) === filterData + }) if (reurnValue.partValue === "A") { - url += `${page}/${total}/${reurnValue.partValue}/${ + url += `${reurnValue.partValue}/${ // reurnValue.sidoValue ? reurnValue.sidoValue : reurnValue.sigunValue reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue }/0/1/SW` + + if(filterData !== '0') + filteredFclt= flcts.filter((fclt: string) => { + return filterData.length==2 ?fclt.slice(0, 2) === filterData:fclt.slice(0, 5) === filterData + }) } else if (reurnValue.partValue === "R") { - url += `${page}/${total}/${reurnValue.partValue}/${reurnValue.regionValue + url += `${reurnValue.partValue}/${reurnValue.regionValue ? reurnValue.regionValue : reurnValue.unitWaterShedValue ? reurnValue.unitWaterShedValue : reurnValue.middleRegionValue }/0/1/SW` } else if (reurnValue.partValue === 'G') { - - - url += `${page}/${total}/${reurnValue.envPartValue}/${reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue}/0/1/SW` - + url += `${reurnValue.envPartValue}/${reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue}/0/1/SW` + if(filterData !== '0') + filteredFclt= flcts.filter((fclt: string) => { + return filterData.length==2 ?fclt.slice(0, 2) === filterData:fclt.slice(0, 5) === filterData + }) + } else { - url += `1/2/A/0/0/1/SW` + url += `A/0/0/1/SW` } if (searchNm && searchNm?.length > 0) { url += "/" + searchNm } - const pbnList = await postFectch(url, flcts, fetchType.COMMON); + const pbnList = await postFectch( `/prcsFclt/ruseInfo/1/${filteredFclt.length}/`+url, filteredFclt, fetchType.COMMON); + + + const checkRuseFcltList = searchNm && searchNm?.length > 0?pbnList.data.map((item)=> item.bscFctCd ):filteredFclt const getRuseList = await postFectch( - `/reuseFc/pubDetailList/${year}`, - flcts, + `/reuseFc/pubDetailList/${year}/${filterData}/${page}/${total}`, + checkRuseFcltList, fetchType.WR ); const checkWrRegListRes = getRuseList.data; - const data: WaterReuseFacility[] = checkWrRegListRes.map( (item: FcPrfType) => { const findData = pbnList.data.find( @@ -140,7 +156,6 @@ const hasuListFetch = async ( ); if (findData) { - const usage = item.prfList.reduce( (acc, cur) => { acc.inUsage += @@ -197,7 +212,11 @@ const hasuListFetch = async ( } ).filter(Boolean); - return data; + + + + + return {data:data, count:getRuseList.count}; }; export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( @@ -237,8 +256,7 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( fclts: string[] ) => { try { - - + const pbnstSttnRes = await hasuListFetch( page, total, @@ -250,7 +268,7 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( // if (Number(total) > 9000) { if (isExcel) { - const pbnstExcel = excelPbnstSttnParsing(pbnstSttnRes); + const pbnstExcel = excelPbnstSttnParsing(pbnstSttnRes.data); set((state: pbnstSttnState) => { return { ...state, @@ -267,8 +285,8 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( ...state, pbnstSttn: { ...state.pbnstSttn, - pbnstSttnRes: pbnstSttnRes, - pbnstSttnResCount: pbnstSttnRes.length + pbnstSttnRes: pbnstSttnRes.data, + pbnstSttnResCount: pbnstSttnRes.count // isInit: false, }, }; @@ -295,13 +313,14 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( fclts ); + set((state: pbnstSttnState) => { return { ...state, pbnstSttn: { ...state.pbnstSttn, - pbnstSttnRes: pbnstSttnRes, - pbnstSttnResCount: pbnstSttnRes.length, + pbnstSttnRes: pbnstSttnRes.data, + pbnstSttnResCount: pbnstSttnRes.count, isInit: false, pbnstDetail: {} as customPbnst }, @@ -365,8 +384,8 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( ...state, pbnstSttn: { ...state.pbnstSttn, - pbnstSearchData: pbnstSttnRes, - pbnstSttnResCount: pbnstSttnRes.length, + pbnstSearchData: pbnstSttnRes.data, + pbnstSttnResCount: pbnstSttnRes.count, }, }; }); diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index 4902ccf3d..fef689f46 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -201,11 +201,11 @@ export const formatPhoneNumber = (tel: string) => { } }; -export const confirmToast = (content: string, saveFunc: () => void) => { +export const confirmToast = (content: string, saveFunc: () => void, contentClass?:string) => { toast( (t) => ( <span className="flex flex-col items-center"> - <div className="mb-6">{content}</div> + <div className={`mb-6 ${contentClass}`} >{content}</div> <div className="flex flex-row space-x-2"> <Button size={"sm"} diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx index 2589422c8..0fb24366a 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx @@ -488,16 +488,6 @@ export function PbnstBuseForm({ /> </div> - {/* <MngRegFormDetail - formList={ruseWaterQulityList} - h1Title={`하수처리수 재이용시설 보증수질 `} - subTitle="※재처리시설이 있는 경우만 작성" - form={form} - moClass="grid grid-cols-12 gap-4" - sonClass="col-span-4" - indexMng={indexMng} - indexBiz={indexBiz} - /> */} <div> <BuseWaterQulity diff --git a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx index 59ce18d93..055d0372f 100644 --- a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx @@ -158,7 +158,7 @@ export default function RswtrRegForm({ </h1> <div className="grid grid-cols-12 flex items-center card-header"> <h1 className="text-2xl font-medium text-label col-span-10"> - 건축물 운영정보 + 건축물 정보 </h1> {!readonly && ( <Button diff --git a/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx index 57424996e..a809c88f6 100644 --- a/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx +++ b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx @@ -1,14 +1,14 @@ +import { useWrStore } from "@/app/store"; import { coRegExcel } from "@/app/store/matDmd/coSttn/headList"; -import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index"; import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel"; -import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui"; -import { Form, FormField } from "@common/business_components/ui/form"; -import { Button } from "@common/components/ui"; +import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index"; +import { filePicked, Title } from "@common/business_components/ui"; +import { Form } from "@common/business_components/ui/form"; +import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; -import { useWrStore } from "@/app/store"; export default function RuseCaseMngExcel() { const { insertRuseCaseMng } = useWrStore((state) => state); @@ -79,6 +79,7 @@ export default function RuseCaseMngExcel() { }; Object.keys(reUseCaseNames).forEach((key: string) => { + console.log("reUseCaseNames", reUseCaseNames) if (item && reUseCaseNames[key] !== undefined) { const mappedKey = reUseCaseNames[key]; if (mappedKey && object.hasOwnProperty(mappedKey)) { @@ -146,23 +147,16 @@ export default function RuseCaseMngExcel() { </div> <div> - <FormField - control={form.control} - name="file" - render={() => ( - <FormUi label=""> - <FileUpload - files={file ? [file] : []} - setFiles={(files: File[] | null) => { - console.log("files", files) - if (files && files?.length === 1) setFiles(files[0]); - }} - fileTypes="" - maxFiles={1} - ></FileUpload> - </FormUi> - )} - /> + <div className="space-y-4 p-4"> + <Input + type="file" + onChange={(e) => { + const selectedFile = e.target.files?.[0]; + if (selectedFile) setFiles(selectedFile); + }} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" + /> + </div> </div> </form> </Form> diff --git a/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx b/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx index cb38717a3..aef145cc1 100644 --- a/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx +++ b/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx @@ -48,22 +48,21 @@ export default function CoDetailModal({ return ( <div> - <ScrollArea className="h-[85vh] mx-8"> - <Form {...form}> - <form onSubmit={form.handleSubmit(onFormSubmit)}> - <div className="grid grid-cols-12 items-center card-header"> - <h1 className="text-2xl font-medium text-label col-span-10"> - 잠재수요처 상세정보 - </h1> - <Button type="submit" - className="col-start-12 col-span-1" - size={"sm"} - disabled={readonly} - > - 저장 - </Button> - </div> - + <Form {...form}> + <form onSubmit={form.handleSubmit(onFormSubmit)}> + <div className="grid grid-cols-12 items-center card-header"> + <h1 className="text-2xl font-medium text-label col-span-10"> + 잠재수요처 상세정보 + </h1> + <Button type="submit" + className="col-start-12 col-span-1" + size={"sm"} + disabled={readonly} + > + 저장 + </Button> + </div> + <ScrollArea className="h-[79vh] mx-8"> <FormField control={form.control} name="fctryMngSn" @@ -865,9 +864,9 @@ export default function CoDetailModal({ /> </div> </div> - </form> - </Form> - </ScrollArea> + </ScrollArea> + </form> + </Form> </div> ); } diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 61f41d634..d068f6aa5 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -8,6 +8,8 @@ const serverApiUrl = CS_URL; const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; +const REUSE_API_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL || ""; +const OPERATION_API_URL = process.env.NEXT_PUBLIC_FO_SERVER_API_URL || ""; export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; @@ -135,6 +137,24 @@ export function middleware(req: NextRequest) { return NextResponse.rewrite(new URL(newUrl)); } + if (pathname.startsWith("/reuse-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${REUSE_API_URL}${pathname.replace("/reuse-service", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/operation-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${OPERATION_API_URL}${pathname.replace("/operation-service", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + return NextResponse.next(); } @@ -146,6 +166,8 @@ export const config = { "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*", + "/reuse-service/:path*", + "/operation-service/:path*", "/" ], }; diff --git a/apps/wr/public/images/map/layericon/BML_CBAS_PS.png b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png index 5ff4add3e..0e70939ba 100644 Binary files a/apps/wr/public/images/map/layericon/BML_CBAS_PS.png and b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_STMS_PS.png b/apps/wr/public/images/map/layericon/BML_STMS_PS.png index 423f2e8fc..bf4357181 100644 Binary files a/apps/wr/public/images/map/layericon/BML_STMS_PS.png and b/apps/wr/public/images/map/layericon/BML_STMS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png index 29c0b77b4..e5a83f998 100644 Binary files a/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png and b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png index 8a1ac0074..aeae3e266 100644 Binary files a/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png and b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png index 087b4e941..b3db8e0a4 100644 Binary files a/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png and b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_STAT_PS.png b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png index 13d8ea689..9a0b11673 100644 Binary files a/apps/wr/public/images/map/layericon/RDL_STAT_PS.png and b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png index ffc225159..3776f6c04 100644 Binary files a/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png and b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/wr/public/locales/ko/message.json b/apps/wr/public/locales/ko/message.json index d3c55de5e..fb7af9ed2 100644 --- a/apps/wr/public/locales/ko/message.json +++ b/apps/wr/public/locales/ko/message.json @@ -1,10 +1,10 @@ { - "deleteMsg" : "{{name}}가 삭제되었습니다.", + "deleteMsg": "{{name}}가 삭제되었습니다.", "insertMsg": "{{name}}가 저장되었습니다.", "updateMsg": "{{name}}가 수정되었습니다.", - "errorMsg": "{{name}} 중 오류가 발생했습니다.", + "errorMsg": "{{name}} 중 오류가 발생했습니다.", "fileErrMsg": "빈 파일 또는 파일 오류입니다.", - "selectErrMsg": "{{name}}을(를) 선택해주세요.", + "selectErrMsg": "{{name}}을(를) 선택해주세요.", "noChangedErrMsg": "변경사항이 없습니다.", "dateErrMsg": "시작년도를 종료년도 이후로 설정할 수 없습니다.", "fileDateErrMsg": "파일업로드 이전에 등록년도를 작성해주세요.", @@ -12,19 +12,15 @@ "isUpdateMsg": "수정한 정보를 저장하시겠습니까?", "isInsertMsg": "등록하시겠습니까?", "isUpdatetMsg": "수정하시겠습니까?", - "matchFinishMsg" : "{{name}}이(가) 완료되었습니다.", + "matchFinishMsg": "{{name}}이(가) 완료되었습니다.", "excelDownErrMsg": "다운로드할 데이터가 없습니다.", - "isMatchFinishMsg":" 매칭완료를 진행하시겠습니까?", - "isNotMatchFinishMsg":"검토완료중 매칭완료 여부를 체크한 항목이 없습니다.", - "canNotChangeMatStateMsg":"매칭완료된 항목은 상태를 변경할 수 없습니다.", - - - + "isMatchFinishMsg": " 매칭완료를 진행하시겠습니까?", + "isNotMatchFinishMsg": "검토완료중 매칭완료 여부를 체크한 항목이 없습니다.", + "canNotChangeMatStateMsg": "매칭완료된 항목은 상태를 변경할 수 없습니다.", + "isInitPw":"사업자번호 {{dmdInfoId}} 의 비밀번호를 초기화하시겠습니까? (초기화비밀번호는 해당 사업자의 사업자 번호입니다)", "alreadyInsertedErrMsg": "사업자번호 {{num}}(은)는 이미 등록되었습니다. 다시 등록해주세요.", "saveRowErrMsg": "수정을 원하는 행을 선택하고 저장버튼을 눌러주세요.", - "costCalcErrMsg":"공사비 계산 진행 후 저장을 눌러주세요", + "costCalcErrMsg": "공사비 계산 진행 후 저장을 눌러주세요", "maxYearErrMsg": "최대 {{year}}년까지 선택가능합니다.", "checkQtyMsg": "요구수량(m³/일)을 확인해주세요 계산할 수 없습니다." - -} - +} \ No newline at end of file diff --git a/apps/wr/public/sample_ruseCaseMng.xlsx b/apps/wr/public/sample_ruseCaseMng.xlsx index 96027d557..3676b4f71 100644 Binary files a/apps/wr/public/sample_ruseCaseMng.xlsx and b/apps/wr/public/sample_ruseCaseMng.xlsx differ diff --git a/apps/wr/src/entities/auth/api/login-service.ts b/apps/wr/src/entities/auth/api/login-service.ts index 1808f7de8..77d114004 100644 --- a/apps/wr/src/entities/auth/api/login-service.ts +++ b/apps/wr/src/entities/auth/api/login-service.ts @@ -17,7 +17,6 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise<void> { try { - console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN) const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { method: "POST", body: JSON.stringify({ diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index f73b428c5..41774fc59 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,411 +1,357 @@ -import { Search } from 'lucide-react'; -import OlMap from 'ol/Map'; -import View from 'ol/View'; -import { defaults as defaultControls, OverviewMap } from 'ol/control'; -import { defaults } from 'ol/interaction'; -import * as olProj from 'ol/proj'; -import { register } from 'ol/proj/proj4'; -import proj4 from 'proj4'; -import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import GisMapToggle from './GisMapToggle'; -import GisSelect from './GisSelect'; -import GisToolBar from './GisToolBar'; -import GisTree from './GisTree'; -import './lib/css/gis.common.css'; -import GisApp from './lib/gis.core'; +import { Search } from "lucide-react"; +import OlMap from "ol/Map"; +import View from "ol/View"; +import { defaults as defaultControls, OverviewMap } from "ol/control"; +import { defaults } from "ol/interaction"; +import * as olProj from "ol/proj"; +import { register } from "ol/proj/proj4"; +import proj4 from "proj4"; +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import GisMapToggle from "./GisMapToggle"; +import GisSelect from "./GisSelect"; +import GisToolBar from "./GisToolBar"; +import GisTree from "./GisTree"; +import "./lib/css/gis.common.css"; +import GisApp from "./lib/gis.core"; -export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); +export const GisContext = createContext({ + gisCore: null, + gisMap: null, + treeData: null, + checkedLayerIds: null, +}); export const useGisContext = () => useContext(GisContext); export const searchUpperPpls = async (fcltMngNo) => { - const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ''; + const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; + const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { + method: "GET", + }); - const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { - method: "GET", - }); + let data = null; + if (res.ok) { + try { + // 응답이 비어있는지 확인 + const contentLength = res.headers.get("content-length"); + if (contentLength === "0") + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - let data = null; - if (res.ok) { - try { - // 응답이 비어있는지 확인 - const contentLength = res.headers.get('content-length'); - if (contentLength === '0') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 + const text = await res.text(); + if (!text || text.trim() === "") + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 - const text = await res.text(); - if (!text || text.trim() === '') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + // JSON 파싱 시도 + data = JSON.parse(text); + } catch (error) { + console.error("JSON parsing error:", error); + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + } + } - // JSON 파싱 시도 - data = JSON.parse(text); - } catch (error) { - console.error('JSON parsing error:', error); - alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - } - } + const select = GisApp.Module.select; + const gisMap = GisApp.Module.core?.map; + let foundFeature = null; - const select = GisApp.Module.select; + data.forEach((item) => { + const pplMngNo = item.ppl_mng_no.trim(); - const gisMap = GisApp.Module.core?.map; + gisMap + .getLayers() + .getArray() + .filter((e) => e.get("lyrTyNm") == "tb_ms_swage_ppl") + .forEach((layer) => { + const source = layer.getSource?.(); + if (source?.getFeatures) { + const features = source.getFeatures(); + for (const f of features) { + if (f.values_.fclt_mng_no.trim() === pplMngNo) { + foundFeature = f; + break; + } + } + } + }); - let foundFeature = null; + if (foundFeature) select.selectMultiFeature(foundFeature, true); + }); - data.forEach(item => { - const pplMngNo = item.ppl_mng_no.trim(); + if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - gisMap.getLayers().getArray().filter(e => e.get("lyrTyNm") == 'tb_ms_swage_ppl').forEach(layer => { - const source = layer.getSource?.(); - if (source?.getFeatures) { - const features = source.getFeatures(); - for (const f of features) { - if (f.values_.fclt_mng_no.trim() === pplMngNo) { - foundFeature = f; - break; - } - } - } - }); + let geometry = foundFeature?.getGeometry(); + if (geometry) { + let extent = geometry.getExtent(); - if (foundFeature) select.selectMultiFeature(foundFeature, true); - }); - - if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - - let geometry = foundFeature?.getGeometry(); - if (geometry) { - let extent = geometry.getExtent(); - - gisMap.getView().fit(extent, { - duration: 300, - padding: [50, 50, 50, 50], - maxZoom: 14, - constrainResolution: true, - nearest: true, - }); - } + gisMap.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 14, + constrainResolution: true, + nearest: true, + }); + } }; function Gis({ - isOpen, //우측 상세 레이어 팝업 - gisMaps, - isSearchModalOpen, - treeData = [], - isSearch = false, - isMiniMap = false, - setIsOpen, - setFeatureData, - featureData, - imgVisble = false, - searchView: SearchView, - detailView: DetailView, - onFeatureSelect, - setIsSearchModalOpen, - onCheckedLayersChange + isOpen, //우측 상세 레이어 팝업 + gisMaps, + isSearchModalOpen, + treeData = [], + isSearch = false, + isMiniMap = false, + setIsOpen, + setFeatureData, + featureData, + imgVisble = false, + searchView: SearchView, + detailView: DetailView, + onFeatureSelect, + setIsSearchModalOpen, + onCheckedLayersChange, + searchBar: SearchBar, + waterDepth: WaterDepth, + height:height="h-[calc(100vh-10rem)]", }) { - const mapRef = useRef(); - const [mapObject, setMapObject] = useState({}); - const [tabVisible, setTabVisible] = useState(true); - const [layerNm, setLayerNm] = useState('') - // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 - // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 - // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 - const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); - const mapInstanceRef = useRef(null); - const checkedLayerIds = useRef([]); - const layerNmRef = useRef('') + const mapRef = useRef(); + const [mapObject, setMapObject] = useState({}); + const [tabVisible, setTabVisible] = useState(true); + const [layerNm, setLayerNm] = useState(""); + // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 + // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 + const gisCore = useMemo( + () => new GisApp.core({ targetId: mapRef.current }), + [], + ); + const mapInstanceRef = useRef(null); + const checkedLayerIds = useRef([]); + const layerNmRef = useRef(""); - // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 - // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); - // const facilityOverlaysRef = useRef([]); - // 체크된 레이어 상태 관리 - const [checkedLayers, setCheckedLayers] = useState([]); - const [checkedLayersData, setCheckedLayersData] = useState([]); + // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 + // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); + // const facilityOverlaysRef = useRef([]); + // 체크된 레이어 상태 관리 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); - let gisMap = null; - const closePopup = () => { - setIsOpen(false); // 팝업 닫기 - setFeatureData(null) - }; + let gisMap = null; + const closePopup = () => { + setIsOpen(false); // 팝업 닫기 + setFeatureData(null); + }; - // Feature Click Event - const handleFeatureSelect = useCallback((data) => { - console.log(data); - // gisMap = mapInstanceRef.current; - // gisMap = mapInstanceRef.current; + // Feature Click Event + const handleFeatureSelect = useCallback( + (data) => { + onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 + }, + [mapInstanceRef], + ); - // if (!gisMap) { - // console.warn('지도가 초기화되지 않았습니다.'); - // return; - // } - // if (!gisMap) { - // console.warn('지도가 초기화되지 않았습니다.'); - // return; - // } + // 지도 생성 및 초기화 + useEffect(() => { + if (!mapRef.current) return; + if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 - // // 항상 같은 select 객체 사용 - // const select = GisApp.Module.select; - // if (!select) return; - // // 항상 같은 select 객체 사용 - // const select = GisApp.Module.select; - // if (!select) return; + // EPSG:5179 등록 + proj4.defs([ + [ + "EPSG:5179", + "+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + ], + ]); + register(proj4); - // // 1. 기존 선택 초기화 - // select.resetSelectedFeature(); + // OpenLayers 지도 객체 생성 + const map = new OlMap({ + layers: [gisCore.baseLayers["base"], gisCore.baseLayers["satellite"]], + renderer: "webgl", + target: mapRef.current, + interactions: defaults({ doubleClickZoom: false }), + controls: defaultControls({ + zoom: false, + rotate: false, + attributionOptions: { + collapsible: false, + }, + }).extend([]), + view: new View({ + projection: GisApp.TO_PROJ, + zoom: gisCore.defaults.zoom, + center: olProj.transform( + [127.4875, 37.4916], + GisApp.FROM_PROJ, + GisApp.TO_PROJ, + ), + minZoom: gisCore.defaults.minZoom, + maxZoom: gisCore.defaults.maxZoom, + }), + }); - // // 2. data가 OpenLayers Feature 인스턴스인지 확인 - // let foundFeature = null; - // if (data && typeof data.getGeometry === 'function' && typeof data.setStyle === 'function') { - // // 이미 Feature 인스턴스라면 그대로 사용 - // foundFeature = data; - // } else { - // // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - // let featureId = data.fid; - // const layerName = layerNmRef.current - // if (layerName === '하수재이용시설') { - // featureId = data.searchPk; - // } else if (layerName === '빗물이용시설' || layerName == '중수도시설') { - // featureId = data.bdstInfoSn; - // } else if (layerName === '하수처리시설') { - // featureId = data.bscFctCd; - // } else if (layerName === '온배수시설') { - // featureId = data.bdstRuseFcltSn; - // } else if(layerName == '수요처'){ - // featureId = data.dmdInfoId.replaceAll('-','') - // } - // else { - // console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm); - // featureId = data.fid || data.id || data.ftrIdn; - // } - // // gisMap.getLayers().getArray().forEach(layer => { - // // const source = layer.getSource?.(); - // // if (source?.getFeatureById) { - // // const feature = source.getFeatureById(String(featureId).trim()); - // // if (feature) foundFeature = feature; - // // } - // // }); - // // const [layerType, id] = String(featureId).split('.'); - // gisMap.getLayers().getArray().forEach(layer => { - // const source = layer.getSource?.(); - // if (source?.getFeatures) { - // const allFeatures = source.getFeatures(); - // for (const f of allFeatures) { - // const id = String(f.getId()); - // // console.log('id', id, featureId) - // if (id && id.split('.')[1] === String(featureId)) { - // foundFeature = f; - // break; - // } - // } - // } - // }); - // } + gisMaps.current = map; - // // 3. 피처 선택 - // if (foundFeature) { - // let geometry = foundFeature.getGeometry(); - // if (geometry) { - // let extent = geometry.getExtent(); + // 미니맵 추가 + if (isMiniMap) { + const overviewMap = new OverviewMap({ + layers: [GisApp.Module.core.baseMiniMapLayers["base"]], + view: new View({ + projection: GisApp.TO_PROJ, + minZoom: 9, + }), + collapsed: false, + collapseLabel: "\u00BB", + label: "\u00AB", + }); + map.addControl(overviewMap); + } - // gisMap.getView().fit(extent, { - // duration: 300, - // padding: [50, 50, 50, 50], - // maxZoom: 16, - // constrainResolution: true, - // nearest: true, - // }); - // } - // select.selectFeature(foundFeature, true); - // } else { - // console.warn('OpenLayers Feature를 찾을 수 없습니다:', data); - // } + // 스케일 컨트롤 및 베이스 레이어 설정 + gisCore.__proto__.map = map; + gisCore.scaleControl(); + gisCore.changeBaseLayer("base"); + setMapObject({ map: map }); + mapInstanceRef.current = map; // 생성 완료 플래그 설정 + }, []); - // setFeatureData(data); - // setIsOpen(true); - // setIsSearchModalOpen(false); + // treeData 변경 시 레이어 코드 생성 + useEffect(() => { + if (!treeData || treeData.length === 0) return; - onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 - console.log(data) - }, [mapInstanceRef]); + // 기존 오버레이 및 레이어 모두 삭제 + gisCore.deleteAllOverlay(); + gisCore.deleteAllLayer(); - // 지도 생성 및 초기화 - useEffect(() => { - if (!mapRef.current) return; - if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 + // 트리 데이터를 레이어 코드 객체로 변환 + const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn"); + GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 - // EPSG:5179 등록 - proj4.defs([ - [ - 'EPSG:5179', - '+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', - ], - ]); - register(proj4); + //setTreeData(data); + // 편집 가능한 레이어 목록 생성 + //const layerItems = getLayerItems(layerCode); + //setEditLayerItems(prev => [...layerItems]) + }, [treeData]); - // OpenLayers 지도 객체 생성 - const map = new OlMap({ - layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], - renderer: 'webgl', - target: mapRef.current, - interactions: defaults({ doubleClickZoom: false }), - controls: defaultControls({ - zoom: false, - rotate: false, - attributionOptions: { - collapsible: false, - }, - }).extend([]), - view: new View({ - projection: GisApp.TO_PROJ, - zoom: gisCore.defaults.zoom, - center: olProj.transform( - [127.4875, 37.4916], - GisApp.FROM_PROJ, - GisApp.TO_PROJ - ), - minZoom: gisCore.defaults.minZoom, - maxZoom: gisCore.defaults.maxZoom, - }), - }); + const getLayerItems = (layerCode) => { + const layerItems = Object.entries(layerCode) + .filter(([key, obj], idx) => obj?.updtPsblYn === "Y") + .map(([key, obj]) => ({ + ...obj, + name: obj.lyrNm, + label: obj.lyrNm, + value: obj.lyrSn + "", + })) + .sort((a, b) => { + if (a.sortOrdr > b.sortOrdr) return 1; + if (a.sortOrdr < b.sortOrdr) return -1; + return 0; + }); + return layerItems; + }; - gisMaps.current = map + if (mapObject.map) { + try { + gisMap = mapObject.map; + } catch (e) { + console.error("오류 발생:", e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 + } + } - // 미니맵 추가 - if (isMiniMap) { - const overviewMap = new OverviewMap({ - layers: [GisApp.Module.core.baseMiniMapLayers['base']], - view: new View({ - projection: GisApp.TO_PROJ, - minZoom: 9, - }), - collapsed: false, - collapseLabel: '\u00BB', - label: '\u00AB', - }); - map.addControl(overviewMap); - } + // 체크된 레이어 정보를 처리하는 함수 + const handleCheckedLayersChange = useCallback( + (checkedIds, checkedNodesData) => { + setTimeout(() => { + setCheckedLayers(checkedIds); + setCheckedLayersData(checkedNodesData); + onCheckedLayersChange?.(checkedIds, checkedNodesData); + }, 1000); + }, + [onCheckedLayersChange], + ); - // 스케일 컨트롤 및 베이스 레이어 설정 - gisCore.__proto__.map = map; - gisCore.scaleControl(); - gisCore.changeBaseLayer('base'); - setMapObject({ map: map }); - mapInstanceRef.current = map; // 생성 완료 플래그 설정 + return ( + <> + <GisContext.Provider + value={{ + gisCore, + gisMap: mapInstanceRef.current, + treeData, + checkedLayerIds, + }} + > + <div className={`relative flex ${height}`}> + <div + ref={mapRef} + className="gis-map relative flex-1" + style={{ height: "100%", padding: 0 }} + > + <GisTree + visible={tabVisible} + setVisible={setTabVisible} + onCheckedChange={handleCheckedLayersChange} + onLayerIconClick={(layerNm) => { + layerNmRef.current = layerNm; + setLayerNm(layerNm); + setIsOpen(true); + const dummyFeature = { + getProperties: () => ({}), // 최소 구조 + }; + onFeatureSelect(dummyFeature); + }} + imageVisible={imgVisble} + /> - }, []); + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} + {isOpen && !isSearchModalOpen && ( + <DetailView + featureData={featureData || { getProperties: () => ({}) }} + seletectedLayerNm={layerNm} + onClose={closePopup} // 닫기 콜백 전달 + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* SearchView가 존재할 때만 렌더링 */} + {SearchView && ( + <SearchView + open={isSearchModalOpen} + onOpenChange={setIsSearchModalOpen} + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* 시도/시군구 조회 영역 */} + {SearchBar && ( + <div + className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded w-max" + > + <SearchBar /> + </div> + )} + <GisToolBar /> + <GisMapToggle /> + <GisSelect onFeatureSelect={handleFeatureSelect} /> + {typeof WaterDepth === "function" && <WaterDepth onClose={() => setIsOpen(false)} />} + </div> + </div> - // treeData 변경 시 레이어 코드 생성 - useEffect(() => { - if (!treeData || treeData.length === 0) return; - - // 기존 오버레이 및 레이어 모두 삭제 - gisCore.deleteAllOverlay(); - gisCore.deleteAllLayer(); - - // 트리 데이터를 레이어 코드 객체로 변환 - const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); - GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 - - //setTreeData(data); - // 편집 가능한 레이어 목록 생성 - //const layerItems = getLayerItems(layerCode); - //setEditLayerItems(prev => [...layerItems]) - }, [treeData]); - - const getLayerItems = (layerCode) => { - const layerItems = Object.entries(layerCode) - .filter(([key, obj], idx) => obj?.updtPsblYn === 'Y') - .map(([key, obj]) => ({ - ...obj, - name: obj.lyrNm, - label: obj.lyrNm, - value: obj.lyrSn + '', - })) - .sort((a, b) => { - if (a.sortOrdr > b.sortOrdr) return 1; - if (a.sortOrdr < b.sortOrdr) return -1; - return 0; - }); - return layerItems; - }; - - if (mapObject.map) { - try { - gisMap = mapObject.map; - } catch (e) { - console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 - } - } - - // 체크된 레이어 정보를 처리하는 함수 - const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => { - setTimeout(() => { - setCheckedLayers(checkedIds); - setCheckedLayersData(checkedNodesData); - onCheckedLayersChange?.(checkedIds, checkedNodesData); - }, 1000); - }, [onCheckedLayersChange]); - - return ( - <> - <GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}> - <div className="h-[calc(100vh-10rem)] flex relative"> - <div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} /> - <GisTree - visible={tabVisible} - setVisible={setTabVisible} - onCheckedChange={handleCheckedLayersChange} - onLayerIconClick={(layerNm) => { - layerNmRef.current = layerNm - setLayerNm(layerNm); setIsOpen(true); const dummyFeature = { - getProperties: () => ({}), // 최소 구조 - }; - onFeatureSelect(dummyFeature); - }} - imageVisible={imgVisble} - /> - - {/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */} - - {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} - {isOpen && !isSearchModalOpen && ( - <DetailView - featureData={featureData || { getProperties: () => ({}) }} - seletectedLayerNm={layerNm} - onClose={closePopup} // 닫기 콜백 전달 - onFeatureSelect={handleFeatureSelect} - /> - )} - {/* SearchView가 존재할 때만 렌더링 */} - {SearchView && ( - <SearchView - open={isSearchModalOpen} - onOpenChange={setIsSearchModalOpen} - onFeatureSelect={handleFeatureSelect} - /> - )} - {/* <GisLonLat /> */} - <GisToolBar /> - <GisMapToggle /> - <GisSelect onFeatureSelect={handleFeatureSelect} /> - </div> - - {isSearch && ( - <div className="absolute right-8 top-[305px] z-20"> - <button - onClick={() => setIsSearchModalOpen(true)} - className="overflow-hidden w-[40px] h-[40px] bg-white - border border-[#9aa7b8] - rounded - shadow-[0_0_5px_#999] - flex items-center justify-center - p-0 - " - > - <Search size={24} className="text-gray-700" /> - </button> - </div> - )} - </GisContext.Provider> - </> - ); + {isSearch && ( + <div className="absolute right-8 top-[305px] z-20"> + <button + onClick={() => setIsSearchModalOpen(true)} + className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]" + > + <Search size={24} className="text-gray-700" /> + </button> + </div> + )} + </GisContext.Provider> + </> + ); } export default React.memo(Gis); diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js index d2594ba95..45b44c882 100644 --- a/packages/common/business_components/src/ui/map/GisContent.js +++ b/packages/common/business_components/src/ui/map/GisContent.js @@ -1,74 +1,61 @@ -import { Search } from 'lucide-react'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useGisContext } from './Gis'; -import './lib/css/gis.edit.css'; -import GisApp from './lib/gis.core'; +import { Search } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useGisContext } from "./Gis"; +import "./lib/css/gis.edit.css"; +import GisApp from "./lib/gis.core"; -export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) { - const { gisMap, gisCore } = useGisContext(); - const [visibleComponents, setVisibleComponents] = useState({}); - const addressResultLayer = useRef(null); - const [playShow, setPlayShow] = useState({ show: false }); +export default function GisContent({ + edit = false, + onFeatureSelect, + isSearch, + setIsSearchModalOpen, +}) { + const { gisMap, gisCore } = useGisContext(); - //셀렉트 버튼 이벤트 - // const clickFeature = useCallback(feature => { - // if (feature) { - // const layer = feature.getLayer(); - // setVisibleComponents(prevState => ({ - // ...prevState, - // })); - // } - // }, []); + //셀렉트 버튼 이벤트 + const selectRslt = useCallback((feature) => { + GisApp.Module.layer.highLightFeature( + [feature], + { + id: GisApp.hlLayerId, + zIndex: 10, + opacity: 1, + selectable: true, + }, + { width: 30 }, + ); + clickFeature(feature); + }, []); - const selectRslt = useCallback(feature => { - GisApp.Module.layer.highLightFeature( - [feature], - { - id: GisApp.hlLayerId, - zIndex: 10, - opacity: 1, - selectable: true, - }, - { width: 30 } - ); - clickFeature(feature); - }, []); + useEffect(() => { + if (gisMap) { + setVisibleComponents((prevState) => ({ + ...prevState, + })); + } + }, [gisMap]); - useEffect(() => { - if (gisMap) { - setVisibleComponents(prevState => ({ - ...prevState, - })); - } - }, [gisMap]); + if (!gisMap) return null; - if (!gisMap) return null; + return ( + <> + <GisTree /> + <GisMapToggle /> + <GisLonLat /> + <GisToolBar /> + <GisSelect onFeatureSelect={onFeatureSelect} /> - return ( - <> - {/*<GisTree />*/} - {/* <GisMapToggle /> - <GisLonLat /> - <GisToolBar /> */} - {/* <GisSelect onFeatureSelect={onFeatureSelect} /> */} - - {/* 검색 모달 */} - {isSearch && ( - <div className="absolute right-8 top-[254px] z-20"> - <button - onClick={() => setIsSearchModalOpen(true)} - className="overflow-hidden w-[40px] h-[40px] bg-white - border border-[#9aa7b8] - rounded - shadow-[0_0_5px_#999] - flex items-center justify-center - p-0 - " - > - <Search size={24} className="text-gray-700" /> - </button> - </div> - )} - </> - ); + {/* 검색 모달 */} + {isSearch && ( + <div className="absolute right-8 top-[254px] z-20"> + <button + onClick={() => setIsSearchModalOpen(true)} + className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]" + > + <Search size={24} className="text-gray-700" /> + </button> + </div> + )} + </> + ); } diff --git a/packages/common/business_components/src/ui/map/GisTab.js b/packages/common/business_components/src/ui/map/GisTab.js deleted file mode 100644 index 57d8a48e8..000000000 --- a/packages/common/business_components/src/ui/map/GisTab.js +++ /dev/null @@ -1,84 +0,0 @@ -// import React, { useState } from 'react'; -// import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs'; -// import GisTree from './GisTree'; -// import GisSearch from './GisSearch'; -// import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; -// import SearchModal from '@/map/SearchModal'; - - -// function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen, onFeatureSelect }) { -// const [activeTab, setActiveTab] = useState('layers'); - -// return ( -// <> -// <div -// className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 -// ${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`} -// > -// <Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden"> -// <TabsList className="flex-none mt-4 border-none rounded-none"> -// <TabsTrigger value="layers" className="flex-1 h-10">레이어</TabsTrigger> -// {/* {isSearch && ( -// <button -// onClick={() => setIsSearchModalOpen(true)} -// className="h-10 w-10 flex items-center justify-center px-0" -// > -// <Search size={18} className="text-gray-700" /> -// </button> -// // <TabsTrigger value="search" className="flex-1 h-10 flex items-center justify-center"> -// // <Search size={18} /> -// // </TabsTrigger> -// )} */} -// </TabsList> - -// {/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */} -// <div -// className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${activeTab === 'layers' ? 'block' : 'hidden' -// }`} -// > -// <GisTree /> -// </div> - -// {/* GisSearch도 동일하게 처리 */} -// {/* {isSearch && ( -// <div -// className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${ -// activeTab === 'search' ? 'block' : 'hidden' -// }`} -// > -// <GisSearch /> -// </div> -// )} */} - - -// {/* 🔍 검색 모달 */} -// {/* <SearchModal -// open={isSearchModalOpen} -// onOpenChange={setIsSearchModalOpen} -// onFeatureSelect={onFeatureSelect} -// /> */} -// </Tabs> - -// {/* 접기 버튼 */} -// <button -// onClick={() => setVisible(false)} -// className="absolute top-1/2 right-[-3px] transform -translate-y-1/2 z-50 bg-white border border-l border-gray-300 rounded-l-none rounded-r px-1 py-2 shadow-sm" -// > -// <ChevronLeft size={16} /> -// </button> -// </div> - -// {/* 열기 버튼 */} -// {!visible && ( -// <button -// onClick={() => setVisible(true)} -// className="absolute top-1/2 left-[-3px] transform -translate-y-1/2 z-50 bg-white border border-r border-gray-300 rounded-r px-1 py-2 shadow-sm" -// > -// <ChevronRight size={16} /> -// </button> -// )} -// </> -// ); -// } - -// export default React.memo(GisTab); diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index ccaa3c186..47e86d4dc 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -189,14 +189,14 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes }; const handleLayerImgClick = (nodeNm, nodeId, layerNm) => { if(!checked.includes(nodeId)) alert('시설을 선택해주세요') - else onLayerIconClick(layerNm) + else onLayerIconClick(nodeNm) } if (!treeData || treeData.length === 0) return null; return ( <> <div - className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 + className={`absolute top-0 left-0 w-[250px] h-full bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 ${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`} > <div diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index b997a7a0a..f7bb7b3ac 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -130,8 +130,6 @@ GisOlLayer.prototype = { strategy: all, }); return source; - - return source; }, /** * [사용]WFS 레이어 생성 @@ -145,11 +143,11 @@ GisOlLayer.prototype = { let source; let lyrTyNm = option.lyrTyNm; - if(lyrTyNm == 'tb_ms_flood_dngr_map' - || lyrTyNm == 'tb_ms_sbmrg_dngr_map' - || lyrTyNm == 'tb_ms_swage_mhl' - || lyrTyNm == 'tb_ms_swage_ppl' - || lyrTyNm == 'tb_ms_swage_pmstn' + if(lyrTyNm == 'tb_ms_flood_dngr_map' //침수위험지도 + || lyrTyNm == 'tb_ms_sbmrg_dngr_map' //도시침수지도 + || lyrTyNm == 'tb_ms_swage_mhl' //맨홀 + || lyrTyNm == 'tb_ms_swage_ppl' //관로 + || lyrTyNm == 'tb_ms_swage_pmstn' //펌프장 ) { source = this.getVectorSourceBbox(option); }else { diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index c3f5f8a60..fa7c9b7fe 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -78,6 +78,10 @@ export function UiTable({ } }, [tableData, headlist]); + useEffect(() => { + setIdx(index); + }, [index]) + const handlePageChange = (page: number) => { setCur(page); onPageChange(page); @@ -120,10 +124,10 @@ export function UiTable({ (boldArray.includes(index) ? ( <TableCell className={`${head.align === "right" ? "text-right" : head.align === "center" ? "text-center" : "text-center"} ${cellClick && idx === index - ? "cursor-pointer !font-[1000]" - : cellClick - ? "cursor-pointer" - : "" + ? "cursor-pointer !font-[1000]" + : cellClick + ? "cursor-pointer" + : "" } sticky top-20 z-40 bg-secondary !font-bold`} style={{ top: `${headerHeight + index * 32 - 4}px` }} onClick={() => handleCellClick(index, head.id)} @@ -151,10 +155,10 @@ export function UiTable({ ) : ( <TableCell className={`${head.align === "right" ? "text-right" : head.align === "center" ? "text-center" : "text-center"} ${cellClick && idx === index - ? "cursor-pointer !font-[1000]" - : cellClick - ? "cursor-pointer" - : "" + ? "cursor-pointer !font-[1000]" + : cellClick + ? "cursor-pointer" + : "" } mt-[${boldArray.length * 40}px]`} onClick={() => handleCellClick(index, head.id)} key={`cell-${head.id}-${index}`}