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/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index ea71862d8..c0d38a5c5 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -233,6 +233,7 @@ const Map = () => { <>
{ 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/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/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/map/page.tsx b/apps/fo/app/pages/map/page.tsx index 2c27869b0..edd7b70d2 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; @@ -123,24 +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={undefined} + searchBar={(props:any) => } />
); 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..e9084870a --- /dev/null +++ b/apps/fo/app/pages/map/searchBar/searchBar.tsx @@ -0,0 +1,159 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import { GEOSERVER_URL } from "@/src/shared/config/env"; + +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/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/shared/config/env.ts b/apps/fo/src/shared/config/env.ts index cc516daf1..8baf1b0f0 100644 --- a/apps/fo/src/shared/config/env.ts +++ b/apps/fo/src/shared/config/env.ts @@ -2,8 +2,7 @@ export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "FO"; export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_WR_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL ||'http://con.mind-one.co.kr:11080/api/v1'; -//export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; -export const SERVER_API_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; +export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; export const SERVER_API_CS_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||'http://dev.onasys.co.kr:8000'; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; 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/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/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 8cfb27e2f..420dfdfb0 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -5,6 +5,7 @@ import { useEffect, useRef, useState, useCallback } from "react"; import { mapService } from "../service"; import NewWindowPopup from "../newWindowPopup"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; +import { SearchBar } from "../searchBar/searchBar"; const Map = () => { const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 @@ -61,24 +62,28 @@ const Map = () => { // 2. data가 OpenLayers Feature 인스턴스인지 확인 let foundFeature: any = null; - console.log(data) + console.log(data); //console.log(data.getId()) let featureId = data.fid; const layerName = data.layerNm; - + if (layerName === "재이용시설") { // 재이용시설 featureId = `${layerName}.${data.searchPk}`; - } else if ( //빗물이용시설, 중수도시설 + } else if ( + //빗물이용시설, 중수도시설 layerName === "빗물이용시설" || layerName == "tb_wr_rswtr_fclt" ) { featureId = `${layerName}.${data.bdstInfoSn}`; - } else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설 + } else if (layerName === "tb_il_swage_prcs_fclt") { + //하수처리시설 featureId = `${layerName}.${data.bscFctCd}`; - } else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설 + } else if (layerName === "tb_wr_htdfl_fclt") { + //온배수시설 featureId = `${layerName}.${data.bdstRuseFcltSn}`; - } else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처 + } else if (layerName == "tb_wr_ruse_fclt_dmd_co") { + //수요처 featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`; } else { console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName); @@ -120,17 +125,6 @@ const Map = () => { setIsSearchModalOpen(false); }; - // MapSearchBar 래퍼 컴포넌트 - // const CustomMapSearchBar = (props: any) => { - // return ( - // - // ); - // }; - return (
{ isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} - searchBar={undefined} + searchBar={(props: any) => { + 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 ( + + ); + }} />
); diff --git a/apps/wr/app/map/searchBar/searchBar.tsx b/apps/wr/app/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..7502daf94 --- /dev/null +++ b/apps/wr/app/map/searchBar/searchBar.tsx @@ -0,0 +1,299 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import { SERVER_API_WR_URL } from "@/src/shared/config"; + +const geoserverUrl = + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; + +const FACILITY_TYPE_URL_MAP: Record = { + 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; + 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([ + { 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 [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 ( +
+
+
+
+ {facilityTypes.length > 1 && ( + + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> +
+
+ +
+
+
+
+ ); +} 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([]); const [objSearch, setObjSearch] = useState({ sgg: "", city: "" }); - + const [sidoCd, setSidoCd] = useState(""); const [sigunguCd, setSigunguCd] = useState(""); - const [selectUtz, setSelectUtz] = useState(["조경용수"]); + // const [selectUtz, setSelectUtz] = useState(["조경용수"]); + const [selectUtz, setSelectUtz] = useState(""); const [name, setName] = useState(""); + 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() { <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/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/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/middleware.ts b/apps/wr/middleware.ts index 61f41d634..1a90a5591 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -8,6 +8,7 @@ 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 || ""; export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; @@ -135,6 +136,15 @@ 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)); + } + return NextResponse.next(); } @@ -146,6 +156,7 @@ export const config = { "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*", + "/reuse-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/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/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 89b7e982b..92a64ed44 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,323 +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, - searchBar: SearchBar, - waterDepth: WaterDepth, + 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) => { - onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 - }, [mapInstanceRef]); + // Feature Click Event + const handleFeatureSelect = useCallback( + (data) => { + onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 + }, + [mapInstanceRef], + ); - // 지도 생성 및 초기화 - useEffect(() => { - if (!mapRef.current) return; - if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 + // 지도 생성 및 초기화 + useEffect(() => { + if (!mapRef.current) return; + if (mapInstanceRef.current) 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); + // 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); - // 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, - }), - }); + // 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, + }), + }); - gisMaps.current = map + gisMaps.current = map; - // 미니맵 추가 - 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); - } + // 미니맵 추가 + 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); + } - // 스케일 컨트롤 및 베이스 레이어 설정 - gisCore.__proto__.map = map; - gisCore.scaleControl(); - gisCore.changeBaseLayer('base'); - setMapObject({ map: map }); - mapInstanceRef.current = map; // 생성 완료 플래그 설정 + // 스케일 컨트롤 및 베이스 레이어 설정 + gisCore.__proto__.map = map; + gisCore.scaleControl(); + gisCore.changeBaseLayer("base"); + setMapObject({ map: map }); + mapInstanceRef.current = map; // 생성 완료 플래그 설정 + }, []); - }, []); + // treeData 변경 시 레이어 코드 생성 + useEffect(() => { + if (!treeData || treeData.length === 0) return; - // treeData 변경 시 레이어 코드 생성 - useEffect(() => { - if (!treeData || treeData.length === 0) return; + // 기존 오버레이 및 레이어 모두 삭제 + gisCore.deleteAllOverlay(); + gisCore.deleteAllLayer(); - // 기존 오버레이 및 레이어 모두 삭제 - gisCore.deleteAllOverlay(); - gisCore.deleteAllLayer(); + // 트리 데이터를 레이어 코드 객체로 변환 + const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn"); + GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 - // 트리 데이터를 레이어 코드 객체로 변환 - const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); - GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 + //setTreeData(data); + // 편집 가능한 레이어 목록 생성 + //const layerItems = getLayerItems(layerCode); + //setEditLayerItems(prev => [...layerItems]) + }, [treeData]); - //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; + }; - 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); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 + } + } - 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], + ); - // 체크된 레이어 정보를 처리하는 함수 - 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={`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} + /> - 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} - /> - - {/* 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 top-4 left-1/2 -translate-x-1/2 z-30 bg-gray-300 rounded " style={{ maxWidth: 'calc(100% - 2rem)' }}> - <SearchBar/> - </div> - )} - <GisToolBar /> - <GisMapToggle /> - <GisSelect onFeatureSelect={handleFeatureSelect} /> + {/* 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> + </div> + </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/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 335b1b119..47e86d4dc 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -196,7 +196,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes 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 412b0d8b9..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 @@ -143,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 {