diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index cc923cf14..332b41e29 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -3617,6 +3617,7 @@ const Map = () => { await select.resetSelectedFeature?.(); const selectedFeatureIds = new Set(); + let floodLayerVisible = false; const selectLoadedGroupFeatures = (source: any) => { const features = source?.getFeatures?.() ?? []; features.forEach((feature: any) => { @@ -3644,6 +3645,7 @@ const Map = () => { FLOOD_DNGR_MAP_LAYER_TYPE ) return; + if (layer.getVisible?.() !== false) floodLayerVisible = true; const source = layer.getSource?.(); if (!source?.getFeatures) return; selectLoadedGroupFeatures(source); @@ -3651,6 +3653,13 @@ const Map = () => { selectLoadedGroupFeatures(source), ); }); + if (!floodLayerVisible) { + event.source?.postMessage?.( + { type: WINDOW_POPUP_MESSAGE_TYPE.MAP_SEARCH_FLOOD_LAYER_REQUIRED }, + event.origin, + ); + return; + } map.getView().fit(extent, { duration: 300, padding: [50, 50, 50, 50], diff --git a/apps/sn/docs/작업계획/SFR-011-008-침수예측/001-fe-SFR-011-008-침수예측현황-작업계획.md b/apps/sn/docs/작업계획/SFR-011-008-침수예측/001-fe-SFR-011-008-침수예측현황-작업계획.md index bb425c07b..3c30fb406 100644 --- a/apps/sn/docs/작업계획/SFR-011-008-침수예측/001-fe-SFR-011-008-침수예측현황-작업계획.md +++ b/apps/sn/docs/작업계획/SFR-011-008-침수예측/001-fe-SFR-011-008-침수예측현황-작업계획.md @@ -36,7 +36,7 @@ - 행 선택 시 기존 도시침수지도 group extent API를 호출한다. - 부모 창에 기존 `MAP_SEARCH_SHOW_FLOOD_GROUP` 메시지를 보내 범위 이동·레이어 표시를 요청한다. -- 도시침수지도에 없는 빈도는 자동 변환하지 않고 화면 안내를 표시한다. +- 도시침수지도 빈도는 30년 이하를 `030`, 100년 초과를 `100`으로 보정하고 그 외 미지원 빈도는 안내한다. ## 검증 diff --git a/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx b/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx index a1c782952..b7aa57bc5 100644 --- a/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx +++ b/apps/sn/src/features/fldngPredcPrst/ui/FldngPredcPrstPopupContent.tsx @@ -8,7 +8,7 @@ import { } from "@common/business_components"; import { Button } from "@common/components"; -import { getBlobData, getRawJson } from "@/components/lib/api"; +import { getBlobData, getData, getRawJson } from "@/components/lib/api"; import { fileDownload } from "@/components/lib/utils/fileDownload"; import LoadingOverlay from "@/components/ui/LoadingBar"; import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions"; @@ -57,18 +57,26 @@ const PAGE_SIZE = 10; const MAP_FREQUENCIES = new Set([30, 50, 80, 100]); const FREQUENCY_OPTIONS = [30, 50, 80, 100] as const; +/** 도시침수지도 DB의 3자리 빈도 코드로 변환한다. 30년 이하는 030, 100년 초과는 100으로 제한한다. */ +const toFloodMapFrequencyCode = (value: number) => { + if (!Number.isFinite(value) || value <= 0) return null; + const normalized = value <= 30 ? 30 : value > 100 ? 100 : value; + if (!MAP_FREQUENCIES.has(normalized)) return null; + return String(normalized).padStart(3, "0"); +}; + const HEADS: UiHeadProps[] = [ { id: "rowNo", title: "순번", widthPercent: 6 }, { id: "ctpvNm", title: "시도", widthPercent: 8 }, { id: "sggNm", title: "시군구", widthPercent: 9 }, { - id: "rainBeginningDateTimeDisplay", - title: "강우시각", + id: "analysisDateTimeDisplay", + title: "분석시각", widthPercent: 13, }, { - id: "analysisDateTimeDisplay", - title: "분석시각", + id: "rainBeginningDateTimeDisplay", + title: "강우시각", widthPercent: 13, }, { @@ -208,6 +216,21 @@ export default function FldngPredcPrstPopupContent() { // 개발 StrictMode에서도 최초 목록 API를 한 번만 호출하기 위한 ref다. const didInitialLoadRef = useRef(false); + // 부모 지도에서 도시침수지도 레이어가 비활성 상태임을 알려오면 팝업에서 안내한다. + useEffect(() => { + const handleMapMessage = (event: MessageEvent) => { + if (event.origin !== window.location.origin) return; + if ( + event.data?.type === + WINDOW_POPUP_MESSAGE_TYPE.MAP_SEARCH_FLOOD_LAYER_REQUIRED + ) { + alert("지도의 좌측 도시침수지도를 선택하여야 지도에 보입니다."); + } + }; + window.addEventListener("message", handleMapMessage); + return () => window.removeEventListener("message", handleMapMessage); + }, [alert]); + useEffect(() => { if (!ctpv.length) void fetchCtpv(); if (!sgg.length) void fetchSgg(); @@ -297,7 +320,8 @@ export default function FldngPredcPrstPopupContent() { /** 선택 행과 동일한 시군구·빈도의 도시침수지도 범위를 부모 지도에 전달한다. */ const handleRowClick = async (row: TableRow) => { const frequencyYear = Number(row.exceededFrequencyYear ?? 0); - if (!MAP_FREQUENCIES.has(frequencyYear)) { + const sbmrgFrqExpln = toFloodMapFrequencyCode(frequencyYear); + if (!sbmrgFrqExpln) { alert( `${frequencyYear || "해당"}년 빈도 도시침수지도는 제공되지 않습니다. (30·50·80·100년 빈도만 제공)`, ); @@ -315,12 +339,12 @@ export default function FldngPredcPrstPopupContent() { setSelectedRowId(buildRowId(row)); setLoading(true); try { - const sbmrgFrqExpln = `${frequencyYear}년`; const params = new URLSearchParams({ sggCd: row.sggCd, sbmrgFrqExpln, }); - const extentData = await getRawJson( + // 백엔드 공통 응답({ data: ... })의 data 영역을 반환하는 API 헬퍼를 사용한다. + const extentData = await getData( `/sewerage-service/api/v1/fldng/flood-dngr-map/group/extent?${params.toString()}`, ); const extent = [ @@ -345,7 +369,7 @@ export default function FldngPredcPrstPopupContent() { alert( error instanceof Error && error.message.includes("위치 정보") ? error.message - : "도시침수지도 이동에 실패했습니다.", + : "도시침수지도 이동에 실패하였습니다.", ); } finally { setLoading(false); @@ -471,8 +495,8 @@ export default function FldngPredcPrstPopupContent() { - {renderDateTimeRange("강우시각", "rainBgngFrom", "rainBgngTo")} {renderDateTimeRange("분석시각", "analysisFrom", "analysisTo")} + {renderDateTimeRange("강우시각", "rainBgngFrom", "rainBgngTo")}