diff --git a/apps/dm/.env.development b/apps/dm/.env.development index 622545fd6..b93394fe0 100644 --- a/apps/dm/.env.development +++ b/apps/dm/.env.development @@ -1,7 +1,8 @@ NEXT_PUBLIC_SITE_ID = DM -NEXT_PUBLIC_PORT = 3000 -NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 -NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 +NEXT_PUBLIC_PORT = 11035 +NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035 +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload @@ -9,9 +10,7 @@ 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_REUSE_API_URL=http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API # NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 diff --git a/apps/dm/.env.local b/apps/dm/.env.local index f691fb313..8ac1f688a 100644 --- a/apps/dm/.env.local +++ b/apps/dm/.env.local @@ -3,17 +3,19 @@ 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_PORT = 11035 +NEXT_PUBLIC_PORT = 3005 #NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035 -NEXT_PUBLIC_PROXY_HOST = http://localhost:3000/ -NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 +NEXT_PUBLIC_PROXY_HOST = http://localhost:3005/ +NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 NEXT_PUBLIC_CS_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_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload -#NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API -NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 +NEXT_PUBLIC_REUSE_API_URL=http://con.mind-one.co.kr:11080/api/v1 +NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API +#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 diff --git a/apps/dm/.env.production b/apps/dm/.env.production index 7576a7dfb..85e97b3ab 100644 --- a/apps/dm/.env.production +++ b/apps/dm/.env.production @@ -11,7 +11,7 @@ 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_REUSE_API_URL=http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/sewerage-service/api 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 40ff3948f..33da31f12 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -74,7 +74,7 @@ export type FormData = z.infer; function SdmInfoInsertContent() { const searchParams = useSearchParams(); - const {map, insertSdmInfoData, Sdm } = useDmStore((state) => state); + const {insertSdmInfoData, Sdm ,setSubsInfo} = useDmStore((state) => state); const { successLogin } = Sdm; const [ismoneyDisabled, setIsmoneyDisabled] = useState(false); const formSchema = sdmInfoFormSchema; @@ -127,7 +127,9 @@ function SdmInfoInsertContent() { if (local) { setIsLogin(true); } - }, []); + setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) + + }, [setSubsInfo]); const form = useForm({ resolver: zodResolver(formSchema), @@ -135,12 +137,27 @@ function SdmInfoInsertContent() { mode: "onChange", }); + const subsInfo = useDmStore((state) => state.map.subsInfo); + useEffect(()=>{ - if(map.subsInfo.length>0){ - form.setValue('candidateTitle', map.subsInfo) + //map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함 + if(subsInfo.length>0){ + form.setValue('candidateTitle', subsInfo) } -},[map.subsInfo, form]) +},[subsInfo, form]) + +//후보지 팝업 값 셋팅 +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]); // 로그인 성공 시 모달 닫기 및 폼 업데이트 useEffect(() => { @@ -154,6 +171,7 @@ function SdmInfoInsertContent() { setFacilityName(userData.dmdCoNm); form.setValue('sdmNm', userData.dmdCoNm); } + } catch (error) { console.error('Error parsing user data:', error); } @@ -208,9 +226,10 @@ const router = useRouter(); - - await insertSdmInfoData(apiData); + setTimeout(() => { + router.push("/"); + }, 1000); } catch (error) { console.error('Error submitting form:', error); } 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 926a8f902..a0aa35145 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx @@ -47,6 +47,7 @@ export default function MyForm() { dmdInfoSn: '', dmdInfoFk: '', status: '', + splyCndtList: [] }; formDataRef.current = convertedData; insertSdmData(convertedData).catch(error => { diff --git a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx index 8d5bdd0ba..04dfa58df 100644 --- a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx +++ b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx @@ -53,14 +53,16 @@ export default function WrAboutPage() { }, observerOptions); // 각 카드 요소를 관찰 - cardRefs.current.forEach((card) => { + const cardsSnapshot = [...cardRefs.current]; + + cardsSnapshot.forEach((card) => { if (card) { observer.observe(card); } }); - + return () => { - cardRefs.current.forEach((card) => { + cardsSnapshot.forEach((card) => { if (card) { observer.unobserve(card); } diff --git a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx index 7ae840beb..826ab3222 100644 --- a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx +++ b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { CHART_FORM, MainChart, type ChartConfig } from '@common/business_components'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react'; interface RegionData { name: string; @@ -193,16 +193,16 @@ const WrStatusPage: React.FC = () => { }; const tableData: TableData[] = [ - { region: '전국', sewage: '717(1,137,538)', rainwater: '3544(13,945)', reclaimed: '1018(222,476)', thermal: '123' }, + { region: '전국', sewage: '717(1,137,538)', rainwater: '3544(13,945)', reclaimed: '1018(222,476)', thermal: '123(492)' }, { region: '서울특별시', sewage: '4(114,908)', rainwater: '816(430)', reclaimed: '371(341)', thermal: '0' }, - { region: '부산광역시', sewage: '13(146,197)', rainwater: '68(335)', reclaimed: '20(512)', thermal: '1' }, - { region: '대구광역시', sewage: '9(102,380)', rainwater: '294(133)', reclaimed: '14(44)', thermal: '2' }, - { region: '인천광역시', sewage: '17(57,691)', rainwater: '165(642)', reclaimed: '35(7,859)', thermal: '15' }, - { region: '광주광역시', sewage: '3(28,738)', rainwater: '74(23)', reclaimed: '15(450)', thermal: '15' }, - { region: '대전광역시', sewage: '2(11,966)', rainwater: '52(2)', reclaimed: '11(492)', thermal: '20' }, - { region: '울산광역시', sewage: '8(36,076)', rainwater: '111(39)', reclaimed: '19(28,904)', thermal: '30' }, + { region: '부산광역시', sewage: '13(146,197)', rainwater: '68(335)', reclaimed: '20(512)', thermal: '1(18)' }, + { region: '대구광역시', sewage: '9(102,380)', rainwater: '294(133)', reclaimed: '14(44)', thermal: '2(12)' }, + { region: '인천광역시', sewage: '17(57,691)', rainwater: '165(642)', reclaimed: '35(7,859)', thermal: '15(32)' }, + { region: '광주광역시', sewage: '3(28,738)', rainwater: '74(23)', reclaimed: '15(450)', thermal: '15(6)' }, + { region: '대전광역시', sewage: '2(11,966)', rainwater: '52(2)', reclaimed: '11(492)', thermal: '20(100)' }, + { region: '울산광역시', sewage: '8(36,076)', rainwater: '111(39)', reclaimed: '19(28,904)', thermal: '30(130)' }, { region: '세종특별자치시', sewage: '9(2,555)', rainwater: '65(16)', reclaimed: '0(0)', thermal: '0' }, - { region: '경기도', sewage: '176(313,298)', rainwater: '1009(4,735)', reclaimed: '252(77,096)', thermal: '40' }, + { region: '경기도', sewage: '176(313,298)', rainwater: '1009(4,735)', reclaimed: '252(77,096)', thermal: '40(143)' }, { region: '강원특별자치도', sewage: '69(19,677)', rainwater: '69(4)', reclaimed: '22(447)', thermal: '0' }, { region: '충청북도', sewage: '38(15,607)', rainwater: '17(91)', reclaimed: '91(2,211)', thermal: '0' }, { region: '충청남도', sewage: '67(41,368)', rainwater: '54(77)', reclaimed: '188(50,434)', thermal: '0' }, @@ -213,7 +213,7 @@ const WrStatusPage: React.FC = () => { { region: '제주특별자치도', sewage: '8(1,666)', rainwater: '47(5,746)', reclaimed: '64(660)', thermal: '0' }, ]; - const regionData: RegionData[] = [ + const regionData: RegionData[] = useMemo(() => [ { name: '서울', x: 170, y: 140, values: [0.3, 0.3, 0.2, 0.2] }, { name: '인천', x: 120, y: 150, values: [0.3, 0.3, 0.2, 0.2] }, { name: '경기', x: 210, y: 210, values: [0.3, 0.3, 0.2, 0.2] }, @@ -231,7 +231,7 @@ const WrStatusPage: React.FC = () => { { name: '울산', x: 450, y: 500, values: [0.3, 0.3, 0.2, 0.2] }, { name: '부산', x: 420, y: 550, values: [0.3, 0.3, 0.2, 0.2] }, { name: '제주', x: 60, y: 750, values: [0.3, 0.3, 0.2, 0.2] }, - ]; + ], []); const handleMarkerClick = (regionId: string) => { if (selectedRegion === regionId) { @@ -242,7 +242,7 @@ const WrStatusPage: React.FC = () => { }; - const createDonutMarker = (svg: SVGSVGElement, x: number, y: number, name: string, values: number[]) => { + const createDonutMarker = useCallback((svg: SVGSVGElement, x: number, y: number, name: string, values: number[]) => { const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FED16A']; const radius = 26; const innerRadius = 18; @@ -334,7 +334,7 @@ const WrStatusPage: React.FC = () => { }); svg.appendChild(g); - }; + }, [handleMarkerClick, regionIdMap]); useEffect(() => { const loadSVG = async () => { @@ -374,7 +374,7 @@ const WrStatusPage: React.FC = () => { }; loadSVG(); - }, []); + }, [createDonutMarker, regionData]); useEffect(() => { const svg = svgContainerRef.current?.querySelector('svg'); @@ -493,10 +493,10 @@ const WrStatusPage: React.FC = () => { 번호 시도 - 하수(이용량,천m³/년) - 빗물(이용량,천m³/년) - 중수도(이용량,천m³/년) - 온배수(이용량,천m³/년) + 하수(이용량,천𝓂³/년) + 빗물(이용량,천𝓂³/년) + 중수도(이용량,천𝓂³/년) + 온배수(이용량,천𝓂³/년) diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index 908f2f09c..dc4e404ce 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -1,42 +1,46 @@ "use client"; import Gis from "@common/business_components/ui/map/Gis"; -import { useEffect, useRef, useState, useCallback } from "react"; +import { useEffect, useRef, useState } from "react"; import { mapService } from "../service"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import { SearchBar } from "@/components/ui/map/searchBar/searchBar"; -import { useDmStore } from "@/app/store"; +import SelectedFcltList from "./selectedFcltList"; +import type { SubsInfo } from "@/app/store/map/type"; -// export type SubsInfo={ -// ruseUtztnFcltNm: string; // 재이용시설명 -// ruseUtztnFcltCd: string; // 재이용시설 코드 -// } const Map = () => { - - //수요처 저장하는 함수 setSubsInfo - //setSubsInfo(subsInfo:SubsInfo[]):void - const { map,setSubsInfo } = useDmStore( - (state) => state - ); - //저장된 수요처 리스트 - const { subsInfo } = map; - const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const gisMaps = useRef(null); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 - const [checkedLayers, setCheckedLayers] = useState([]); - const [checkedLayersData, setCheckedLayersData] = useState([]); - const [selectDataPk, setSelectDataPk] = useState(""); - const [subs, setSubs] = useState([]); - - //적용 버튼 클릭 이벤트 + const [selectDataPk, setSelectDataPk] = useState>({}); + const [selectedFclts, setSelectedFclts] = useState([]); + + //적용 버튼 클릭 이벤트 const onApply = () => { - //setSubs([{'41110PB001R1':'수원제1,2 재이용1'},{'41110PB001R2':'수원제1,2 재이용2'}]); - console.log(subs) + if (selectedFclts.length > 3) { + alert("재이용시설 후보지는 최대 3개까지만 선택할 수 있습니다."); + return; + } + + const subsInfoList: SubsInfo[] = selectedFclts.map((fclt) => ({ + ruseUtztnFcltNm: fclt.ruseUtztnFcltNm, + ruseUtztnFcltCd: fclt.pbnstRuseFcltId, + })); + + window.opener.postMessage( + { + type: "SET_SUBS_INFO", + payload: subsInfoList, // 또는 필요한 데이터 + }, + "*" + ); + + window.close(); }; + //트리 레이어 데이터 조회 const fetchData = async () => { try { const response = await mapService.getLyrStngs(); @@ -46,76 +50,167 @@ const Map = () => { } }; - // 팝업 닫기 핸들러를 useCallback으로 메모이제이션 - const handlePopupClose = useCallback(() => { - setIsOpen(false); - setFeatureData(null); - }, []); + //하수처리시설코드로 재이용시설 목록 조회 + const reuseData = async (ruseFcltPk: any) => { + try { + const response = await mapService.getPbnstRuseFclts(ruseFcltPk); + const data = response.data || []; + setSelectedFclts((prev) => { + const newItems = data.filter( + (item: any) => + item && + Object.keys(item).length > 0 && + !prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId) + ); + console.log(newItems) + return [...prev, ...newItems]; + }); + + } catch (error) { + console.error("데이터 불러오기 실패:", error); + } + }; // 컴포넌트 마운트 시 1회 실행 useEffect(() => { fetchData(); }, []); - useEffect(() => { - console.log("Selected Data Pk: ", selectDataPk); - }, [selectDataPk]); + // 레이어 생성 후 초기화 처리 + const onCheckedLayersChange = () => { + //수요처 ID + const urlParams = new URLSearchParams(window.location.search); + const dmdInfoId = urlParams.get("dmdInfoId"); - // 체크된 레이어 정보를 받는 핸들러 - const handleCheckedLayersChange = ( - checkedIds: string[], - checkedNodesData: any[] - ) => { - setCheckedLayers(checkedIds); - setCheckedLayersData(checkedNodesData); - }; - - // SearchModal, GisContent 등에서 호출 - const onFeatureSelect = (data: any) => { - const map = gisMaps.current; - if (!map) { - console.warn("지도가 초기화되지 않았습니다."); - return; - } - - // 항상 같은 select 객체 사용 - const select = GisApp.Module.select as any; - if (!select) return; - - // 1. 기존 선택 초기화 - select.resetSelectedFeature(); - - // 2. data가 OpenLayers Feature 인스턴스인지 확인 + //수요처 위치로 화면 이동 + const maps = gisMaps.current; + const layer = GisApp.Module.layer as any; + const layerNm = "tb_wr_ruse_fclt_dmd_co"; + const featureId = `${layerNm}.${dmdInfoId}`; let foundFeature: any = null; - if ( - data && - typeof data.getGeometry === "function" && - typeof data.setStyle === "function" - ) { - // 이미 Feature 인스턴스라면 그대로 사용 - foundFeature = data; - } else { - // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - const featureId = data.fid || data.ftrIdn || data.id; - map - .getLayers() - .getArray() - .forEach((layer: any) => { + + maps + .getLayers() + .getArray() + .forEach((layer: any) => { + if (layer.get("lyrTyNm") == layerNm) { const source = layer.getSource?.(); if (source?.getFeatureById) { const feature = source.getFeatureById(String(featureId).trim()); - if (feature) foundFeature = feature; + if (feature) { + foundFeature = feature; + return; + } } - }); + } + }); + + if (foundFeature) { + const geometry = foundFeature.getGeometry(); + + if (geometry) { + const extent = geometry.getExtent(); + + if (geometry.getType() === "Point") { + maps.getView().setCenter(geometry.getCoordinates()); + maps.getView().setZoom(11); + } else { + maps.getView().fit(extent, { + duration: 300, + padding: [30, 30, 30, 30], + maxZoom: 16, + }); + } + } + + layer.highLightFeature( + [foundFeature], + { + id: GisApp.hlLayerId, + zIndex: 10, + opacity: 1, + selectable: true, + }, + { width: 30 } + ); } - // 3. 피처 선택 + const formatNumber = (val: any) => { + const num = Number(val); + return isNaN(num) ? "-" : num.toLocaleString(); + }; + + // 재이용시설 오버레이 + maps + .getLayers() + .getArray() + .forEach((layer: any) => { + if (layer.get("lyrTyNm") === "tb_wr_bdst_ruse_fclt") { + const source = layer.getSource?.(); + if (source?.getFeatures) { + const features = source.getFeatures(); + + features.forEach((feature: any) => { + // 필요한 고유 ID 또는 속성 가져오기 (예: ftrIdn 또는 feature.getId()) + const featureId = + feature.getId() || feature.get("ftrIdn") || Math.random(); + + // HTML 또는 React 오버레이 컴포넌트 구성 + const overlayContent = ` +
+ +
+ ${feature.get("swtrmt_fclt_nm") || "-"} +
+ + + + + + + + + +
용량(m³/일)${formatNumber(feature.get("fclt_cpct"))}
+
+ `; + feature.set("html", overlayContent); + feature.set("overLayId", `overLay.${featureId}`); + }); + } + } + }); + }; + + // 검색해서 재이용시설 선택하면 해당 시설로 화면 이동 처리 + useEffect(() => { + if (!selectDataPk || Object.keys(selectDataPk).length === 0) return; + const maps = gisMaps.current; + let foundFeature: any = null; + const layerNm = "tb_wr_bdst_ruse_fclt"; + const featureId = `${layerNm}.${selectDataPk.bscFctCd}`; + maps + .getLayers() + .getArray() + .forEach((layer: any) => { + if (layer.get("lyrTyNm") == layerNm) { + const source = layer.getSource?.(); + if (source?.getFeatureById) { + const feature = source.getFeatureById(String(featureId).trim()); + if (feature) { + foundFeature = feature; + return; + } + } + } + }); + if (foundFeature) { const geometry = foundFeature.getGeometry(); if (geometry) { const extent = geometry.getExtent(); - map.getView().fit(extent, { + maps.getView().fit(extent, { duration: 300, padding: [50, 50, 50, 50], maxZoom: 16, @@ -123,21 +218,23 @@ const Map = () => { nearest: true, }); } - select.selectFeature(foundFeature, true); } else { - console.warn("OpenLayers Feature를 찾을 수 없습니다:", data); + console.warn("OpenLayers Feature를 찾을 수 없습니다:", selectDataPk); } + }, [selectDataPk]); - setFeatureData(data); - setIsOpen(true); - setIsSearchModalOpen(false); + // 재이용시설 클릭 시 선택 시설목록에 추가 + const onFeatureSelect = (data: any) => { + // 처리시설코드 + const swtrmtFcltCd = data.get("swtrmt_fclt_cd"); + reuseData(swtrmtFcltCd); }; return ( <> { - if (hasuPk !== "") { + setHasuPk={(hasuPk: any) => { + if (hasuPk) { setSelectDataPk(hasuPk); } }} @@ -150,7 +247,7 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - detailView={null} + detailView={undefined} onFeatureSelect={onFeatureSelect} setIsOpen={setIsOpen} setFeatureData={setFeatureData} @@ -158,11 +255,14 @@ const Map = () => { setIsSearchModalOpen={setIsSearchModalOpen} isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} - onCheckedLayersChange={handleCheckedLayersChange} + onCheckedLayersChange={onCheckedLayersChange} imgVisble={false} - setData={setSubs} /> + setSelectedFclts(updatedList)} + /> ); }; diff --git a/apps/dm/app/(popup-layout)/map/popup/sdmInfoMap.tsx b/apps/dm/app/(popup-layout)/map/popup/sdmInfoMap.tsx deleted file mode 100644 index 84dc49119..000000000 --- a/apps/dm/app/(popup-layout)/map/popup/sdmInfoMap.tsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client"; - -import Gis from "@common/business_components/ui/map/Gis"; -import { useEffect, useRef, useState, useCallback } from "react"; -import { mapService } from "../service"; -import GisApp from "@common/business_components/ui/map/lib/gis.core"; -import { SearchBar } from "@/components/ui/map/searchBar/searchBar"; - -const Map = () => { - const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 - const gisMaps = useRef(null); - const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 - const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 - const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 - const [checkedLayers, setCheckedLayers] = useState([]); - const [checkedLayersData, setCheckedLayersData] = useState([]); - const [selectDataPk, setSelectDataPk] = useState(""); - - const fetchData = async () => { - try { - const response = await mapService.getLyrStngs(); - setTreeData(response); // API 응답 저장 - } catch (error) { - console.error("데이터 불러오기 실패:", error); - } - }; - - // 팝업 닫기 핸들러를 useCallback으로 메모이제이션 - const handlePopupClose = useCallback(() => { - setIsOpen(false); - setFeatureData(null); - }, []); - - // 컴포넌트 마운트 시 1회 실행 - useEffect(() => { - fetchData(); - }, []); - - useEffect(() => { - console.log("Selected Data Pk: ", selectDataPk); - }, [selectDataPk]); - - // 체크된 레이어 정보를 받는 핸들러 - const handleCheckedLayersChange = ( - checkedIds: string[], - checkedNodesData: any[] - ) => { - setCheckedLayers(checkedIds); - setCheckedLayersData(checkedNodesData); - }; - - // SearchModal, GisContent 등에서 호출 - const onFeatureSelect = (data: any) => { - const map = gisMaps.current; - if (!map) { - console.warn("지도가 초기화되지 않았습니다."); - return; - } - - // 항상 같은 select 객체 사용 - const select = GisApp.Module.select as any; - if (!select) return; - - // 1. 기존 선택 초기화 - select.resetSelectedFeature(); - - // 2. data가 OpenLayers Feature 인스턴스인지 확인 - let foundFeature: any = null; - if ( - data && - typeof data.getGeometry === "function" && - typeof data.setStyle === "function" - ) { - // 이미 Feature 인스턴스라면 그대로 사용 - foundFeature = data; - } else { - // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - const featureId = data.fid || data.ftrIdn || data.id; - map - .getLayers() - .getArray() - .forEach((layer: any) => { - const source = layer.getSource?.(); - if (source?.getFeatureById) { - const feature = source.getFeatureById(String(featureId).trim()); - if (feature) foundFeature = feature; - } - }); - } - - // 3. 피처 선택 - if (foundFeature) { - const geometry = foundFeature.getGeometry(); - if (geometry) { - const extent = geometry.getExtent(); - - map.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); - } - - setFeatureData(data); - setIsOpen(true); - setIsSearchModalOpen(false); - }; - - return ( - <> - { - if (hasuPk !== "") { - setSelectDataPk(hasuPk); - } - }} - /> -
- -
- - ); -}; - -export default Map; diff --git a/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx b/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx new file mode 100644 index 000000000..bdf4d7e2c --- /dev/null +++ b/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx @@ -0,0 +1,81 @@ +import React from "react"; + +type FcltItem = { + pbnstRuseFcltId: string; + ruseUtztnFcltNm: string; + splyExtraCpct?: number; + ruseCpct?: number; + [key: string]: any; +}; + +export default function SelectedFcltList({ + data = [], + onRemove, +}: { + data: FcltItem[]; + onRemove: (updatedList: FcltItem[]) => void; +}) { + const handleDelete = (code: string) => { + const updatedList = data.filter((item) => item.pbnstRuseFcltId !== code); + onRemove(updatedList); + }; + + const TableContent = ( + <> + + + 시설명 + + 하수처리량 +
+ (㎥/일) + + + 재이용량 +
+ (㎥/일) + + 삭제 + + + + {data.map((item) => ( + + {item.ruseUtztnFcltNm} + + {Number(item.splyExtraCpct || 0).toLocaleString()} + + + {Number(item.ruseCpct || 0).toLocaleString()} + + + + + + ))} + + + ); + + if (data.length === 0) return null; + + return ( +
+

+ 선택된 시설 +

+
+ + {TableContent} +
+
+
+ ); +} diff --git a/apps/dm/app/(popup-layout)/map/popup/windowPotal.tsx b/apps/dm/app/(popup-layout)/map/popup/windowPotal.tsx deleted file mode 100644 index 38b8fc7db..000000000 --- a/apps/dm/app/(popup-layout)/map/popup/windowPotal.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { createPortal } from 'react-dom'; - -// 현재 문서의 스타일(CSS)을 새 창으로 복사하는 함수 -const copyStyles = (sourceDoc: Document, targetDoc: Document) => { - Array.from(sourceDoc.styleSheets).forEach(styleSheet => { - try { - if (styleSheet.cssRules) { //