diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 229e3f629..48868313b 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,518 +1,364 @@ -import { Search } from 'lucide-react'; -import OlMap from 'ol/Map'; -import Overlay from 'ol/Overlay'; -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 { createRoot } from 'react-dom/client'; -import GisLonLat from './GisLonLat'; -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 FacilityInfoPopup from './FacilityInfoPopup'; +import { Search } from "lucide-react"; +import OlMap from "ol/Map"; +import View from "ol/View"; +import { defaults as defaultControls, OverviewMap } from "ol/control"; +import { defaults } from "ol/interaction"; +import * as olProj from "ol/proj"; +import { register } from "ol/proj/proj4"; +import proj4 from "proj4"; +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import GisMapToggle from "./GisMapToggle"; +import GisSelect from "./GisSelect"; +import GisToolBar from "./GisToolBar"; +import GisTree from "./GisTree"; +import "./lib/css/gis.common.css"; +import GisApp from "./lib/gis.core"; -export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); +export const GisContext = createContext({ + gisCore: null, + gisMap: null, + treeData: null, + checkedLayerIds: null, +}); export const useGisContext = () => useContext(GisContext); export const searchUpperPpls = async (fcltMngNo) => { - const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ''; + const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; + const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { + method: "GET", + }); - const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { - method: "GET", - }); + let data = null; + if (res.ok) { + try { + // 응답이 비어있는지 확인 + const contentLength = res.headers.get("content-length"); + if (contentLength === "0") + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - let data = null; - if (res.ok) { - try { - // 응답이 비어있는지 확인 - const contentLength = res.headers.get('content-length'); - if (contentLength === '0') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 + const text = await res.text(); + if (!text || text.trim() === "") + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 - const text = await res.text(); - if (!text || text.trim() === '') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + // JSON 파싱 시도 + data = JSON.parse(text); + } catch (error) { + console.error("JSON parsing error:", error); + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + } + } - // JSON 파싱 시도 - data = JSON.parse(text); - } catch (error) { - console.error('JSON parsing error:', error); - alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - } - } + const select = GisApp.Module.select; + const gisMap = GisApp.Module.core?.map; + let foundFeature = null; - const select = GisApp.Module.select; + data.forEach((item) => { + const pplMngNo = item.ppl_mng_no.trim(); - const gisMap = GisApp.Module.core?.map; + gisMap + .getLayers() + .getArray() + .filter((e) => e.get("lyrTyNm") == "tb_ms_swage_ppl") + .forEach((layer) => { + const source = layer.getSource?.(); + if (source?.getFeatures) { + const features = source.getFeatures(); + for (const f of features) { + if (f.values_.fclt_mng_no.trim() === pplMngNo) { + foundFeature = f; + break; + } + } + } + }); - let foundFeature = null; + if (foundFeature) select.selectMultiFeature(foundFeature, true); + }); - data.forEach(item => { - const pplMngNo = item.ppl_mng_no.trim(); + if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - gisMap.getLayers().getArray().filter(e => e.get("lyrTyNm") == 'tb_ms_swage_ppl').forEach(layer => { - const source = layer.getSource?.(); - if (source?.getFeatures) { - const features = source.getFeatures(); - for (const f of features) { - if (f.values_.fclt_mng_no.trim() === pplMngNo) { - foundFeature = f; - break; - } - } - } - }); + let geometry = foundFeature?.getGeometry(); + if (geometry) { + let extent = geometry.getExtent(); - if (foundFeature) select.selectMultiFeature(foundFeature, true); - }); - - if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - - let geometry = foundFeature?.getGeometry(); - if (geometry) { - let extent = geometry.getExtent(); - - gisMap.getView().fit(extent, { - duration: 300, - padding: [50, 50, 50, 50], - maxZoom: 14, - constrainResolution: true, - nearest: true, - }); - } + gisMap.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 14, + constrainResolution: true, + nearest: true, + }); + } }; - function Gis({ - isOpen, - gisMaps, - isSearchModalOpen, - treeData = [], - isSearch = false, - isMiniMap = false, - setIsOpen, - setFeatureData, - featureData, - imgVisble = false, - searchView: SearchView, - detailView: DetailView, - onFeatureSelect, - setIsSearchModalOpen, - onCheckedLayersChange + isOpen, //우측 상세 레이어 팝업 + gisMaps, + isSearchModalOpen, + treeData = [], + isSearch = false, + isMiniMap = false, + setIsOpen, + setFeatureData, + featureData, + imgVisble = false, + searchView: SearchView, + detailView: DetailView, + onFeatureSelect, + setIsSearchModalOpen, + onCheckedLayersChange, + searchBar: SearchBar, + waterDepth: WaterDepth, + height: height = "h-[calc(100vh-10rem)]", }) { - const mapRef = useRef(); - const [mapObject, setMapObject] = useState({}); - const [tabVisible, setTabVisible] = useState(true); - const [layerNm, setLayerNm] = useState('') - // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 - // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 - // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 - const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); - const mapInstanceRef = useRef(null); - const checkedLayerIds = useRef([]); - const layerNmRef = useRef('') + const mapRef = useRef(); + const [mapObject, setMapObject] = useState({}); + const [tabVisible, setTabVisible] = useState(true); + const [layerNm, setLayerNm] = useState(""); + // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 + // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 + const gisCore = useMemo( + () => new GisApp.core({ targetId: mapRef.current }), + [], + ); + const mapInstanceRef = useRef(null); + const checkedLayerIds = useRef([]); + const layerNmRef = useRef(""); - // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 - // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); - // const facilityOverlaysRef = useRef([]); - // 체크된 레이어 상태 관리 - const [checkedLayers, setCheckedLayers] = useState([]); - const [checkedLayersData, setCheckedLayersData] = useState([]); + // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 + // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); + // const facilityOverlaysRef = useRef([]); + // 체크된 레이어 상태 관리 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); - let gisMap = null; - const closePopup = () => { - setIsOpen(false); // 팝업 닫기 - setFeatureData(null) - }; + let gisMap = null; + const closePopup = () => { + setIsOpen(false); // 팝업 닫기 + setFeatureData(null); + }; - // Feature Click Event - const handleFeatureSelect = useCallback((data) => { - gisMap = mapInstanceRef.current; + // Feature Click Event + const handleFeatureSelect = useCallback( + (data) => { + onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 + }, + [mapInstanceRef], + ); - if (!gisMap) { - console.warn('지도가 초기화되지 않았습니다.'); - return; - } + // 지도 생성 및 초기화 + useEffect(() => { + if (!mapRef.current) return; + if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 - // 항상 같은 select 객체 사용 - const select = GisApp.Module.select; - if (!select) return; + // EPSG:5179 등록 + proj4.defs([ + [ + "EPSG:5179", + "+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + ], + ]); + register(proj4); - // 1. 기존 선택 초기화 - select.resetSelectedFeature(); + // OpenLayers 지도 객체 생성 + const map = new OlMap({ + layers: [gisCore.baseLayers["base"], gisCore.baseLayers["satellite"]], + renderer: "webgl", + target: mapRef.current, + interactions: defaults({ doubleClickZoom: false }), + controls: defaultControls({ + zoom: false, + rotate: false, + attributionOptions: { + collapsible: false, + }, + }).extend([]), + view: new View({ + projection: GisApp.TO_PROJ, + zoom: gisCore.defaults.zoom, + center: olProj.transform( + [127.4875, 37.4916], + GisApp.FROM_PROJ, + GisApp.TO_PROJ, + ), + minZoom: gisCore.defaults.minZoom, + maxZoom: gisCore.defaults.maxZoom, + }), + }); - // 2. data가 OpenLayers Feature 인스턴스인지 확인 - let foundFeature = null; - if (data && typeof data.getGeometry === 'function' && typeof data.setStyle === 'function') { - // 이미 Feature 인스턴스라면 그대로 사용 - foundFeature = data; - } else { - // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - let featureId = data.fid; - const layerName = layerNmRef.current - if (layerName === '하수재이용시설') { - featureId = data.searchPk; - } else if (layerName === '빗물이용시설' || layerName == '중수도시설') { - featureId = data.bdstInfoSn; - } else if (layerName === '하수처리시설') { - featureId = data.bscFctCd; - } else if (layerName === '온배수시설') { - featureId = data.bdstRuseFcltSn; - } else if(layerName == '수요처'){ - featureId = data.dmdInfoId.replaceAll('-','') - } - else { - console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm); - featureId = data.fid || data.id || data.ftrIdn; - } - // gisMap.getLayers().getArray().forEach(layer => { - // const source = layer.getSource?.(); - // if (source?.getFeatureById) { - // const feature = source.getFeatureById(String(featureId).trim()); - // if (feature) foundFeature = feature; - // } - // }); - // const [layerType, id] = String(featureId).split('.'); - gisMap.getLayers().getArray().forEach(layer => { - const source = layer.getSource?.(); - if (source?.getFeatures) { - const allFeatures = source.getFeatures(); - for (const f of allFeatures) { - const id = String(f.getId()); - // console.log('id', id, featureId) - if (id && id.split('.')[1] === String(featureId)) { - foundFeature = f; - break; - } - } - } - }); - } + gisMaps.current = map; - // 3. 피처 선택 - if (foundFeature) { - let geometry = foundFeature.getGeometry(); - if (geometry) { - let extent = geometry.getExtent(); + // 미니맵 추가 + if (isMiniMap) { + const overviewMap = new OverviewMap({ + layers: [GisApp.Module.core.baseMiniMapLayers["base"]], + view: new View({ + projection: GisApp.TO_PROJ, + minZoom: 9, + }), + collapsed: false, + collapseLabel: "\u00BB", + label: "\u00AB", + }); + map.addControl(overviewMap); + } - gisMap.getView().fit(extent, { - duration: 300, - padding: [50, 50, 50, 50], - maxZoom: 16, - constrainResolution: true, - nearest: true, - }); - } - select.selectFeature(foundFeature, true); - } else { - console.warn('OpenLayers Feature를 찾을 수 없습니다:', data); - } + // 스케일 컨트롤 및 베이스 레이어 설정 + gisCore.__proto__.map = map; + gisCore.scaleControl(); + gisCore.changeBaseLayer("base"); + setMapObject({ map: map }); + mapInstanceRef.current = map; // 생성 완료 플래그 설정 + }, []); - setFeatureData(data); - setIsOpen(true); - setIsSearchModalOpen(false); - onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 - }, [mapInstanceRef]); + // treeData 변경 시 레이어 코드 생성 + useEffect(() => { + if (!treeData || treeData.length === 0) return; - // 지도 생성 및 초기화 - useEffect(() => { - if (!mapRef.current) return; - if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 + // 기존 오버레이 및 레이어 모두 삭제 + gisCore.deleteAllOverlay(); + gisCore.deleteAllLayer(); - // 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); + // 트리 데이터를 레이어 코드 객체로 변환 + const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn"); + GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 - // 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, - }), - }); + //setTreeData(data); + // 편집 가능한 레이어 목록 생성 + //const layerItems = getLayerItems(layerCode); + //setEditLayerItems(prev => [...layerItems]) + }, [treeData]); - gisMaps.current = map + 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 (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 (mapObject.map) { + try { + gisMap = mapObject.map; + } catch (e) { + console.error("오류 발생:", e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 + } + } - // 스케일 컨트롤 및 베이스 레이어 설정 - gisCore.__proto__.map = map; - gisCore.scaleControl(); - gisCore.changeBaseLayer('base'); - setMapObject({ map: map }); - mapInstanceRef.current = map; // 생성 완료 플래그 설정 - }, []); + // 체크된 레이어 정보를 처리하는 함수 + const handleCheckedLayersChange = useCallback( + (checkedIds, checkedNodesData) => { + setTimeout(() => { + setCheckedLayers(checkedIds); + setCheckedLayersData(checkedNodesData); + onCheckedLayersChange?.(checkedIds, checkedNodesData); + }, 1000); + }, + [onCheckedLayersChange], + ); - // treeData 변경 시 레이어 코드 생성 - useEffect(() => { - if (!treeData || treeData.length === 0) return; + return ( + <> + +
+
+ { + layerNmRef.current = layerNm; + setLayerNm(layerNm); + setIsOpen(true); + const dummyFeature = { + getProperties: () => ({}), // 최소 구조 + }; + onFeatureSelect(dummyFeature); + }} + imageVisible={imgVisble} + /> - // 기존 오버레이 및 레이어 모두 삭제 - gisCore.deleteAllOverlay(); - gisCore.deleteAllLayer(); + {typeof WaterDepth === "function" && ( +
+ setIsOpen(false)} + /> +
+ )} - // 트리 데이터를 레이어 코드 객체로 변환 - const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); - GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} + {isOpen && !isSearchModalOpen && ( + ({}) }} + seletectedLayerNm={layerNm} + onClose={closePopup} // 닫기 콜백 전달 + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* SearchView가 존재할 때만 렌더링 */} + {SearchView && ( + + )} + {/* 시도/시군구 조회 영역 */} + {SearchBar && ( +
+ +
+ )} + + + +
+
- //setTreeData(data); - // 편집 가능한 레이어 목록 생성 - const layerItems = getLayerItems(layerCode); - //setEditLayerItems(prev => [...layerItems]) - }, [treeData]); - - const getLayerItems = (layerCode) => { - const layerItems = Object.entries(layerCode) - .filter(([key, obj], idx) => obj?.updtPsblYn === 'Y') - .map(([key, obj]) => ({ - ...obj, - name: obj.lyrNm, - label: obj.lyrNm, - value: obj.lyrSn + '', - })) - .sort((a, b) => { - if (a.sortOrdr > b.sortOrdr) return 1; - if (a.sortOrdr < b.sortOrdr) return -1; - return 0; - }); - return layerItems; - }; - - if (mapObject.map) { - try { - gisMap = mapObject.map; - } catch (e) { - console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 - } - } - - // // 데이터 로드 및 팝업 생성 로직 - // useEffect(() => { - // if (!mapInstanceRef.current) return; // 맵이 생성되지 않았으면 실행 안 함 - - // // 1. 팝업 데이터 가져오기 (feature.js의 getData와 유사) - // const fetchPopupData = async () => { - // // 실제 API 호출 로직으로 대체해야 합니다. - // // 예시: const response = await yourApiService.getFcltyWtCnt(); - // // setFacilityInfoPopupData(response.data); - - // // 임시 목업 데이터 - // const mockData = [ - // { ftrIdn: 1, ftrCde: 'SA206', bplcNm: '처리장A', fcltyUseYn: 'Y', x: 127.005, y: 37.282, aCnt: 10, cCnt: 5, eCnt: 3, mCnt: 8, iCnt: 2 }, - // { ftrIdn: 2, ftrCde: 'SA112', bplcNm: '처리장B', fcltyUseYn: 'N', x: 127.010, y: 37.285, aCnt: 8, cCnt: 4, eCnt: 2, mCnt: 6, iCnt: 1 } - // ]; - // setFacilityInfoPopupData(mockData); - // }; - - // fetchPopupData(); - - // }, [mapInstanceRef.current]); // 맵 인스턴스가 생성된 후 한 번만 실행 - - // // 데이터가 변경되면 오버레이를 생성/업데이트하는 로직 - // useEffect(() => { - // const map = mapInstanceRef.current; - // if (!map || facilityInfoPopupData.length === 0) return; - - // // 기존 오버레이 정리 - // facilityOverlaysRef.current.forEach(overlay => map.removeOverlay(overlay)); - // facilityOverlaysRef.current = []; - - // facilityInfoPopupData.forEach(data => { - // const popupId = `${data.ftrIdn}_${data.ftrCde}`; - - // // 팝업을 담을 div 엘리먼트 생성 - // const popupElement = document.createElement('div'); - // popupElement.id = popupId; - - // // OpenLayers Overlay 생성 - // const position = olProj.transform([Number(data.x), Number(data.y)], GisApp.FROM_PROJ, GisApp.TO_PROJ); - // const overlay = new Overlay({ - // element: popupElement, - // position: position, - // offset: [-75, -210], // 팝업 너비의 절반, 높이만큼 오프셋 조정 - // positioning: 'bottom-center', - // className: 'facility-popup-overlay' // 필요시 공통 클래스 추가 - // }); - - // map.addOverlay(overlay); - // facilityOverlaysRef.current.push(overlay); - - // // React 컴포넌트를 popupElement에 렌더링 - // const root = createRoot(popupElement); - // root.render(); - // }); - - // // 초기 가시성 업데이트 - // updatePopupVisibility(); - - // }, [facilityInfoPopupData]); // 팝업 데이터가 변경될 때마다 실행 - - // // 레이어 가시성에 따라 팝업 가시성을 제어하는 함수 - // const updatePopupVisibility = useCallback(() => { - // const map = mapInstanceRef.current; - // if (!map) return; - - // const visibleLayers = new Set(); - // map.getLayers().forEach(layer => { - // if (layer.getVisible()) { - // // feature.js의 로직처럼 typeName이나 ftrCde를 사용해 필터링 - // const typeName = layer.get('typeName'); - // if (typeName === 'VW_WTL_FCLTY_AS') { // 예시 레이어 이름 - // visibleLayers.add(layer.get('ftrCde')); - // } - // } - // }); - - // facilityOverlaysRef.current.forEach(overlay => { - // const element = overlay.getElement(); - // const ftrCde = element.querySelector('table')?.classList[0]; // FacilityPopup의 클래스에서 ftrCde 가져오기 - - // if (visibleLayers.has(ftrCde)) { - // element.style.display = ''; - // } else { - // element.style.display = 'none'; - // } - // }); - // }, []); - - // GisTree에서 체크 상태가 변경될 때 이 함수를 호출하도록 연결해야 합니다. - // 예시: GisTree 컴포넌트에 onCheck 콜백을 추가하고, 그 콜백에서 이 함수를 호출 - // useEffect(() => { - // // 레이어 가시성이 변경될 때마다 팝업 가시성 업데이트 - // const map = mapInstanceRef.current; - // if (!map) return; - - // const handleLayerVisibilityChange = () => { - // updatePopupVisibility(); - // }; - - // map.getLayers().on('change:length', handleLayerVisibilityChange); - // map.getLayers().forEach(layer => { - // layer.on('change:visible', handleLayerVisibilityChange); - // }); - - // return () => { - // // 클린업 - // map.getLayers().un('change:length', handleLayerVisibilityChange); - // map.getLayers().forEach(layer => { - // layer.un('change:visible', handleLayerVisibilityChange); - // }); - // }; - // }, [updatePopupVisibility]); - - // 체크된 레이어 정보를 처리하는 함수 - const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => { - setTimeout(() => { - setCheckedLayers(checkedIds); - setCheckedLayersData(checkedNodesData); - onCheckedLayersChange?.(checkedIds, checkedNodesData); - }, 0); - }, [onCheckedLayersChange]); - - return ( - <> - -
-
- { - layerNmRef.current = nodeNm - setLayerNm(nodeNm); setIsOpen(true); const dummyFeature = { - getProperties: () => ({}), // 최소 구조 - }; - onFeatureSelect(dummyFeature); - }} - imageVisible={imgVisble} - /> - - {/* */} - - {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} - {isOpen && !isSearchModalOpen && ( - ({}) }} - seletectedLayerNm={layerNm} - onClose={closePopup} // 닫기 콜백 전달 - onFeatureSelect={handleFeatureSelect} - /> - )} - {/* SearchView가 존재할 때만 렌더링 */} - {SearchView && ( - - )} - {/* */} - - - -
- - {isSearch && ( -
- -
- )} - - - ); + {isSearch && ( +
+ +
+ )} + + + ); } export default React.memo(Gis);