지도 코드 정리

This commit is contained in:
yschoi
2025-07-31 13:15:22 +09:00
parent 5382328f94
commit 637929ea26
9 changed files with 217 additions and 321 deletions
+12 -10
View File
@@ -62,10 +62,9 @@ const Map = () => {
Object.keys(item).length > 0 &&
!prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId)
);
console.log(newItems)
console.log(newItems);
return [...prev, ...newItems];
});
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
@@ -232,14 +231,6 @@ const Map = () => {
return (
<>
<SearchBar
setHasuPk={(hasuPk: any) => {
if (hasuPk) {
setSelectDataPk(hasuPk);
}
}}
onApply={onApply}
/>
<div className="bg-container">
<Gis
treeData={treeData}
@@ -257,6 +248,17 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={onCheckedLayersChange}
imgVisble={false}
searchBar={(props: any) => (
<SearchBar
setHasuPk={(hasuPk: any) => {
if (hasuPk) {
setSelectDataPk(hasuPk);
}
}}
onApply={onApply}
{...props}
/>
)}
/>
</div>
<SelectedFcltList
+21 -7
View File
@@ -13,7 +13,11 @@ interface FeatureData {
}
interface MapInstance {
getLayers: () => { getArray: () => Array<{ getSource?: () => { getFeatureById?: (id: string) => FeatureData | null } }> };
getLayers: () => {
getArray: () => Array<{
getSource?: () => { getFeatureById?: (id: string) => FeatureData | null };
}>;
};
getView: () => { fit: (extent: number[], options: object) => void };
}
@@ -26,20 +30,20 @@ const Map = () => {
const fetchData = async () => {
try {
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`;
const reqUrl = `${API_URL}/lyrStngs/FO_NETWORK`;
const response = await fetch(reqUrl, {
method: "GET",
headers: {
accept: "application/json",
}
},
});
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
const data = await response.json(); // 여기서 json 파싱
const data = await response.json(); // 여기서 json 파싱
setTreeData(data); // 데이터 설정
} catch (error) {
console.error("데이터 불러오기 실패:", error);
@@ -60,7 +64,12 @@ const Map = () => {
}
// 항상 같은 select 객체 사용
const select = (GisApp.Module.select as unknown) as { resetSelectedFeature: () => void; selectFeature: (feature: FeatureData, selected: boolean) => void } | undefined;
const select = GisApp.Module.select as unknown as
| {
resetSelectedFeature: () => void;
selectFeature: (feature: FeatureData, selected: boolean) => void;
}
| undefined;
if (!select) return;
// 1. 기존 선택 초기화
@@ -115,9 +124,12 @@ const Map = () => {
};
// onCheckedLayersChange handler
const handleCheckedLayersChange = (checkedIds: string[], checkedNodesData: unknown[]) => {
const handleCheckedLayersChange = (
checkedIds: string[],
checkedNodesData: unknown[]
) => {
// Handle checked layers change if needed
console.log('Checked layers changed:', checkedIds, checkedNodesData);
console.log("Checked layers changed:", checkedIds, checkedNodesData);
};
return (
@@ -137,6 +149,8 @@ const Map = () => {
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={false}
searchBar={undefined}
/>
</div>
);
+2
View File
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import SearchModal from "./searchModal/searchModal";
import FeatureClickEventPopup from "./featureClickEventPopup";
import { SearchBar } from "./searchBar/searchBar";
// 타입 정의 추가
export type LyrStngProps = {
@@ -130,6 +131,7 @@ const Map = () => {
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={undefined}
searchBar={SearchBar}
/>
</div>
);
@@ -0,0 +1,83 @@
"use client";
import {
SearchDiv,
SearchFormLeft,
SelectBox,
type SelectDataType,
} from "@common/business_components/ui";
import { useCallback, useEffect, useState } from "react";
export function SearchBar() {
const [sidoValue, setSidoValue] = useState<string>("");
const [sigunValue, setSigunValue] = useState<string>("");
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
{ text: "시도 선택", val: "0" },
]);
const initializeSigunData = useCallback((sidoCd: string) => {
if (sidoCd === "0") {
setSigunValue("0");
setFilteredSigunData([{ text: "시도 선택", val: "0" }]);
} else {
const newFilteredData = [
{ text: "데이터", val: "0" },
// ...Common.selectSigunData.filter(
// (item: SelectDataType) => item.val.substring(0, 2) === sidoCd,
// ),
];
setSigunValue("0");
setFilteredSigunData(newFilteredData);
}
}, []);
const onSelectValue = useCallback(
(val: string, type: string) => {
switch (type) {
case "ctpvCd":
initializeSigunData(val);
setSidoValue(val);
break;
case "sggCd":
setSigunValue(val);
}
},
[initializeSigunData],
);
return (
<>
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<div className="card card-border flex">
<div className="col-span-10 flex flex-row">
<SelectBox
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
label="시도"
selectArray={/* Common.selectSidoData || */ []}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue || /* Common.selectSidoData[0]?.val || */ ""}
/>
<SelectBox
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
label="시군구"
selectArray={filteredSigunData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<SelectBox
className="mr-3 w-[250px]"
selectClass="rounded-lg bg-white"
labelClass="w-[200px]"
label="하수처리시설명"
selectArray={filteredSigunData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
</div>
</div>
</div>
</div>
</>
);
}
+1 -10
View File
@@ -136,16 +136,6 @@ const Map = () => {
gisMaps={gisMaps}
isMiniMap={false}
searchView={undefined}
// searchView={(props: any) => (
// <MapSearchBar
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// checkedLayers={checkedLayers}
// checkedLayersData={checkedLayersData}
// {...props}
// />
// )
// }
detailView={(props: any) => (
<NewWindowPopup
onClose={handlePopupClose} // 메모이제이션된 함수 사용
@@ -163,6 +153,7 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
searchBar={undefined}
/>
</div>
);
+1 -10
View File
@@ -123,16 +123,6 @@ const Map = () => {
gisMaps={gisMaps}
isMiniMap={false}
searchView={undefined}
// searchView={(props: any) => (
// <MapSearchBar
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// checkedLayers={checkedLayers}
// checkedLayersData={checkedLayersData}
// {...props}
// />
// )
// }
detailView={(props: any) =>(
<NewWindowPopup
onClose={handlePopupClose} // 메모이제이션된 함수 사용
@@ -150,6 +140,7 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
searchBar={undefined}
/>
</div>
);
@@ -98,7 +98,8 @@ function Gis({
detailView: DetailView,
onFeatureSelect,
setIsSearchModalOpen,
onCheckedLayersChange
onCheckedLayersChange,
searchBar: SearchBar
}) {
const mapRef = useRef();
const [mapObject, setMapObject] = useState({});
@@ -127,102 +128,7 @@ function Gis({
// Feature Click Event
const handleFeatureSelect = useCallback((data) => {
console.log(data);
// gisMap = mapInstanceRef.current;
// gisMap = mapInstanceRef.current;
// if (!gisMap) {
// console.warn('지도가 초기화되지 않았습니다.');
// return;
// }
// if (!gisMap) {
// console.warn('지도가 초기화되지 않았습니다.');
// return;
// }
// // 항상 같은 select 객체 사용
// const select = GisApp.Module.select;
// if (!select) return;
// // 항상 같은 select 객체 사용
// const select = GisApp.Module.select;
// if (!select) return;
// // 1. 기존 선택 초기화
// select.resetSelectedFeature();
// // 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;
// }
// }
// }
// });
// }
// // 3. 피처 선택
// if (foundFeature) {
// let geometry = foundFeature.getGeometry();
// if (geometry) {
// let extent = geometry.getExtent();
// 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);
// }
// setFeatureData(data);
// setIsOpen(true);
// setIsSearchModalOpen(false);
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
console.log(data)
}, [mapInstanceRef]);
// 지도 생성 및 초기화
@@ -347,44 +253,48 @@ function Gis({
<>
<GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}>
<div className="h-[calc(100vh-10rem)] flex relative">
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
<GisTree
visible={tabVisible}
setVisible={setTabVisible}
onCheckedChange={handleCheckedLayersChange}
onLayerIconClick={(layerNm) => {
layerNmRef.current = layerNm
setLayerNm(layerNm); setIsOpen(true); const dummyFeature = {
getProperties: () => ({}), // 최소 구조
};
onFeatureSelect(dummyFeature);
}}
imageVisible={imgVisble}
/>
{/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */}
{/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
{isOpen && !isSearchModalOpen && (
<DetailView
featureData={featureData || { getProperties: () => ({}) }}
seletectedLayerNm={layerNm}
onClose={closePopup} // 닫기 콜백 전달
onFeatureSelect={handleFeatureSelect}
<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}
/>
)}
{/* SearchView가 존재할 때만 렌더링 */}
{SearchView && (
<SearchView
open={isSearchModalOpen}
onOpenChange={setIsSearchModalOpen}
onFeatureSelect={handleFeatureSelect}
/>
)}
{/* <GisLonLat /> */}
<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 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} />
</div>
</div>
{isSearch && (
@@ -1,74 +1,61 @@
import { Search } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useGisContext } from './Gis';
import './lib/css/gis.edit.css';
import GisApp from './lib/gis.core';
import { Search } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useGisContext } from "./Gis";
import "./lib/css/gis.edit.css";
import GisApp from "./lib/gis.core";
export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) {
const { gisMap, gisCore } = useGisContext();
const [visibleComponents, setVisibleComponents] = useState({});
const addressResultLayer = useRef(null);
const [playShow, setPlayShow] = useState({ show: false });
export default function GisContent({
edit = false,
onFeatureSelect,
isSearch,
setIsSearchModalOpen,
}) {
const { gisMap, gisCore } = useGisContext();
//셀렉트 버튼 이벤트
// const clickFeature = useCallback(feature => {
// if (feature) {
// const layer = feature.getLayer();
// setVisibleComponents(prevState => ({
// ...prevState,
// }));
// }
// }, []);
//셀렉트 버튼 이벤트
const selectRslt = useCallback((feature) => {
GisApp.Module.layer.highLightFeature(
[feature],
{
id: GisApp.hlLayerId,
zIndex: 10,
opacity: 1,
selectable: true,
},
{ width: 30 },
);
clickFeature(feature);
}, []);
const selectRslt = useCallback(feature => {
GisApp.Module.layer.highLightFeature(
[feature],
{
id: GisApp.hlLayerId,
zIndex: 10,
opacity: 1,
selectable: true,
},
{ width: 30 }
);
clickFeature(feature);
}, []);
useEffect(() => {
if (gisMap) {
setVisibleComponents((prevState) => ({
...prevState,
}));
}
}, [gisMap]);
useEffect(() => {
if (gisMap) {
setVisibleComponents(prevState => ({
...prevState,
}));
}
}, [gisMap]);
if (!gisMap) return null;
if (!gisMap) return null;
return (
<>
<GisTree />
<GisMapToggle />
<GisLonLat />
<GisToolBar />
<GisSelect onFeatureSelect={onFeatureSelect} />
return (
<>
{/*<GisTree />*/}
{/* <GisMapToggle />
<GisLonLat />
<GisToolBar /> */}
{/* <GisSelect onFeatureSelect={onFeatureSelect} /> */}
{/* 검색 모달 */}
{isSearch && (
<div className="absolute right-8 top-[254px] z-20">
<button
onClick={() => setIsSearchModalOpen(true)}
className="overflow-hidden w-[40px] h-[40px] bg-white
border border-[#9aa7b8]
rounded
shadow-[0_0_5px_#999]
flex items-center justify-center
p-0
"
>
<Search size={24} className="text-gray-700" />
</button>
</div>
)}
</>
);
{/* 검색 모달 */}
{isSearch && (
<div className="absolute right-8 top-[254px] z-20">
<button
onClick={() => setIsSearchModalOpen(true)}
className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]"
>
<Search size={24} className="text-gray-700" />
</button>
</div>
)}
</>
);
}
@@ -1,84 +0,0 @@
// import React, { useState } from 'react';
// import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs';
// import GisTree from './GisTree';
// import GisSearch from './GisSearch';
// import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
// import SearchModal from '@/map/SearchModal';
// function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen, onFeatureSelect }) {
// const [activeTab, setActiveTab] = useState('layers');
// return (
// <>
// <div
// className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40
// ${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`}
// >
// <Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden">
// <TabsList className="flex-none mt-4 border-none rounded-none">
// <TabsTrigger value="layers" className="flex-1 h-10">레이어</TabsTrigger>
// {/* {isSearch && (
// <button
// onClick={() => setIsSearchModalOpen(true)}
// className="h-10 w-10 flex items-center justify-center px-0"
// >
// <Search size={18} className="text-gray-700" />
// </button>
// // <TabsTrigger value="search" className="flex-1 h-10 flex items-center justify-center">
// // <Search size={18} />
// // </TabsTrigger>
// )} */}
// </TabsList>
// {/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */}
// <div
// className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${activeTab === 'layers' ? 'block' : 'hidden'
// }`}
// >
// <GisTree />
// </div>
// {/* GisSearch도 동일하게 처리 */}
// {/* {isSearch && (
// <div
// className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${
// activeTab === 'search' ? 'block' : 'hidden'
// }`}
// >
// <GisSearch />
// </div>
// )} */}
// {/* 🔍 검색 모달 */}
// {/* <SearchModal
// open={isSearchModalOpen}
// onOpenChange={setIsSearchModalOpen}
// onFeatureSelect={onFeatureSelect}
// /> */}
// </Tabs>
// {/* 접기 버튼 */}
// <button
// onClick={() => setVisible(false)}
// className="absolute top-1/2 right-[-3px] transform -translate-y-1/2 z-50 bg-white border border-l border-gray-300 rounded-l-none rounded-r px-1 py-2 shadow-sm"
// >
// <ChevronLeft size={16} />
// </button>
// </div>
// {/* 열기 버튼 */}
// {!visible && (
// <button
// onClick={() => setVisible(true)}
// className="absolute top-1/2 left-[-3px] transform -translate-y-1/2 z-50 bg-white border border-r border-gray-300 rounded-r px-1 py-2 shadow-sm"
// >
// <ChevronRight size={16} />
// </button>
// )}
// </>
// );
// }
// export default React.memo(GisTab);