From 6bdef285324fa98f74d99b88fce2f8788b993728 Mon Sep 17 00:00:00 2001 From: yschoi Date: Fri, 25 Jul 2025 10:03:28 +0900 Subject: [PATCH] =?UTF-8?q?=ED=9B=84=EB=B3=B4=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=A7=80=EB=8F=84=20=ED=8C=9D=EC=97=85=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm/.env.local | 2 + apps/dm/app/(base-layout)/layout.tsx | 20 +++ .../pages/sdmMng/sdmApplyList/page.tsx | 0 .../sdmInfoInsert/modalSimilar/page.tsx | 0 .../pages/sdmMng/sdmInfoInsert/page.tsx | 0 .../pages/sdmMng/sdmInsert/page.tsx | 0 .../pages/sdmMng/sdmLogin/page.tsx | 0 .../pages/wrAbout/page.tsx | 0 .../pages/wrExample/page.tsx | 0 .../pages/wrOverview/page.tsx | 0 .../pages/wrStatus/page.tsx | 0 apps/dm/app/(popup-layout)/layout.tsx | 19 +++ .../map/mainMap/page.tsx | 0 apps/dm/app/(popup-layout)/map/popup/page.tsx | 147 ++++++++++++++++++ .../map/popup/sdmInfoMap.tsx | 51 +++--- .../map/popup/windowPotal.tsx | 2 +- .../map/service/index.ts | 0 .../map/service/map.ts | 0 apps/dm/app/layout.tsx | 4 +- apps/dm/app/middleware.ts | 130 ++++++++++++++++ apps/dm/app/page.tsx | 3 - .../ui/sdmInfoInsertForm/component.tsx | 31 ++-- .../business_components/src/ui/map/Gis.js | 117 +------------- 23 files changed, 367 insertions(+), 159 deletions(-) create mode 100644 apps/dm/app/(base-layout)/layout.tsx rename apps/dm/app/{ => (base-layout)}/pages/sdmMng/sdmApplyList/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/sdmMng/sdmInfoInsert/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/sdmMng/sdmInsert/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/sdmMng/sdmLogin/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/wrAbout/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/wrExample/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/wrOverview/page.tsx (100%) rename apps/dm/app/{ => (base-layout)}/pages/wrStatus/page.tsx (100%) create mode 100644 apps/dm/app/(popup-layout)/layout.tsx rename apps/dm/app/{pages => (popup-layout)}/map/mainMap/page.tsx (100%) create mode 100644 apps/dm/app/(popup-layout)/map/popup/page.tsx rename apps/dm/app/{pages => (popup-layout)}/map/popup/sdmInfoMap.tsx (77%) rename apps/dm/app/{pages => (popup-layout)}/map/popup/windowPotal.tsx (98%) rename apps/dm/app/{pages => (popup-layout)}/map/service/index.ts (100%) rename apps/dm/app/{pages => (popup-layout)}/map/service/map.ts (100%) create mode 100644 apps/dm/app/middleware.ts diff --git a/apps/dm/.env.local b/apps/dm/.env.local index 106493928..a561c3904 100644 --- a/apps/dm/.env.local +++ b/apps/dm/.env.local @@ -8,6 +8,8 @@ NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035 NEXT_PUBLIC_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_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 # 최용성 diff --git a/apps/dm/app/(base-layout)/layout.tsx b/apps/dm/app/(base-layout)/layout.tsx new file mode 100644 index 000000000..56260331f --- /dev/null +++ b/apps/dm/app/(base-layout)/layout.tsx @@ -0,0 +1,20 @@ +"use client" +import Base from "../base"; +import "../globals.css"; +import { StoreProvider } from "../store"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + ); +} \ No newline at end of file diff --git a/apps/dm/app/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx similarity index 100% rename from apps/dm/app/pages/sdmMng/sdmApplyList/page.tsx rename to apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx diff --git a/apps/dm/app/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx similarity index 100% rename from apps/dm/app/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx rename to apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx diff --git a/apps/dm/app/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx similarity index 100% rename from apps/dm/app/pages/sdmMng/sdmInfoInsert/page.tsx rename to apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx diff --git a/apps/dm/app/pages/sdmMng/sdmInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx similarity index 100% rename from apps/dm/app/pages/sdmMng/sdmInsert/page.tsx rename to apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx diff --git a/apps/dm/app/pages/sdmMng/sdmLogin/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogin/page.tsx similarity index 100% rename from apps/dm/app/pages/sdmMng/sdmLogin/page.tsx rename to apps/dm/app/(base-layout)/pages/sdmMng/sdmLogin/page.tsx diff --git a/apps/dm/app/pages/wrAbout/page.tsx b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx similarity index 100% rename from apps/dm/app/pages/wrAbout/page.tsx rename to apps/dm/app/(base-layout)/pages/wrAbout/page.tsx diff --git a/apps/dm/app/pages/wrExample/page.tsx b/apps/dm/app/(base-layout)/pages/wrExample/page.tsx similarity index 100% rename from apps/dm/app/pages/wrExample/page.tsx rename to apps/dm/app/(base-layout)/pages/wrExample/page.tsx diff --git a/apps/dm/app/pages/wrOverview/page.tsx b/apps/dm/app/(base-layout)/pages/wrOverview/page.tsx similarity index 100% rename from apps/dm/app/pages/wrOverview/page.tsx rename to apps/dm/app/(base-layout)/pages/wrOverview/page.tsx diff --git a/apps/dm/app/pages/wrStatus/page.tsx b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx similarity index 100% rename from apps/dm/app/pages/wrStatus/page.tsx rename to apps/dm/app/(base-layout)/pages/wrStatus/page.tsx diff --git a/apps/dm/app/(popup-layout)/layout.tsx b/apps/dm/app/(popup-layout)/layout.tsx new file mode 100644 index 000000000..60d925f3e --- /dev/null +++ b/apps/dm/app/(popup-layout)/layout.tsx @@ -0,0 +1,19 @@ +"use client" +import "../globals.css"; +import { StoreProvider } from "../store"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + ); +} \ No newline at end of file diff --git a/apps/dm/app/pages/map/mainMap/page.tsx b/apps/dm/app/(popup-layout)/map/mainMap/page.tsx similarity index 100% rename from apps/dm/app/pages/map/mainMap/page.tsx rename to apps/dm/app/(popup-layout)/map/mainMap/page.tsx diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx new file mode 100644 index 000000000..84dc49119 --- /dev/null +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -0,0 +1,147 @@ +"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/pages/map/popup/sdmInfoMap.tsx b/apps/dm/app/(popup-layout)/map/popup/sdmInfoMap.tsx similarity index 77% rename from apps/dm/app/pages/map/popup/sdmInfoMap.tsx rename to apps/dm/app/(popup-layout)/map/popup/sdmInfoMap.tsx index 21b76f943..84dc49119 100644 --- a/apps/dm/app/pages/map/popup/sdmInfoMap.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/sdmInfoMap.tsx @@ -4,6 +4,7 @@ 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로 설정 @@ -13,6 +14,7 @@ const Map = () => { const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 const [checkedLayers, setCheckedLayers] = useState([]); const [checkedLayersData, setCheckedLayersData] = useState([]); + const [selectDataPk, setSelectDataPk] = useState(""); const fetchData = async () => { try { @@ -34,6 +36,10 @@ const Map = () => { fetchData(); }, []); + useEffect(() => { + console.log("Selected Data Pk: ", selectDataPk); + }, [selectDataPk]); + // 체크된 레이어 정보를 받는 핸들러 const handleCheckedLayersChange = ( checkedIds: string[], @@ -107,25 +113,34 @@ const Map = () => { }; return ( -
- + { + if (hasuPk !== "") { + setSelectDataPk(hasuPk); + } + }} /> -
+
+ +
+ ); }; diff --git a/apps/dm/app/pages/map/popup/windowPotal.tsx b/apps/dm/app/(popup-layout)/map/popup/windowPotal.tsx similarity index 98% rename from apps/dm/app/pages/map/popup/windowPotal.tsx rename to apps/dm/app/(popup-layout)/map/popup/windowPotal.tsx index e601e7c9c..38b8fc7db 100644 --- a/apps/dm/app/pages/map/popup/windowPotal.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/windowPotal.tsx @@ -85,7 +85,7 @@ const NewWindowPortal: React.FC = ({ children, onClose, ti alert('팝업이 차단되었습니다. 팝업 차단을 해제하고 다시 시도해주세요.'); onCloseRef.current(); } - }, [handleUnload, onClose, title]); + }, []); // `createPortal`을 사용해 children을 새 창의 container div에 렌더링합니다. return container ? createPortal(children, container) : null; diff --git a/apps/dm/app/pages/map/service/index.ts b/apps/dm/app/(popup-layout)/map/service/index.ts similarity index 100% rename from apps/dm/app/pages/map/service/index.ts rename to apps/dm/app/(popup-layout)/map/service/index.ts diff --git a/apps/dm/app/pages/map/service/map.ts b/apps/dm/app/(popup-layout)/map/service/map.ts similarity index 100% rename from apps/dm/app/pages/map/service/map.ts rename to apps/dm/app/(popup-layout)/map/service/map.ts diff --git a/apps/dm/app/layout.tsx b/apps/dm/app/layout.tsx index 7c0f47a30..119daee77 100644 --- a/apps/dm/app/layout.tsx +++ b/apps/dm/app/layout.tsx @@ -11,9 +11,7 @@ export default function RootLayout({ return ( - - {children} - + {children} ); diff --git a/apps/dm/app/middleware.ts b/apps/dm/app/middleware.ts new file mode 100644 index 000000000..6ddf29f9f --- /dev/null +++ b/apps/dm/app/middleware.ts @@ -0,0 +1,130 @@ +import { NextRequest, NextResponse } from "next/server"; +import path from "path"; + +const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id"; +const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || ""; +const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; +const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; +const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; +const DC_URL = process.env.NEXT_PUBLIC_DC_URL || ""; + +export function middleware(req: NextRequest) { + console.log("Middleware 진입:", req.nextUrl.pathname); + const pathname = req.nextUrl.pathname; + const searchParams = req.nextUrl.searchParams; + + if (pathname.startsWith("/server")) { + + const decodedParams = Array.from(searchParams.entries()) + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); + + const newUrl = `${SERVER_API_URL}${pathname.replace("/server", "")}${ + decodedParams ? `?${decodedParams}` : "" + }`; + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/geoserver")) { + let queryParams = searchParams.toString(); + const typename = searchParams.get("TYPENAME"); + + let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; + + if ( + typename == "hasudo:tb_il_swage_prcs_fclt" //하수처리시설 + || typename == "hasudo:tb_ms_swage_prcs_zone" //하수처리구역 + || typename == "hasudo:tb_ms_sbmrg_dngr_map" //침수위험지도 + || typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도 + ) { + newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; + } + else if ( + typename == "hasudo:tb_ms_swage_ppl" //관로 + || typename == "hasudo:tb_ms_swage_mhl" //맨홀 + || typename == "hasudo:tb_ms_swage_pmstn" //펌프장 + || typename == "hasudo:tb_ms_stofcbr" //우수토실 + ) { + newUrl = `${LX_GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; + } + else if ( + typename == "hasudo:om_dt_untrt_swg_rain" //중점우수토실 + || typename == "hasudo:om_wqlt_station" //관측소 + || typename == "hasudo:tb_wr_bdst_ruse_fclt" //재이용시설 + || typename == "hasudo:tb_wr_rnwtr_utztn_fclt" //빗물이용시설 + || typename == "hasudo:tb_wr_rswtr_fclt" //중수도시설 + || typename == "hasudo:tb_wr_htdfl_fclt" //온배수시설 + || typename == "hasudo:tb_wr_ruse_fclt_dmd_co" //수요처 + || typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처 + ){ + const url = req.nextUrl.clone(); + url.searchParams.set('TYPENAME', typename.replace("hasudo:","")); + queryParams = url.searchParams.toString(); + newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; + } + + console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if ( + pathname.startsWith("/attach-service") || + pathname.startsWith("/cms-service") + ) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + const newUrl = `${SERVER_API_URL}${pathname}`; + + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + console.log( + `[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`, + ); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } + + if (pathname.startsWith("/user-service")) { + const requestHeaders = new Headers(req.headers); + const newUrl = `${SERVER_API_URL}${pathname}`; + + console.log( + `[Middleware] Rewriting from '/user-service' path to: ${newUrl}`, + ); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } + + if (pathname.startsWith("/dc-service")) { + const requestHeaders = new Headers(req.headers); + const decodedParams = Array.from(searchParams.entries()) + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); + + const newUrl = `${DC_URL}${pathname}${decodedParams ? `?${decodedParams}` : ""}`; + console.log( + `[Middleware] Rewriting from '/dc-service' path to: ${newUrl}`, + ); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } + return NextResponse.next(); +} + +export const config = { + matcher: [ + "/server/:path*", + "/geoserver/:path*", + "/attach-service/:path*", + "/user-service/:path*", + "/cms-service/:path*", + "/dc-service/:path*", + ], + +}; diff --git a/apps/dm/app/page.tsx b/apps/dm/app/page.tsx index a5f49f0bc..fdb323086 100644 --- a/apps/dm/app/page.tsx +++ b/apps/dm/app/page.tsx @@ -30,9 +30,6 @@ export default function Home() { - ); diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index a63244b98..94ebe1057 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -1,5 +1,5 @@ -import ModalSimilarPage from "@/app/pages/sdmMng/sdmInfoInsert/modalSimilar/page"; -import type { FormData } from "@/app/pages/sdmMng/sdmInfoInsert/page"; +import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page"; +import type { FormData } from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page"; import { useDmStore } from "@/app/store"; import { CANDIDATE, @@ -18,8 +18,8 @@ import { AccessibleFormField } from "./AccessibleFormField"; import type { ControllerRenderProps, UseFormReturn } from "react-hook-form"; import SearchInputText from "../common/searhDemandInput/component"; import type { DemandCompType } from "@/app/store/sdmMng"; -import NewWindowPortal from "@/app/pages/map/popup/windowPotal"; -import Map from "@/app/pages/map/popup/sdmInfoMap"; +import NewWindowPortal from "@/app/(popup-layout)/map/popup/windowPotal"; +import Map from "@/app/(popup-layout)/map/popup/sdmInfoMap"; export interface SdmInfoRegFormProps { form: UseFormReturn; @@ -82,11 +82,17 @@ export function SdmInfoRegForm({ } }, [searchParams, form]); + const handleCandidateClose = useCallback( + () => setIsCandidatePopup(false), + [] + ); + const searchCandidate = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - console.log(1); - setIsCandidatePopup(true); + //setIsCandidatePopup(true); + const popupOptions = `width=1920,height=1080,resizable='y',scrollbars='y'`; + window.open('/map/popup', 'title', popupOptions); }; const searchSimilarCase = (e: React.MouseEvent) => { @@ -610,19 +616,6 @@ export function SdmInfoRegForm({ /> )} - {/* 후보지 조회 새 창 포털 */} - {isCandidatePopup && ( - setIsCandidatePopup(false)} - title="공급처 후보지 조회" - width={window.screen.availWidth} - height={window.screen.availHeight} - resizable="no" - scrollbars="yes" - > - - - )} ); } diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 5386207d8..18ef123c3 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -38,6 +38,7 @@ function Gis({ setIsSearchModalOpen, onCheckedLayersChange }) { + const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); const [tabVisible, setTabVisible] = useState(true); @@ -151,6 +152,7 @@ function Gis({ // 지도 생성 및 초기화 useEffect(() => { + if (!mapRef.current) return; if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 @@ -257,120 +259,6 @@ function Gis({ } } - // // 데이터 로드 및 팝업 생성 로직 - // 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(() => { @@ -418,7 +306,6 @@ function Gis({ onFeatureSelect={handleFeatureSelect} /> )} -