Files
frontend/apps/dm_new/app/(popup-layout)/map/popup/page.tsx
T
2026-04-02 11:31:43 +09:00

353 lines
12 KiB
TypeScript

"use client";
import Gis from "@common/business_components/ui/map/Gis";
import { useEffect, useRef, useState } from "react";
import { mapService } from "../service";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { SearchBar } from "@/components/ui/map/searchBar/searchBar";
import SelectedFcltList from "./selectedFcltList";
import type { SubsInfo } from "@/app/store/map/type";
const Map = () => {
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(null); // 선택된 피쳐 데이터
const [selectDataPk, setSelectDataPk] = useState<Record<string, any>>({});
const [selectedFclts, setSelectedFclts] = useState<any[]>([]);
//적용 버튼 클릭 이벤트
const onApply = () => {
if (selectedFclts.length > 3) {
alert("재이용시설 후보지는 최대 3개까지만 선택할 수 있습니다.");
return;
}
const subsInfoList: SubsInfo[] = selectedFclts.map((fclt) => ({
ruseUtztnFcltNm: fclt.ruseUtztnFcltNm,
ruseUtztnFcltCd: fclt.pbnstRuseFcltId,
}));
if (!window.opener || typeof window.opener.postMessage !== "function") {
alert("부모 창을 찾을 수 없습니다. 팝업이 'noopener'로 열렸거나, 보안 정책으로 차단되었을 수 있어요.");
return;
}
window.opener.postMessage(
{
type: "SET_SUBS_INFO",
payload: subsInfoList, // 배열로 전송 (최대 3개)
},
"*"
);
window.close();
};
//트리 레이어 데이터 조회
const fetchData = async () => {
try {
const response = await mapService.getLyrStngs();
setTreeData(response); // API 응답 저장
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
};
//하수처리시설코드로 재이용시설 목록 조회
const reuseData = async (ruseFcltPk: any) => {
if (!ruseFcltPk) {
console.warn("[reuseData] ruseFcltPk가 없습니다.");
return;
}
try {
console.log("[reuseData] API 호출 - ruseFcltPk:", ruseFcltPk);
const response = await mapService.getPbnstRuseFclts(ruseFcltPk);
console.log("[reuseData] API 응답 전체:", response);
// 응답이 배열이면 바로 사용, 객체면 .data 추출
const data = Array.isArray(response)
? response
: Array.isArray(response?.data)
? response.data
: response?.data
? [response.data]
: [];
console.log("[reuseData] 파싱된 data:", data);
setSelectedFclts((prev) => {
const newItems = data.filter(
(item: any) =>
item &&
Object.keys(item).length > 0 &&
!prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId)
);
console.log("[reuseData] 추가될 newItems:", newItems);
return [...prev, ...newItems];
});
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
};
// 컴포넌트 마운트 시 1회 실행
useEffect(() => {
fetchData();
}, []);
// 부모창에 초기 선택값 요청 (팝업 열릴 때 기존 선택 목록 받아오기)
useEffect(() => {
const origin = window.location.origin;
if (window.opener) {
window.opener.postMessage({ type: "REQUEST_INIT_SUBS_INFO" }, origin);
}
}, []);
// 부모창에서 초기 선택값 수신
useEffect(() => {
const origin = window.location.origin;
const handler = (event: MessageEvent) => {
if (event.origin !== origin) return;
const data = event.data;
if (!data || typeof data !== "object") return;
if (data.type === "INIT_SUBS_INFO") {
const list = Array.isArray(data.payload) ? data.payload : [];
setSelectedFclts(
list.map((x) => ({
ruseUtztnFcltNm: x.ruseUtztnFcltNm ?? "",
pbnstRuseFcltId: x.ruseUtztnFcltCd ?? "",
}))
);
}
};
window.addEventListener("message", handler);
return () => window.removeEventListener("message", handler);
}, []);
// 레이어 생성 후 초기화 처리
const onCheckedLayersChange = () => {
//수요처 ID
const urlParams = new URLSearchParams(window.location.search);
const dmdInfoId = urlParams.get("dmdInfoId");
console.log("[하이라이트] dmdInfoId:", dmdInfoId);
//수요처 위치로 화면 이동
const maps = gisMaps.current;
const layer = GisApp.Module.layer as any;
const layerNm = "tb_wr_ruse_fclt_dmd_co";
const featureId = `${layerNm}.${dmdInfoId}`;
let foundFeature: any = null;
// 현재 로드된 레이어 목록 출력
const allLayerNames = maps
.getLayers()
.getArray()
.map((l: any) => l.get("lyrTyNm") || l.get("name") || "(unnamed)");
console.log("[하이라이트] 전체 레이어 목록:", allLayerNames);
console.log("[하이라이트] 탐색 대상 레이어:", layerNm);
console.log("[하이라이트] 탐색 대상 featureId:", featureId);
maps
.getLayers()
.getArray()
.forEach((layer: any) => {
if (layer.get("lyrTyNm") == layerNm) {
console.log("[하이라이트] 레이어 발견:", layerNm);
const source = layer.getSource?.();
if (source?.getFeatureById) {
const feature = source.getFeatureById(String(featureId).trim());
console.log("[하이라이트] feature 탐색결과:", feature);
if (feature) {
foundFeature = feature;
return;
}
// feature 못 찾을 때 소스 내 전체 feature ID 출력
if (source?.getFeatures) {
const allIds = source.getFeatures().map((f: any) => f.getId());
console.log("[하이라이트] 소스 내 전체 feature ID:", allIds);
}
}
}
});
if (!foundFeature) {
console.warn("[하이라이트] feature를 찾지 못했습니다. 위 레이어 목록/ID를 확인하세요.");
}
if (foundFeature) {
const geometry = foundFeature.getGeometry();
if (geometry) {
const extent = geometry.getExtent();
if (geometry.getType() === "Point") {
maps.getView().setCenter(geometry.getCoordinates());
maps.getView().setZoom(11);
} else {
maps.getView().fit(extent, {
duration: 300,
padding: [30, 30, 30, 30],
maxZoom: 16,
});
}
}
layer.highLightFeature(
[foundFeature],
{
id: GisApp.hlLayerId,
zIndex: 10,
opacity: 1,
selectable: true,
},
{ width: 30 }
);
} else {
console.warn("수요처의 좌표정보를 찾을 수 없습니다. dmdInfoId:", dmdInfoId);
}
const formatNumber = (val: any) => {
const num = Number(val);
return isNaN(num) ? "-" : num.toLocaleString();
};
// 재이용시설 오버레이
maps
.getLayers()
.getArray()
.forEach((layer: any) => {
if (layer.get("lyrTyNm") === "tb_wr_bdst_ruse_fclt") {
const source = layer.getSource?.();
if (source?.getFeatures) {
const features = source.getFeatures();
features.forEach((feature: any) => {
// 필요한 고유 ID 또는 속성 가져오기 (예: ftrIdn 또는 feature.getId())
const featureId =
feature.getId() || feature.get("ftrIdn") || Math.random();
// HTML 또는 React 오버레이 컴포넌트 구성
const overlayContent = `
<div class="rounded-md border border-gray-300 bg-white shadow-md text-sm w-[120px]">
<!-- 헤더 -->
<div class="bg-cyan-700 text-white px-3 py-2 text-[11px] font-semibold text-center rounded-t-md">
${feature.get("swtrmt_fclt_nm") || "-"}
</div>
<!-- 테이블 -->
<table class="w-full text-sm text-gray-800 border-t border-gray-200">
<tbody>
<tr class="border-b border-gray-200">
<th class="bg-gray-100 text-gray-600 font-semibold text-[10px] text-left">용량(m³/일)</th>
<td class="text-right text-[10px] font-bold">${formatNumber(feature.get("fclt_cpct"))}</td>
</tr>
</tbody>
</table>
</div>
`;
feature.set("html", overlayContent);
feature.set("overLayId", `overLay.${featureId}`);
});
}
}
});
};
// 검색해서 재이용시설 선택하면 해당 시설로 화면 이동 처리
useEffect(() => {
console.warn(selectDataPk);
if (!selectDataPk || Object.keys(selectDataPk).length === 0) return;
const maps = gisMaps.current;
let foundFeature: any = null;
const layerNm = "tb_wr_bdst_ruse_fclt";
const featureId = `${layerNm}.${selectDataPk.bscFctCd}`;
maps
.getLayers()
.getArray()
.forEach((layer: any) => {
if (layer.get("lyrTyNm") == layerNm) {
const source = layer.getSource?.();
if (source?.getFeatureById) {
const feature = source.getFeatureById(String(featureId).trim());
if (feature) {
foundFeature = feature;
return;
}
}
}
});
if (foundFeature) {
const geometry = foundFeature.getGeometry();
if (geometry) {
const extent = geometry.getExtent();
maps.getView().fit(extent, {
duration: 300,
padding: [50, 50, 50, 50],
maxZoom: 16,
constrainResolution: true,
nearest: true,
});
}
} else {
console.warn("OpenLayers Feature를 찾을 수 없습니다:", selectDataPk);
}
}, [selectDataPk]);
// 재이용시설 클릭 시 선택 시설목록에 추가
const onFeatureSelect = (data: any) => {
// feature의 모든 속성 출력 (어떤 키가 있는지 확인)
const properties = data.getProperties?.() ?? {};
console.log("[onFeatureSelect] feature 속성 전체:", properties);
// 처리시설코드
const swtrmtFcltCd = data.get("swtrmt_fclt_cd");
console.log("[onFeatureSelect] swtrmt_fclt_cd:", swtrmtFcltCd);
reuseData(swtrmtFcltCd);
};
return (
<>
<div className="bg-container">
<Gis
showZoomLevel={true}
height='h-screen'
treeData={treeData}
isSearch={false}
gisMaps={gisMaps}
isMiniMap={false}
searchView={undefined}
detailView={undefined}
onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
featureData={featureData}
setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={onCheckedLayersChange}
imgVisble={false}
searchBar={
<SearchBar
setHasuPk={(hasuPk: any) => {
if (hasuPk) setSelectDataPk(hasuPk);
}}
onApply={onApply}
/>
}
waterDepth={undefined}
/>
</div>
<SelectedFcltList
data={selectedFclts}
onRemove={(updatedList) => setSelectedFclts(updatedList)}
/>
</>
);
};
export default Map;