Files
frontend/apps/wr/app/(map-layout)/map/mainMap/page.tsx
T
2025-08-10 22:20:22 +09:00

323 lines
11 KiB
TypeScript

"use client";
import Gis from "@common/business_components/ui/map/Gis";
import { useEffect, useRef, useState, useCallback } from "react";
import { mapService } from "../../service";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { SearchBar } from "@common/business_components/ui/map/searchBar/searchBar";
import { useWrStore } from "@/app/store";
import { getFeatureCd } from "@/components/lib/func/mapUtil";
import toast, { Toaster } from "react-hot-toast";
const Map = () => {
const { comMap } = useWrStore((state) => state);
const { isFeatured } = comMap;
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const gisMaps = useRef<any | null>(null);
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState<any>(null); // 선택된 피쳐 데이터
const [checkedLayers, setCheckedLayers] = useState<string[]>([]);
const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]);
const popupRef = useRef<Window | null>(null);
const popupDetailRef = useRef<Window | null>(null);
const fetchData = async () => {
try {
const response = await mapService.getLyrStngs('WR_NETWORK');
setTreeData(response); // API 응답 저장
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
};
// 팝업 닫기 핸들러를 useCallback으로 메모이제이션
// const handlePopupClose = useCallback(() => {
// setIsOpen(false);
// setFeatureData(null);
// }, []);
// 컴포넌트 마운트 시 1회 실행
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
const messageHandler = (event: MessageEvent) => {
if (event.data.type === "POPUP_CLOSED") {
if (event.data.target === "detail") {
setFeatureData(null);
setIsOpen(false);
setIsSearchModalOpen(true);
popupDetailRef.current = null;
} else {
popupRef.current = null;
}
}
};
window.addEventListener("message", messageHandler);
return () => {
window.removeEventListener("message", messageHandler);
};
}, []);
// 체크된 레이어 정보를 받는 핸들러
const handleCheckedLayersChange = (
checkedIds: string[],
checkedNodesData: any[]
) => {
setCheckedLayers(checkedIds);
setCheckedLayersData(checkedNodesData);
};
// SearchModal, GisContent 등에서 호출
const onFeatureFocus = () => {
console.log("onFeatureFocus");
};
const onFeatureSelect = (data: any) => {
const map = gisMaps.current;
if (!map) {
console.warn("지도가 초기화되지 않았습니다.");
return;
}
// 항상 같은 select 객체 사용
const select = GisApp.Module.select as any;
if (!select) return;
// 1. 기존 선택 초기화
select.resetSelectedFeature();
// 2. data가 OpenLayers Feature 인스턴스인지 확인
let foundFeature: any = null;
let featureId = data.fid;
const layerName = data.layerNm;
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 {
featureId = `${layerName}.${data.fid || data.id || data.ftrIdn}`;
}
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를 찾을 수 없습니다:");
}
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);
};
useEffect(() => {
if (isFeatured) {
onFeatureSelect(isFeatured);
}
}, [isFeatured]);
useEffect(() => {
const bc = new BroadcastChannel("mapDetailPopUp");
bc.onmessage = (event) => {
if (event.data.type === "SELECT_FEATURE") {
onFeatureSelect(event.data.feature);
closePopup();
}
};
}, []);
const getSelectedLayers = (isFeature: any) => {
const map = gisMaps.current;
if (!map) {
console.warn("지도가 초기화되지 않았습니다.");
return;
}
let result = null;
const layers = map
? map
.getLayers()
.getArray()
.filter((l: any) => l.getVisible())
: []; // gisMap이 null인지 확인 후 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: () => null };
layers.forEach((layer: any) => {
const source = layer.getSource();
if (
source &&
source.hasFeature &&
isFeature &&
typeof isFeature.getId === "function" &&
source.hasFeature(isFeature)
) {
featureLayer = layer; // 피처가 속한 레이어 찾기)
}
});
result = featureLayer.get("lyrNm");
return result;
};
const renderDetailView = useCallback(
(props: any) => {
const map = gisMaps.current;
if (!map) {
console.warn("지도가 초기화되지 않았습니다.");
return;
}
const isFeature = props?.featureData;
if( typeof isFeature.getProperties === "function" ){
if (
Object.keys(isFeature?.getProperties()).length === 0 &&
isFeature.constructor === Object
) {
const url = `/mapDetailPopUp?layerNm=${encodeURIComponent(props.seletectedLayerNm)}`;
const width = window.screen.availWidth;
const height = window.screen.availHeight;
const left = 0;
const top = 0;
const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=no`;
popupRef.current = window.open(url, "지도 레이어 팝업", popupOptions);
} else {
const layerNm = getSelectedLayers(isFeature);
const safeProps = isFeature.getProperties();
const featureCd = getFeatureCd(safeProps, String(layerNm));
const url = `/mapFeaturePopup?layerNm=${encodeURIComponent(String(layerNm))}&featureCd=${encodeURIComponent(featureCd)} `;
const width =
String(layerNm) === "하수재이용시설"
? Math.floor(window.innerWidth * 0.8)
: Math.floor(window.innerWidth * 0.4); // 화면 폭의 40%
const height =
String(layerNm) === "하수재이용시설"
? Math.floor(window.innerHeight - 150)
: Math.floor(window.innerHeight - 450); // 150px 여백 확보; // calc(100% - 70px)
const left = (window.screen.availWidth - width) / 2;
const top = (window.screen.availHeight - height) / 2;
const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
popupDetailRef.current = window.open(
url,
"지도 시설 상세정보 팝업",
popupOptions
);
}
}else{
console.log(' data확인 필요')
}
},
[checkedLayers, checkedLayersData, featureData] // 필요 시 의존성 넣기
);
const closePopup = () => {
if (popupRef.current && !popupRef.current.closed) {
popupRef.current.close();
popupRef.current = null;
setIsOpen(false);
setFeatureData(null);
}
};
const onOpenPopup = (data:any) => {
if(data){
const url = `/mapDetailPopUp?layerNm=${encodeURIComponent(data)}`;
const width = window.screen.availWidth;
const height = window.screen.availHeight;
const left = 0;
const top = 0;
const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=no`;
popupRef.current = window.open(url, "지도 레이어 팝업", popupOptions);
}else{
toast.error('시설 및 수요처가 선택되지 않았습니다.')
}
}
return (
<div className="bg-container">
<Gis
treeData={treeData}
isSearch={false}
gisMaps={gisMaps}
isMiniMap={false}
searchView={undefined}
detailView={renderDetailView}
onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
featureData={featureData}
setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
searchBar={
<SearchBar
gisMaps={gisMaps}
facilityTypes={[
{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" },
{ text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" },
{ text: "빗물이용시설", val: "tb_wr_rnwtr_utztn_fclt" },
{ text: "중수도시설", val: "tb_wr_rswtr_fclt" },
{ text: "온배수시설", val: "tb_wr_htdfl_fclt" },
{ text: "수요처", val: "tb_wr_ruse_fclt_dmd_co" },
{ text: "잠재수요처", val: "tb_wr_tmpr_dmd_co" },
]}
onFeatureFocus={onFeatureFocus}
/>
}
waterDepth={undefined}
onOpenPopup={(data:any) =>{
onOpenPopup(data)
}}
/>
<Toaster position="top-center" />
</div>
);
};
export default Map;