gis 설정 추가
This commit is contained in:
@@ -6,8 +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 "./components/popupComponent/featureClickEventPopup";
|
||||
import { SearchBar } from "./searchBar/searchBar";
|
||||
import { WaterDepth } from "./waterDepth/waterDepth";
|
||||
import { ProcSearchBar } from "@common/business_components/ui/map/searchBar";
|
||||
|
||||
// 타입 정의 추가
|
||||
export type LyrStngProps = {
|
||||
@@ -39,8 +38,7 @@ const Map = () => {
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
|
||||
|
||||
const [featureData, setFeatureData] = useState<any>(null); // 선택된 피쳐 데이터
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
@@ -118,6 +116,7 @@ const Map = () => {
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
const facilityTypes = [{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" }];
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
@@ -136,8 +135,7 @@ const Map = () => {
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={undefined}
|
||||
searchBar={SearchBar}
|
||||
// waterDepth={WaterDepth}
|
||||
searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />}
|
||||
waterDepth={undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { mapService } from "../service";
|
||||
import NewWindowPopup from "../components/newWindowPopup";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { SearchBar } from "../searchBar/searchBar";
|
||||
import { UndoIcon } from "lucide-react";
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
@@ -46,7 +47,8 @@ const Map = () => {
|
||||
};
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
const onFeatureSelect = (data: any) => {
|
||||
const onFeatureFocus = (data: any) => {
|
||||
console.log("page:onFeatureFocus");
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
@@ -64,26 +66,6 @@ const Map = () => {
|
||||
let foundFeature: any = null;
|
||||
let featureId = data.fid;
|
||||
const layerName = data.layerNm;
|
||||
console.log("선택된 레이어 이름:", data, layerName);
|
||||
if (layerName === "하수재이용시설") {
|
||||
// 재이용시설
|
||||
featureId = `${layerName}.${data.searchPk}`;
|
||||
} else if (
|
||||
//빗물이용시설, 중수도시설
|
||||
layerName === "빗물이용시설" ||
|
||||
layerName == "중수도시설"
|
||||
) {
|
||||
featureId = `${layerName}.${data.bdstInfoSn}`;
|
||||
} else if (layerName === "하수처리시설") { //하수처리시설
|
||||
featureId = `${layerName}.${data.bscFctCd}`;
|
||||
} else if (layerName === "온배수시설") { //온배수시설
|
||||
featureId = `${layerName}.${data.bdstRuseFcltSn}`;
|
||||
} else if (layerName == "수요처") { //수요처
|
||||
featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`;
|
||||
} else {
|
||||
console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName);
|
||||
featureId = `${layerName}.${data.fid || data.id || data.ftrIdn}`;
|
||||
}
|
||||
map
|
||||
.getLayers()
|
||||
.getArray()
|
||||
@@ -109,28 +91,15 @@ const Map = () => {
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
select.selectFeature(foundFeature, true);
|
||||
// select.selectFeature(foundFeature, true);
|
||||
} else {
|
||||
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
|
||||
}
|
||||
const currentId = data?.fid || data?.id || data?.ftrIdn;
|
||||
const lastId = featureData?.fid || featureData?.id || featureData?.ftrIdn;
|
||||
if (featureData && currentId === lastId) return; // 이전과 같으면 무시
|
||||
setFeatureData(data);
|
||||
setIsOpen(true);
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
// MapSearchBar 래퍼 컴포넌트
|
||||
// const CustomMapSearchBar = (props: any) => {
|
||||
// return (
|
||||
// <MapSearchBar
|
||||
// {...props}
|
||||
// searchType="공급처 후보지 조회"
|
||||
// showApplyButton={false}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
const renderDetailView = useCallback(
|
||||
(props: any) => (
|
||||
<NewWindowPopup
|
||||
@@ -152,7 +121,7 @@ const Map = () => {
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={renderDetailView}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
onFeatureSelect={onFeatureFocus}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
@@ -173,8 +142,9 @@ const Map = () => {
|
||||
];
|
||||
return (
|
||||
<SearchBar
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
gisMaps={gisMaps}
|
||||
facilityTypes={facilityTypes}
|
||||
onFeatureFocus={onFeatureFocus}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { useEffect, useRef, useState } from "react";
|
||||
interface SearchInputTextProps {
|
||||
initValue?: string;
|
||||
setValue: (val: string) => void;
|
||||
setRtnData: (val: { bscFctNm: string; bscFctCd: string }) => void;
|
||||
searchList: { bscFctNm: string; bscFctCd: string }[];
|
||||
setRtnData: (val: { fnm: string; fid: string }) => void;
|
||||
searchList: { fnm: string; fid: string }[];
|
||||
onEnter?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
@@ -59,9 +59,9 @@ export default function SearchInputText({
|
||||
setHighlightIndex(-1);
|
||||
};
|
||||
|
||||
const handleSelect = (item: { bscFctNm: string; bscFctCd: string }) => {
|
||||
setText(item.bscFctNm);
|
||||
setValue(item.bscFctNm);
|
||||
const handleSelect = (item: { fnm: string; fid: string }) => {
|
||||
setText(item.fnm);
|
||||
setValue(item.fnm);
|
||||
setRtnData(item);
|
||||
setShowResults(false);
|
||||
};
|
||||
@@ -107,7 +107,7 @@ export default function SearchInputText({
|
||||
}`}
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
{item.bscFctNm}
|
||||
{item.fnm}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// SearchBar.tsx
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
|
||||
import { Button } from "@common/components/ui";
|
||||
@@ -23,15 +22,15 @@ const FACILITY_TYPE_URL_MAP: Record<string, string> = {
|
||||
};
|
||||
|
||||
interface SearchBarProps {
|
||||
onFeatureSelect: (feature: any) => void;
|
||||
// gisMaps: React.RefObject<any>;
|
||||
gisMaps: React.RefObject<any>;
|
||||
facilityTypes: { text: string; val: string }[];
|
||||
onFeatureFocus: any;
|
||||
}
|
||||
|
||||
export function SearchBar({
|
||||
onFeatureSelect,
|
||||
// gisMaps,
|
||||
gisMaps,
|
||||
facilityTypes,
|
||||
onFeatureFocus,
|
||||
}: SearchBarProps) {
|
||||
const [sidoValue, setSidoValue] = useState("0");
|
||||
const [sigunValue, setSigunValue] = useState("0");
|
||||
@@ -39,9 +38,9 @@ export function SearchBar({
|
||||
facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0"
|
||||
);
|
||||
const [fcltyNm, setFcltyNm] = useState("");
|
||||
const [searchList, setSearchList] = useState<
|
||||
{ bscFctNm: string; bscFctCd: string }[]
|
||||
>([]);
|
||||
const [searchList, setSearchList] = useState<{ fnm: string; fid: string }[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [sidoData, setSidoData] = useState<SelectDataType[]>([
|
||||
{ text: "시도", val: "0" },
|
||||
@@ -163,9 +162,7 @@ export function SearchBar({
|
||||
body: JSON.stringify(procResult),
|
||||
});
|
||||
const procJson2 = await procRes2.json();
|
||||
const procResult2 = (procJson2.data || []).map(
|
||||
(item: any) => item.bscFctCd
|
||||
);
|
||||
const procResult2 = (procJson2.data || []).map((item: any) => item.fid);
|
||||
|
||||
//최종 처리시설코드로 재이용시설조회
|
||||
const res = await fetch(url, {
|
||||
@@ -220,11 +217,62 @@ export function SearchBar({
|
||||
const newList = list.map((item: any) => {
|
||||
const code = item.val;
|
||||
const name = item.text;
|
||||
return { bscFctNm: name, bscFctCd: code };
|
||||
return { fnm: name, fid: code };
|
||||
});
|
||||
setSearchList(newList);
|
||||
};
|
||||
|
||||
const moveFocus = (feature: any) => {
|
||||
|
||||
// console.log('searchbar:moveFocus')
|
||||
// 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;
|
||||
// let featureId = data.fid;
|
||||
// const layerName = data.layerNm;
|
||||
// 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);
|
||||
// }
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="col-span-4">
|
||||
@@ -269,13 +317,10 @@ export function SearchBar({
|
||||
setValue={(val: string = "") => {
|
||||
setFcltyNm(val);
|
||||
}}
|
||||
setRtnData={(selected: {
|
||||
bscFctNm: string;
|
||||
bscFctCd: string;
|
||||
}) => {
|
||||
setRtnData={(selected: { fnm: string; fid: string }) => {
|
||||
// 선택된 항목이 있을 경우 onFeatureSelect 실행
|
||||
const type = facilityType;
|
||||
onFeatureSelect({
|
||||
moveFocus({
|
||||
layerNm: type,
|
||||
...selected,
|
||||
});
|
||||
|
||||
@@ -116,8 +116,8 @@ function Gis({
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar,
|
||||
waterDepth: WaterDepth,
|
||||
height:height="h-[calc(100vh-10rem)]",
|
||||
waterDepth: WaterDepth,
|
||||
height: height = "h-[calc(100vh-10rem)]",
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
@@ -147,18 +147,30 @@ function Gis({
|
||||
setFeatureData(null);
|
||||
};
|
||||
|
||||
// Feature Click Event
|
||||
// 피처 클릭시
|
||||
const handleFeatureSelect = useCallback(
|
||||
(data) => {
|
||||
console.log("handleFeatureSelect");
|
||||
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
|
||||
},
|
||||
[mapInstanceRef],
|
||||
);
|
||||
|
||||
//피처로 포커스 이동
|
||||
const handleFeatureFocus = useCallback(
|
||||
(layerName, data) => {
|
||||
console.log("handleFeatureFocus");
|
||||
console.log(layerName);
|
||||
console.log(data);
|
||||
// onFeatureFocus?.(data);
|
||||
},
|
||||
[mapInstanceRef],
|
||||
);
|
||||
|
||||
// 지도 생성 및 초기화
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
|
||||
//if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
|
||||
|
||||
// EPSG:5179 등록
|
||||
proj4.defs([
|
||||
@@ -169,33 +181,53 @@ function Gis({
|
||||
]);
|
||||
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,
|
||||
}),
|
||||
});
|
||||
let map = mapInstanceRef.current;
|
||||
|
||||
gisMaps.current = map;
|
||||
// OpenLayers 지도 객체 생성
|
||||
if (!map) {
|
||||
console.log("지도 객체 생성");
|
||||
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,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// 📌 재마운트 시 target 없으면 다시 지정
|
||||
if (!map.getTarget()) {
|
||||
console.log("재마운트");
|
||||
map.setTarget(mapRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
const select = GisApp.Module.select;
|
||||
if (select) {
|
||||
console.log("select 바인딩");
|
||||
if (select.selectInteraction) {
|
||||
map.removeInteraction(select.selectInteraction);
|
||||
}
|
||||
select.map = map; // map 바인딩
|
||||
select.setInteraction();
|
||||
select.pointerMoveEventHandler();
|
||||
}
|
||||
|
||||
// 미니맵 추가
|
||||
if (isMiniMap) {
|
||||
@@ -212,12 +244,21 @@ function Gis({
|
||||
map.addControl(overviewMap);
|
||||
}
|
||||
|
||||
gisMaps.current = map;
|
||||
|
||||
// 스케일 컨트롤 및 베이스 레이어 설정
|
||||
gisCore.__proto__.map = map;
|
||||
gisCore.scaleControl();
|
||||
gisCore.changeBaseLayer("base");
|
||||
setMapObject({ map: map });
|
||||
mapInstanceRef.current = map; // 생성 완료 플래그 설정
|
||||
|
||||
// 📌 언마운트 시 완전 제거
|
||||
return () => {
|
||||
console.log("지도 제거");
|
||||
map.setTarget(null); // DOM 연결 해제
|
||||
mapInstanceRef.current = null; // 참조 초기화
|
||||
};
|
||||
}, []);
|
||||
|
||||
// treeData 변경 시 레이어 코드 생성
|
||||
@@ -231,11 +272,6 @@ function Gis({
|
||||
// 트리 데이터를 레이어 코드 객체로 변환
|
||||
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn");
|
||||
GisApp.layerCode = layerCode; // 전역 레이어 코드 설정
|
||||
|
||||
//setTreeData(data);
|
||||
// 편집 가능한 레이어 목록 생성
|
||||
//const layerItems = getLayerItems(layerCode);
|
||||
//setEditLayerItems(prev => [...layerItems])
|
||||
}, [treeData]);
|
||||
|
||||
const getLayerItems = (layerCode) => {
|
||||
@@ -267,9 +303,9 @@ function Gis({
|
||||
const handleCheckedLayersChange = useCallback(
|
||||
(checkedIds, checkedNodesData) => {
|
||||
setTimeout(() => {
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
}, 1000);
|
||||
},
|
||||
[onCheckedLayersChange],
|
||||
@@ -326,16 +362,16 @@ function Gis({
|
||||
)}
|
||||
{/* 시도/시군구 조회 영역 */}
|
||||
{SearchBar && (
|
||||
<div
|
||||
className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded w-max"
|
||||
>
|
||||
<SearchBar />
|
||||
<div className="absolute left-1/2 top-4 z-30 w-max -translate-x-1/2 rounded">
|
||||
{SearchBar}
|
||||
</div>
|
||||
)}
|
||||
<GisToolBar />
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
{typeof WaterDepth === "function" && <WaterDepth onClose={() => setIsOpen(false)} />}
|
||||
{typeof WaterDepth === "function" && (
|
||||
<WaterDepth onClose={() => setIsOpen(false)} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,46 +1,42 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { GisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
import GisOlSelect from './lib/gis.select';
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { GisContext } from "./Gis";
|
||||
import GisApp from "./lib/gis.core";
|
||||
import GisOlSelect from "./lib/gis.select";
|
||||
|
||||
export default React.memo(function GisSelect({ onFeatureSelect }) {
|
||||
const { gisMap } = useContext(GisContext);
|
||||
|
||||
const { gisMap } = useContext(GisContext);
|
||||
useEffect(() => {
|
||||
if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함
|
||||
|
||||
// 이미 select가 있으면 재생성하지 않음
|
||||
const select = new GisOlSelect(gisMap);
|
||||
GisApp.Module.select = select;
|
||||
|
||||
useEffect(() => {
|
||||
if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함
|
||||
// 피처 선택 시 콜백 등록
|
||||
select.getSelectedInfo(function (features) {
|
||||
if (features?.[0]) {
|
||||
console.log("GisSelect features", features);
|
||||
onFeatureSelect(features[0]);
|
||||
}
|
||||
});
|
||||
}, [gisMap, onFeatureSelect]);
|
||||
|
||||
// 이미 select가 있으면 재생성하지 않음
|
||||
if (!GisApp.Module.select) {
|
||||
const select = new GisOlSelect(gisMap);
|
||||
GisApp.Module.select = select;
|
||||
// const select = new GisOlSelect(gisMap);
|
||||
// GisApp.Module.select = select;
|
||||
|
||||
// 피처 선택 시 콜백 등록
|
||||
select.getSelectedInfo(function (features) {
|
||||
if (features?.[0]) {
|
||||
console.log('GisSelect features', features);
|
||||
onFeatureSelect(features[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [gisMap, onFeatureSelect]);
|
||||
// select.getSelectedInfo(function (features) {
|
||||
// //clickFeature(features[0]);
|
||||
// if(features.length > 0) {
|
||||
// const layer = features[0];
|
||||
// // console.log('GisSelect layer', layer);
|
||||
// onFeatureSelect(layer); //피쳐 속성데이터 상위컴포넌트로 전달
|
||||
// }
|
||||
// });
|
||||
|
||||
// const select = new GisOlSelect(gisMap);
|
||||
// GisApp.Module.select = select;
|
||||
|
||||
// select.getSelectedInfo(function (features) {
|
||||
// //clickFeature(features[0]);
|
||||
// if(features.length > 0) {
|
||||
// const layer = features[0];
|
||||
// // console.log('GisSelect layer', layer);
|
||||
// onFeatureSelect(layer); //피쳐 속성데이터 상위컴포넌트로 전달
|
||||
// }
|
||||
// });
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <div className="select_control map_control_div ol-unselectable ol-control" style={{ pointerEvents: 'auto', zIndex: '1' }}>
|
||||
return (
|
||||
<>
|
||||
{/* <div className="select_control map_control_div ol-unselectable ol-control" style={{ pointerEvents: 'auto', zIndex: '1' }}>
|
||||
<h3>선,면,다각형을 이용한 시설물 선택 영역</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -55,6 +51,6 @@ export default React.memo(function GisSelect({ onFeatureSelect }) {
|
||||
</li>
|
||||
</ul>
|
||||
</div> */}
|
||||
</>
|
||||
);
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,255 +2,331 @@ import {
|
||||
MultiBackend,
|
||||
Tree,
|
||||
getBackendOptions,
|
||||
DndProvider
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useGisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
|
||||
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick,imageVisible=false }) {
|
||||
DndProvider,
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useGisContext } from "./Gis";
|
||||
import GisApp from "./lib/gis.core";
|
||||
|
||||
export default React.memo(function GisTree({
|
||||
checkedNodes = [],
|
||||
setCheckedNodes = () => {},
|
||||
visible,
|
||||
setVisible,
|
||||
onCheckedChange,
|
||||
onLayerIconClick,
|
||||
imageVisible = false,
|
||||
}) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
const [checked, setChecked] = useState([]);
|
||||
const [expanded, setExpanded] = useState([]);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('layers');
|
||||
|
||||
const [activeTab, setActiveTab] = useState("layers");
|
||||
|
||||
useEffect(() => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
const nodeData = setNodeValues(treeData);
|
||||
setNodes(nodeData);
|
||||
gisCore.setAllLayersOnMap(false);
|
||||
}
|
||||
if (treeData && treeData.length > 0) {
|
||||
const nodeData = setNodeValues(treeData);
|
||||
setNodes(nodeData);
|
||||
gisCore.setAllLayersOnMap(false);
|
||||
}
|
||||
}, [treeData]);
|
||||
|
||||
// 트리 노드 체크 시 레이어 표시/숨김
|
||||
|
||||
useEffect(() => {
|
||||
if (nodes.length > 0) {
|
||||
// 기본으로 체크된 노드들 찾기
|
||||
const defaultChecked = getDefaultCheckedNodes(nodes);
|
||||
setChecked(defaultChecked);
|
||||
|
||||
// 기본으로 확장된 노드들 설정
|
||||
const defaultExpanded = Array.from(new Set([
|
||||
...defaultChecked,
|
||||
...defaultChecked.flatMap((id) => getAncestors(id, nodes))
|
||||
]));
|
||||
setExpanded(defaultExpanded);
|
||||
|
||||
// 기본 체크된 레이어들을 지도에 표시
|
||||
gisCore.setLayerOnMap(defaultChecked, true);
|
||||
|
||||
// 초기 체크된 레이어 정보를 상위로 전달
|
||||
const defaultCheckedNodesData = nodes.filter(node => defaultChecked.includes(node.id));
|
||||
onCheckedChange?.(defaultChecked, defaultCheckedNodesData);
|
||||
}
|
||||
}, [nodes]); // onCheckedChange를 의존성에 추가
|
||||
|
||||
// 초기 표시 여부가 'Y'인 레이어들 찾기
|
||||
const getDefaultCheckedNodes = (nodes, values = []) => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
if (node.data.initEprssYn === 'Y') {
|
||||
values.push(node.data.lyrSn);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
const getDescendants = (id, all) => {
|
||||
const children = all.filter((n) => n.parent === id);
|
||||
return children.reduce(
|
||||
(acc, child) => acc.concat(child.id, ...getDescendants(child.id, all)),
|
||||
[]
|
||||
if (nodes.length > 0) {
|
||||
const defaultChecked = getDefaultCheckedNodes(nodes);
|
||||
setChecked(defaultChecked);
|
||||
|
||||
const defaultExpanded = Array.from(
|
||||
new Set([
|
||||
...defaultChecked,
|
||||
...defaultChecked.flatMap((id) => getAncestors(id, nodes)),
|
||||
])
|
||||
);
|
||||
setExpanded(defaultExpanded);
|
||||
|
||||
gisCore.setLayerOnMap(defaultChecked, true);
|
||||
|
||||
const defaultCheckedNodesData = nodes.filter((node) =>
|
||||
defaultChecked.includes(node.id)
|
||||
);
|
||||
onCheckedChange?.(defaultChecked, defaultCheckedNodesData);
|
||||
}
|
||||
}, [nodes]);
|
||||
|
||||
// ===== 유틸 함수 =====
|
||||
const getDescendants = (id, all) => {
|
||||
const children = all.filter((n) => n.parent === id);
|
||||
return children.reduce(
|
||||
(acc, child) => acc.concat(child.id, ...getDescendants(child.id, all)),
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const getAncestors = (id, all) => {
|
||||
const node = all.find((n) => n.id === id);
|
||||
if (!node || node.parent == null) return [];
|
||||
return [node.parent, ...getAncestors(node.parent, all)];
|
||||
const node = all.find((n) => n.id === id);
|
||||
if (!node || node.parent == null) return [];
|
||||
return [node.parent, ...getAncestors(node.parent, all)];
|
||||
};
|
||||
|
||||
const isEveryChildChecked = (parentId, all, checked) => {
|
||||
const children = all.filter((n) => n.parent === parentId);
|
||||
return children.every((c) => checked.includes(c.id));
|
||||
|
||||
const getChildren = (parentId, all) =>
|
||||
all.filter((n) => n.parent === parentId);
|
||||
|
||||
const areAllChildrenChecked = (parentId, all, checkedIds) => {
|
||||
const children = getChildren(parentId, all);
|
||||
return (
|
||||
children.length > 0 &&
|
||||
children.every((c) => checkedIds.includes(c.id))
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const updateParentCheckState = (nodeId, all, checkedIds) => {
|
||||
const parent = all.find((n) => n.id === nodeId)?.parent;
|
||||
if (parent == null) return checkedIds;
|
||||
|
||||
if (areAllChildrenChecked(parent, all, checkedIds)) {
|
||||
if (!checkedIds.includes(parent)) {
|
||||
checkedIds.push(parent);
|
||||
}
|
||||
} else {
|
||||
checkedIds = checkedIds.filter((id) => id !== parent);
|
||||
}
|
||||
return updateParentCheckState(parent, all, checkedIds);
|
||||
};
|
||||
|
||||
// ===== 기본 체크 노드 찾기 =====
|
||||
const getDefaultCheckedNodes = (nodes) => {
|
||||
let checkedIds = nodes
|
||||
.filter((n) => n.data.initEprssYn === "Y")
|
||||
.map((n) => n.id);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
checkedIds = updateParentCheckState(node.id, nodes, checkedIds);
|
||||
});
|
||||
|
||||
return Array.from(new Set(checkedIds));
|
||||
};
|
||||
|
||||
// ===== 체크박스 클릭 처리 =====
|
||||
const handleCheck = (id) => {
|
||||
setChecked((prevChecked) => {
|
||||
const isChecked = prevChecked.includes(id);
|
||||
let newChecked = [...prevChecked];
|
||||
if (isChecked) {
|
||||
const descendants = getDescendants(id, nodes);
|
||||
newChecked = newChecked.filter((cid) => cid !== id && !descendants.includes(cid));
|
||||
const ancestors = getAncestors(id, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (newChecked.includes(p)) {
|
||||
newChecked = newChecked.filter((cid) => cid !== p);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const descendants = getDescendants(id, nodes);
|
||||
newChecked = Array.from(new Set([...newChecked, id, ...descendants]));
|
||||
const ancestors = getAncestors(id, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (isEveryChildChecked(p, nodes, newChecked)) {
|
||||
newChecked.push(p);
|
||||
}
|
||||
});
|
||||
// setExpanded((prev) => Array.from(new Set([...prev, ...ancestors])));
|
||||
}
|
||||
const allIds = nodes.map((n) => n.id);
|
||||
const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
|
||||
gisCore.setLayerOnMap(newChecked, true);
|
||||
gisCore.setLayerOnMap(unchecked, false);
|
||||
|
||||
// 체크된 노드 정보와 함께 노드 데이터를 상위로 전달
|
||||
const checkedNodesData = nodes.filter(node => newChecked.includes(node.id));
|
||||
onCheckedChange?.(newChecked, checkedNodesData);
|
||||
|
||||
return newChecked;
|
||||
});
|
||||
setChecked((prevChecked) => {
|
||||
let newChecked = [...prevChecked];
|
||||
const isChecked = prevChecked.includes(id);
|
||||
|
||||
if (isChecked) {
|
||||
// 해제
|
||||
const descendants = getDescendants(id, nodes);
|
||||
newChecked = newChecked.filter(
|
||||
(cid) => cid !== id && !descendants.includes(cid)
|
||||
);
|
||||
} else {
|
||||
// 체크
|
||||
const descendants = getDescendants(id, nodes);
|
||||
newChecked = Array.from(new Set([...newChecked, id, ...descendants]));
|
||||
}
|
||||
|
||||
// 부모 상태 재귀 업데이트
|
||||
newChecked = updateParentCheckState(id, nodes, newChecked);
|
||||
|
||||
const allIds = nodes.map((n) => n.id);
|
||||
const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
|
||||
gisCore.setLayerOnMap(newChecked, true);
|
||||
gisCore.setLayerOnMap(unchecked, false);
|
||||
|
||||
const checkedNodesData = nodes.filter((node) =>
|
||||
newChecked.includes(node.id)
|
||||
);
|
||||
onCheckedChange?.(newChecked, checkedNodesData);
|
||||
|
||||
return newChecked;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ===== 노드 데이터 변환 =====
|
||||
const setNodeValues = (result) => {
|
||||
return result.map((item) => {
|
||||
const src = makeLegendSrc(item);
|
||||
let label = item.lyrNm;
|
||||
return {
|
||||
id: item.lyrSn,
|
||||
text: label,
|
||||
parent: item.upperLyrSn,
|
||||
droppable: !!item.children,
|
||||
data: item,
|
||||
icon: src ? <img src={src} alt={item.lyrNm} /> : '',
|
||||
};
|
||||
});
|
||||
return result.map((item) => {
|
||||
const src = makeLegendSrc(item);
|
||||
let label = item.lyrNm;
|
||||
return {
|
||||
id: item.lyrSn,
|
||||
text: label,
|
||||
parent: item.upperLyrSn,
|
||||
droppable: !!item.children,
|
||||
data: item,
|
||||
icon: src ? <img src={src} alt={item.lyrNm} /> : "",
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const makeLegendSrc = layerinfo => {
|
||||
if (!layerinfo.gobjTy) return null;
|
||||
|
||||
let src;
|
||||
if (layerinfo.iconNm) {
|
||||
src = GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm;
|
||||
} else {
|
||||
const ss = makeLegendImage(layerinfo);
|
||||
src = ss.src;
|
||||
}
|
||||
return src;
|
||||
|
||||
const makeLegendSrc = (layerinfo) => {
|
||||
if (!layerinfo.gobjTy) return null;
|
||||
let src;
|
||||
if (layerinfo.iconNm) {
|
||||
src = GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm;
|
||||
} else {
|
||||
const ss = makeLegendImage(layerinfo);
|
||||
src = ss.src;
|
||||
}
|
||||
return src;
|
||||
};
|
||||
|
||||
const makeLegendImage = layerinfo => {
|
||||
let image = document.createElement('img');
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = 16;
|
||||
canvas.height = 16;
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
|
||||
if (layerinfo.gobjTy === 'lineLabelPoligon') {
|
||||
ctx.moveTo(1, 1);
|
||||
ctx.lineTo(15, 1);
|
||||
ctx.lineTo(15, 15);
|
||||
ctx.lineTo(1, 15);
|
||||
ctx.lineTo(1, 1);
|
||||
} else if (layerinfo.gobjTy === 'line') {
|
||||
ctx.moveTo(1, 15);
|
||||
ctx.lineTo(5, 1);
|
||||
ctx.lineTo(9, 15);
|
||||
ctx.lineTo(15, 1);
|
||||
} else {
|
||||
ctx.moveTo(1, 1);
|
||||
ctx.lineTo(15, 1);
|
||||
ctx.lineTo(15, 15);
|
||||
ctx.lineTo(1, 15);
|
||||
ctx.lineTo(1, 1);
|
||||
}
|
||||
|
||||
if (layerinfo.gobjTy === 'lineLabelPoligon') {
|
||||
ctx.fillStyle = layerinfo.polgnColor;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = layerinfo.lnColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (layerinfo.gobjTy === 'line') {
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = layerinfo.lnColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
image.src = canvas.toDataURL('image/png');
|
||||
return image;
|
||||
|
||||
const makeLegendImage = (layerinfo) => {
|
||||
let image = document.createElement("img");
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.width = 16;
|
||||
canvas.height = 16;
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.beginPath();
|
||||
|
||||
if (layerinfo.gobjTy === "lineLabelPoligon") {
|
||||
ctx.moveTo(1, 1);
|
||||
ctx.lineTo(15, 1);
|
||||
ctx.lineTo(15, 15);
|
||||
ctx.lineTo(1, 15);
|
||||
ctx.lineTo(1, 1);
|
||||
} else if (layerinfo.gobjTy === "line") {
|
||||
ctx.moveTo(1, 15);
|
||||
ctx.lineTo(5, 1);
|
||||
ctx.lineTo(9, 15);
|
||||
ctx.lineTo(15, 1);
|
||||
} else {
|
||||
ctx.moveTo(1, 1);
|
||||
ctx.lineTo(15, 1);
|
||||
ctx.lineTo(15, 15);
|
||||
ctx.lineTo(1, 15);
|
||||
ctx.lineTo(1, 1);
|
||||
}
|
||||
|
||||
if (layerinfo.gobjTy === "lineLabelPoligon") {
|
||||
ctx.fillStyle = layerinfo.polgnColor;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = layerinfo.lnColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (layerinfo.gobjTy === "line") {
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = layerinfo.lnColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
image.src = canvas.toDataURL("image/png");
|
||||
return image;
|
||||
};
|
||||
|
||||
const handleLayerImgClick = (nodeNm, nodeId, layerNm) => {
|
||||
if(!checked.includes(nodeId)) alert('시설을 선택해주세요')
|
||||
else onLayerIconClick(nodeNm)
|
||||
}
|
||||
if (!checked.includes(nodeId)) alert("시설을 선택해주세요");
|
||||
else onLayerIconClick(nodeNm);
|
||||
};
|
||||
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
|
||||
// ===== 렌더 =====
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
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
|
||||
className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${activeTab === 'layers' ? 'block' : 'hidden'}`}
|
||||
>
|
||||
<h3 className="text-3xl text-gray-600 text-bold font-bold mx-2 my-4 ">레이어</h3>
|
||||
<hr className="mb-4" />
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={nodes}
|
||||
rootId={null}
|
||||
initialOpen={expanded}
|
||||
onOpenChange={setExpanded}
|
||||
render={(node, { depth, isOpen, onToggle }) => (
|
||||
<div
|
||||
className="flex items-center gap-2 py-1 px-2 hover:bg-gray-100 rounded"
|
||||
style={{ marginInlineStart: depth * 16 + (!node.droppable ? 16 : 0) }}
|
||||
>
|
||||
{node.droppable && (
|
||||
<i onClick={onToggle} className={`diveicon di-arrow-forward mr-2 text-base text-label ${isOpen ? 'rotate-90' : ''}`}
|
||||
title={isOpen ? '닫기' : '열기'}>
|
||||
</i>
|
||||
)}
|
||||
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer" />
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
|
||||
{imageVisible ? depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{handleLayerImgClick(node.text, node.id, node.data.lyrTyNm)}} alt={node.text}></img></span> : null}
|
||||
</div>
|
||||
)}
|
||||
sort={(a, b) => {
|
||||
const aOrder = parseInt(a.data?.sortOrdr ?? '0');
|
||||
const bOrder = parseInt(b.data?.sortOrdr ?? '0');
|
||||
return aOrder - bOrder;
|
||||
}}
|
||||
<>
|
||||
<div
|
||||
className={`absolute left-0 top-0 z-40 flex h-full w-[250px] flex-col overflow-hidden border-r border-gray-300 bg-white transition-all duration-300 ${
|
||||
visible
|
||||
? "translate-x-0 opacity-100"
|
||||
: "pointer-events-none -translate-x-full opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`grow overflow-y-auto border-none p-2 pt-0 shadow-none ${
|
||||
activeTab === "layers" ? "block" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<h3 className="text-bold mx-2 my-4 text-3xl font-bold text-gray-600">
|
||||
레이어
|
||||
</h3>
|
||||
<hr className="mb-4" />
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={nodes}
|
||||
rootId={null}
|
||||
initialOpen={expanded}
|
||||
onOpenChange={setExpanded}
|
||||
render={(node, { depth, isOpen, onToggle }) => {
|
||||
const isVisible = node.data.exprsYn === "Y";
|
||||
const hasVisibleChildren = nodes.some(
|
||||
(child) =>
|
||||
child.parent === node.id && child.data.exprsYn === "Y"
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-2 rounded px-2 py-1 hover:bg-gray-100"
|
||||
style={{
|
||||
marginInlineStart: depth * 16,
|
||||
display: isVisible ? "flex" : "none",
|
||||
}}
|
||||
>
|
||||
{hasVisibleChildren ? (
|
||||
<i
|
||||
onClick={onToggle}
|
||||
className={`diveicon di-arrow-forward mr-2 text-base text-label ${
|
||||
isOpen ? "rotate-90" : ""
|
||||
}`}
|
||||
title={isOpen ? "닫기" : "열기"}
|
||||
/>
|
||||
</DndProvider>
|
||||
</div>
|
||||
{/* 접기 버튼 */}
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span style={{ display: "inline-block", width: 16 }} />
|
||||
)}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked.includes(node.id)}
|
||||
onChange={() => handleCheck(node.id)}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">
|
||||
{node.text}
|
||||
</span>
|
||||
{imageVisible && depth > 0 && (
|
||||
<span>
|
||||
<img
|
||||
className="h-5 w-5 cursor-pointer"
|
||||
src="/images/map/layericon/Layer_Select_BTN.png"
|
||||
onClick={() =>
|
||||
handleLayerImgClick(
|
||||
node.text,
|
||||
node.id,
|
||||
node.data.lyrTyNm
|
||||
)
|
||||
}
|
||||
alt={node.text}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
sort={(a, b) => {
|
||||
const aOrder = parseInt(a.data?.sortOrdr ?? "0");
|
||||
const bOrder = parseInt(b.data?.sortOrdr ?? "0");
|
||||
return aOrder - bOrder;
|
||||
}}
|
||||
/>
|
||||
</DndProvider>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setVisible(false)}
|
||||
className="absolute right-[-3px] top-1/2 z-50 -translate-y-1/2 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
</div>
|
||||
{!visible && (
|
||||
<button
|
||||
onClick={() => setVisible(true)}
|
||||
className="absolute left-[-3px] top-1/2 z-50 -translate-y-1/2 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm"
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -566,18 +566,24 @@ GisApp.core.prototype = {
|
||||
*/
|
||||
setLayerOnMap: function (layerIds, visible, addQurryFlter) {
|
||||
for (let i in layerIds) {
|
||||
|
||||
|
||||
let id = layerIds[i];
|
||||
let layer = this.getLayerById(id);
|
||||
if (layer) {
|
||||
layer.setVisible(visible);
|
||||
} else if (visible) {
|
||||
const layerCode = GisApp.layerCode[id];
|
||||
const actlLyrYn = layerCode.actlLyrYn;
|
||||
if(actlLyrYn == 'N') continue;
|
||||
if(!layerCode.lyrTyNm) continue;
|
||||
const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || '');
|
||||
layerCode['flterSql'] = queryFlter;
|
||||
//layer = this.addWmsLayer(layerCode);
|
||||
layer = this.addWfsLayer(layerCode);
|
||||
|
||||
if(layerCode.srvcNm == 'wms'){
|
||||
layer = this.addWmsLayer(layerCode);
|
||||
}else {
|
||||
layer = this.addWfsLayer(layerCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (GisApp.Module.select) {
|
||||
|
||||
@@ -112,6 +112,7 @@ GisOlLayer.prototype = {
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
let lyrTyNm = options.lyrTyNm;
|
||||
let typename = this.storageNm + lyrTyNm;
|
||||
let filter = options?.flterSql && options.flterSql && options.flterSql.trim() !== ''
|
||||
|
||||
let params = {};
|
||||
params['TYPENAME'] = typename;
|
||||
@@ -120,6 +121,7 @@ GisOlLayer.prototype = {
|
||||
params['REQUEST'] = 'GetFeature';
|
||||
params['SRS'] = toProj;
|
||||
params['OUTPUTFORMAT'] = 'application/json';
|
||||
params['CQL_FILTER'] = filter;
|
||||
let strParams = new URLSearchParams(params).toString();
|
||||
|
||||
let source = new VectorSource({
|
||||
@@ -141,17 +143,16 @@ GisOlLayer.prototype = {
|
||||
|
||||
//레이어 소스 생성
|
||||
let source;
|
||||
let cmncNm = option.cmncNm;
|
||||
|
||||
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' //펌프장
|
||||
) {
|
||||
source = this.getVectorSourceBbox(option);
|
||||
}else {
|
||||
source = this.getVectorTileSource(option);
|
||||
try {
|
||||
if(cmncNm == 'bbox') {
|
||||
source = this.getVectorSourceBbox(option);
|
||||
}else {
|
||||
source = this.getVectorTileSource(option);
|
||||
}
|
||||
}catch(error){
|
||||
console.warn(option);
|
||||
}
|
||||
|
||||
returnLayer.setSource(source);
|
||||
@@ -564,6 +565,15 @@ GisOlLayer.prototype = {
|
||||
cesiumScene.camera.moveEnd.addEventListener(cesiumScene._moveEndListener);
|
||||
}
|
||||
},
|
||||
getFeatureById: function(featureId){
|
||||
this.layers
|
||||
.getArray()
|
||||
.forEach(layer => {
|
||||
const source = layer.getSource();
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) return feature;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function getOpenLayersZoom(cesiumScene) {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// SearchInputText.tsx
|
||||
"use client";
|
||||
import { Input } from "@common/components/ui";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface SearchInputTextProps {
|
||||
initValue?: string;
|
||||
setValue: (val: string) => void;
|
||||
setRtnData: (val: { fnm: string; fid: string }) => void;
|
||||
searchList: { fnm: string; fid: string }[];
|
||||
onEnter?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SearchInputText({
|
||||
initValue,
|
||||
setValue,
|
||||
setRtnData,
|
||||
searchList,
|
||||
onEnter,
|
||||
className,
|
||||
}: SearchInputTextProps) {
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
const [text, setText] = useState(initValue ?? "");
|
||||
const [highlightIndex, setHighlightIndex] = useState(-1);
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setText(initValue ?? "");
|
||||
}, [initValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchList.length > 0) {
|
||||
setShowResults(true);
|
||||
} else {
|
||||
setShowResults(false);
|
||||
}
|
||||
setHighlightIndex(-1);
|
||||
}, [searchList]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (
|
||||
wrapperRef.current &&
|
||||
!(wrapperRef.current as any).contains(event.target)
|
||||
) {
|
||||
setShowResults(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [wrapperRef]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setText(e.target.value);
|
||||
setValue(e.target.value);
|
||||
setHighlightIndex(-1);
|
||||
};
|
||||
|
||||
const handleSelect = (item: { fnm: string; fid: string }) => {
|
||||
setText(item.fnm);
|
||||
setValue(item.fnm);
|
||||
setRtnData(item);
|
||||
setShowResults(false);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
if (highlightIndex >= 0 && highlightIndex < searchList.length) {
|
||||
const selected = searchList[highlightIndex];
|
||||
if (selected) handleSelect(selected);
|
||||
} else if (onEnter) {
|
||||
onEnter();
|
||||
}
|
||||
} else if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setHighlightIndex((prev) =>
|
||||
prev < searchList.length - 1 ? prev + 1 : prev
|
||||
);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setHighlightIndex((prev) => (prev > 0 ? prev - 1 : -1));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative" ref={wrapperRef}>
|
||||
<Input
|
||||
value={text}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="시설명을 입력하세요"
|
||||
className={
|
||||
className + " rounded-lg border px-2 py-1 text-sm h-[29.5px]"
|
||||
}
|
||||
/>
|
||||
{showResults && (
|
||||
<div className="absolute z-10 mt-1 max-h-200 w-full overflow-auto rounded border bg-white shadow">
|
||||
{searchList.length > 0 ? (
|
||||
searchList.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`cursor-pointer px-3 py-1 hover:bg-gray-100 ${
|
||||
highlightIndex === index ? "bg-blue-100" : ""
|
||||
}`}
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
{item.fnm}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="px-3 py-2 text-sm text-gray-500">
|
||||
검색 결과가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "../../../ui";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
const geoserverUrl =
|
||||
"/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json";
|
||||
|
||||
const FACILITY_TYPE_URL_MAP: Record<string, string> = {
|
||||
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<any>;
|
||||
facilityTypes: { text: string; val: string }[];
|
||||
}
|
||||
|
||||
export const ComboSearchBar = ({
|
||||
onFeatureSelect,
|
||||
gisMaps,
|
||||
facilityTypes,
|
||||
}: SearchBarProps) => {
|
||||
const [sidoValue, setSidoValue] = useState("0");
|
||||
const [sigunValue, setSigunValue] = useState("0");
|
||||
const [sidoData, setSidoData] = useState<SelectDataType[]>([
|
||||
{ text: "선택", val: "0" },
|
||||
]);
|
||||
const [sigunData, setSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "선택", val: "0" },
|
||||
]);
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "선택", val: "0" },
|
||||
]);
|
||||
|
||||
const [facilityData, setFacilityData] = useState<SelectDataType[]>([
|
||||
{ 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 (
|
||||
<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">
|
||||
{facilityTypes.length > 1 && (
|
||||
<SelectBox
|
||||
labelClass="w-[120px]"
|
||||
className="mr-10 w-[190px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="시설물유형"
|
||||
selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "facilityType")
|
||||
}
|
||||
value={facilityType}
|
||||
/>
|
||||
)}
|
||||
<SelectBox
|
||||
labelClass="w-[40px]"
|
||||
className="mr-5 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={sidoData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "ctpvCd")
|
||||
}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="mr-5 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={filteredSigunData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "sggCd")
|
||||
}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[60px]"
|
||||
className="mr-3 w-[180px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="시설명"
|
||||
selectArray={facilityData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "fcltCd")
|
||||
}
|
||||
value={facilityValue}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button
|
||||
color="primary"
|
||||
className="mx-2 ml-5 mt-1"
|
||||
size={"sm"}
|
||||
onClick={handleSearch}
|
||||
>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./comboSearchBar";
|
||||
export * from "./procSearchBar";
|
||||
export * from './searchBar'
|
||||
@@ -0,0 +1,307 @@
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "../../../ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import GisApp from "../lib/gis.core";
|
||||
|
||||
const geoserverUrl =
|
||||
"/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json";
|
||||
|
||||
const FACILITY_TYPE_URL_MAP: Record<string, string> = {
|
||||
tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설
|
||||
};
|
||||
|
||||
interface SearchBarProps {
|
||||
gisMaps: React.RefObject<any>;
|
||||
facilityTypes: { text: string; val: string }[];
|
||||
}
|
||||
|
||||
export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
const [sidoValue, setSidoValue] = useState("0");
|
||||
const [sigunValue, setSigunValue] = useState("0");
|
||||
const [sidoData, setSidoData] = useState<SelectDataType[]>([
|
||||
{ text: "시도", val: "0" },
|
||||
]);
|
||||
const [sigunData, setSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "시군구", val: "0" },
|
||||
]);
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "시군구", val: "0" },
|
||||
]);
|
||||
|
||||
const [facilityData, setFacilityData] = useState<SelectDataType[]>([
|
||||
{ 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);
|
||||
|
||||
// 하수처리시설일 때 바로 handleFeatureFocus 호출
|
||||
if (val !== "0") {
|
||||
handleFeatureFocus({
|
||||
fid: val,
|
||||
layerNm: "tb_il_swage_prcs_fclt", // 레이어 이름
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[initializeSigunData, sigunData, sigunValue, sidoData, sidoValue],
|
||||
);
|
||||
|
||||
const handleFeatureFocus = (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;
|
||||
let featureId = data.fid;
|
||||
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,
|
||||
});
|
||||
} else {
|
||||
console.warn("geometry를 찾을 수 없습니다:", data);
|
||||
}
|
||||
} else {
|
||||
console.warn("Feature를 찾을 수 없습니다:", data);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
labelClass="w-[40px]"
|
||||
className="mr-10 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={sidoData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "ctpvCd")
|
||||
}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="mr-10 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={filteredSigunData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "sggCd")
|
||||
}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[60px]"
|
||||
className="mr-3 w-[180px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={facilityData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "fcltCd")
|
||||
}
|
||||
value={facilityValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,347 @@
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "../../../ui";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import SearchInputText from "./SearchInputText";
|
||||
|
||||
const FACILITY_TYPE_URL_MAP: Record<string, string> = {
|
||||
tb_il_swage_prcs_fclt:
|
||||
"/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&propertyName=swtrmt_fclt_cd,swtrmt_fclt_nm&cql_filter=",
|
||||
tb_wr_bdst_ruse_fclt:
|
||||
"/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000/",
|
||||
tb_wr_rnwtr_utztn_fclt:
|
||||
"/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}/{fcltyNm}",
|
||||
tb_wr_rswtr_fclt:
|
||||
"/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}/{fcltyNm}",
|
||||
tb_wr_htdfl_fclt:
|
||||
"/reuse-service/drain/facilityList/1/1000/{sggCd}/{fcltyNm}",
|
||||
tb_wr_ruse_fclt_dmd_co:
|
||||
"/reuse-service/reuseFc/request/1/1000/{sggCd}/{fcltyNm}",
|
||||
tb_wr_tmpr_dmd_co:
|
||||
"/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}/{fcltyNm}",
|
||||
};
|
||||
|
||||
interface SearchBarProps {
|
||||
gisMaps: React.RefObject<any>;
|
||||
facilityTypes: { text: string; val: string }[];
|
||||
onFeatureFocus: any;
|
||||
}
|
||||
|
||||
export const SearchBar = ({
|
||||
gisMaps,
|
||||
facilityTypes,
|
||||
onFeatureFocus,
|
||||
}: SearchBarProps) => {
|
||||
const [sidoValue, setSidoValue] = useState("0");
|
||||
const [sigunValue, setSigunValue] = useState("0");
|
||||
const [facilityType, setFacilityType] = useState(
|
||||
facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0"
|
||||
);
|
||||
const [fcltyNm, setFcltyNm] = useState("");
|
||||
const [searchList, setSearchList] = useState<{ fnm: string; fid: string }[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [sidoData, setSidoData] = useState<SelectDataType[]>([
|
||||
{ text: "시도", val: "0" },
|
||||
]);
|
||||
const [sigunData, setSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "시군구", val: "0" },
|
||||
]);
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "시군구", val: "0" },
|
||||
]);
|
||||
|
||||
const fetchSido = async () => {
|
||||
const res = await fetch("/cms-service/api/v1/code/cache/ADSTDSTRCD/Y");
|
||||
const json = await res.json();
|
||||
const sidoList = json
|
||||
.filter((item: any) => item.lwpoCd?.length === 2)
|
||||
.map((item: any) => ({ val: item.lwpoCd, text: item.lwpoCdNm }));
|
||||
const sigunList = json
|
||||
.filter((item: any) => item.lwpoCd?.length > 2)
|
||||
.map((item: any) => ({ val: item.lwpoCd, text: item.lwpoCdNm }));
|
||||
setSidoData([{ text: "시도", val: "0" }, ...sidoList]);
|
||||
setSigunData(sigunList);
|
||||
};
|
||||
|
||||
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]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSido();
|
||||
}, []);
|
||||
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
if (type === "ctpvCd") {
|
||||
setSidoValue(val);
|
||||
initializeSigunData(val);
|
||||
setSearchList([]);
|
||||
setFcltyNm("");
|
||||
} else if (type === "sggCd") {
|
||||
setSigunValue(val);
|
||||
setSearchList([]);
|
||||
setFcltyNm("");
|
||||
} else if (type === "facilityType") {
|
||||
setFacilityType(val);
|
||||
setSearchList([]);
|
||||
setFcltyNm("");
|
||||
}
|
||||
},
|
||||
[initializeSigunData]
|
||||
);
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!fcltyNm) return alert("시설명을 입력하세요.");
|
||||
|
||||
const sidoNm = sidoData.find((item) => item.val === sidoValue)?.text || "";
|
||||
const sggNm = sigunData.find((item) => item.val === sigunValue)?.text || "";
|
||||
|
||||
const type = facilityType;
|
||||
const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type];
|
||||
if (!baseUrlTemplate) return;
|
||||
|
||||
let url = baseUrlTemplate;
|
||||
const year = new Date().getFullYear().toString();
|
||||
url = url
|
||||
.replace("{year}", year)
|
||||
.replace("{sggCd}", sigunValue)
|
||||
.replace("{sidoNm}", encodeURIComponent(sidoNm))
|
||||
.replace("{sggNm}", encodeURIComponent(sggNm))
|
||||
.replace("{fcltyNm}", encodeURIComponent(fcltyNm));
|
||||
|
||||
let list: SelectDataType[] = [];
|
||||
|
||||
if (type === "tb_il_swage_prcs_fclt") {
|
||||
const cqlFilter = `ctpv_nm='${sidoNm}' and sgg_nm='${sggNm}' and swtrmt_fclt_nm like '%${fcltyNm}%'`;
|
||||
url += encodeURIComponent(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 procRes = await fetch("/reuse-service/reuseFc/pubFc");
|
||||
const procJson = await procRes.json();
|
||||
const procData = procJson.data;
|
||||
const procResult = procData
|
||||
.filter((code: string) => code.slice(0, 5) === sigunValue)
|
||||
.map((code: string) => {
|
||||
return code;
|
||||
});
|
||||
|
||||
//처리시설명으로 처리시설코드 조회
|
||||
let procUrl2 =
|
||||
"/operation-service/prcsFclt/ruseInfo/1/1000/A/{sggCd}/0/1/SW/{fcltyNm}";
|
||||
procUrl2 = procUrl2
|
||||
.replace("{sggCd}", sigunValue)
|
||||
.replace("{fcltyNm}", encodeURIComponent(fcltyNm));
|
||||
const procRes2 = await fetch(procUrl2, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(procResult),
|
||||
});
|
||||
const procJson2 = await procRes2.json();
|
||||
const procResult2 = (procJson2.data || []).map((item: any) => item.fid);
|
||||
|
||||
//최종 처리시설코드로 재이용시설조회
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(procResult2),
|
||||
});
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
if (list.length === 0) {
|
||||
alert("검색 결과가 없습니다.");
|
||||
setSearchList([]);
|
||||
setFcltyNm("");
|
||||
return;
|
||||
}
|
||||
|
||||
const newList = list.map((item: any) => {
|
||||
const code = item.val;
|
||||
const name = item.text;
|
||||
return { fnm: name, fid: code };
|
||||
});
|
||||
setSearchList(newList);
|
||||
};
|
||||
|
||||
const moveFocus = (feature: any) => {
|
||||
|
||||
// console.log('searchbar:moveFocus')
|
||||
// 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;
|
||||
// let featureId = data.fid;
|
||||
// const layerName = data.layerNm;
|
||||
// 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);
|
||||
// }
|
||||
};
|
||||
|
||||
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">
|
||||
{facilityTypes.length > 1 && (
|
||||
<SelectBox
|
||||
labelClass="w-[120px]"
|
||||
className="mr-5 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={[
|
||||
{ text: "시설 구분", val: "0" },
|
||||
...facilityTypes,
|
||||
]}
|
||||
onSelectValue={(val:any) =>
|
||||
onSelectValue(val.toString(), "facilityType")
|
||||
}
|
||||
value={facilityType}
|
||||
/>
|
||||
)}
|
||||
<SelectBox
|
||||
labelClass="w-[40px]"
|
||||
className="mr-5 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={sidoData}
|
||||
onSelectValue={(val:any) => onSelectValue(val.toString(), "ctpvCd")}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="mr-5 w-[150px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={filteredSigunData}
|
||||
onSelectValue={(val:any) => onSelectValue(val.toString(), "sggCd")}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<SearchInputText
|
||||
initValue={fcltyNm}
|
||||
setValue={(val: string = "") => {
|
||||
setFcltyNm(val);
|
||||
}}
|
||||
setRtnData={(selected: { fnm: string; fid: string }) => {
|
||||
// 선택된 항목이 있을 경우 onFeatureSelect 실행
|
||||
const type = facilityType;
|
||||
moveFocus({
|
||||
layerNm: type,
|
||||
...selected,
|
||||
});
|
||||
}}
|
||||
searchList={searchList}
|
||||
onEnter={handleSearch}
|
||||
className="mr-5 w-[150px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button
|
||||
color="primary"
|
||||
className="mx-2 mt-1"
|
||||
size="sm"
|
||||
onClick={handleSearch}
|
||||
>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user