diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index bdda554da..94603ab30 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -256,6 +256,7 @@ const GIS_EDIT_VERTEX_LAYER_ID = "SN_GIS_EDIT_VERTEX_HANDLES"; const WATER_RISK_AREA_LAYER_ID = "SN_WATER_RISK_AREA"; const WATER_RISK_PIPE_LAYER_ID = "SN_WATER_RISK_PIPE"; const WATER_RISK_MANHOLE_LAYER_ID = "SN_WATER_RISK_MANHOLE"; +const SHORT_TERM_FORECAST_GRID_LAYER_NAME = "초단기예보격자망"; const FLOOD_HISTORY_SELECTION_PADDING: [number, number, number, number] = [ 100, 380, 120, 120, ]; @@ -2981,6 +2982,11 @@ const Map = () => { const sinkholeScope = useMemo(() => buildSinkholeScope(userInfo), [userInfo]); const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 + // 통수위험 결과 행을 선택할 때 기존 체크를 유지하면서 강우 격자 레이어를 켜기 위한 요청이다. + const [checkedLayerRequest, setCheckedLayerRequest] = useState<{ + requestKey: number; + layerNames: string[]; + } | null>(null); const [layerLoadError, setLayerLoadError] = useState(null); const gisMaps = useRef(null); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 @@ -4181,6 +4187,10 @@ const Map = () => { ...current, selectedAreaId: areaId, })); + setCheckedLayerRequest((current) => ({ + requestKey: (current?.requestKey ?? 0) + 1, + layerNames: [SHORT_TERM_FORECAST_GRID_LAYER_NAME], + })); setIsOpen(false); setFeatureData(null); return; @@ -9043,6 +9053,7 @@ const Map = () => { /> 초단기예보격자망`을 자동으로 ON 한다. +- 기존 사용자가 켜둔 다른 시설물 레이어는 유지하고 초단기예보격자망만 추가 체크한다. +- 사용자가 격자망을 다시 끈 뒤 다른 결과 행을 선택해도 새 요청으로 다시 ON 한다. +- 백엔드 API·DB·통수위험 mapView 응답은 변경하지 않는다. diff --git a/apps/sn/docs/작업내용/SFR-011-007-통수위험분석/001-fe-SFR-011-007-통수위험분석-통합구현기록.md b/apps/sn/docs/작업내용/SFR-011-007-통수위험분석/001-fe-SFR-011-007-통수위험분석-통합구현기록.md index 9dc07998d..eb32a40df 100644 --- a/apps/sn/docs/작업내용/SFR-011-007-통수위험분석/001-fe-SFR-011-007-통수위험분석-통합구현기록.md +++ b/apps/sn/docs/작업내용/SFR-011-007-통수위험분석/001-fe-SFR-011-007-통수위험분석-통합구현기록.md @@ -251,3 +251,12 @@ - 팝업 최초 조회와 현재·과거 탭 전환 시 정렬 파라미터를 비워 백엔드 기본 강우시작시간 최신순을 적용한다. - 사용자가 다른 정렬 열을 선택하면 기존 서버 정렬을 유지하고, 탭을 전환하면 다시 강우시작시간 최신순으로 돌아간다. - 동일 날짜·지역에 여러 강우이벤트가 존재해도 React 행 키가 겹치지 않도록 분석일·강우시작시간·분석기준시간·행정코드·지역번호를 조합한다. + +## 18. 2026-08-31 통수위험 행 선택과 초단기예보격자망 연계 + +- 부모 지도에서 `WATER_RISK_SELECT_RESULT` 메시지를 정상 검증한 뒤 통수위험 결과·mapView 상태와 함께 초단기예보격자망 체크 요청을 생성한다. +- 공통 `Gis`에 `checkedLayerRequest`를 추가하고 `GisTree`가 레이어명을 기준으로 기존 체크 상태에 대상 노드를 합치는 방식으로 구현했다. +- 대상 노드와 조상인 `강우관련` 그룹을 펼쳐 자동 체크 결과를 사용자가 트리에서 바로 확인할 수 있게 했다. +- 요청마다 `requestKey`를 증가시켜 사용자가 격자망을 수동 해제한 후 다른 통수위험 행을 선택해도 다시 활성화한다. +- 지도 체크박스 DOM을 직접 조작하지 않고 기존 `checkSpecificNode` 경로를 사용해 실제 WFS 레이어와 체크 상태, `onCheckedLayersChange` 콜백을 함께 갱신한다. +- 공통 기능은 요청이 없는 기존 `Gis` 사용 화면에서 동작하지 않으므로 타 화면의 기본 체크 계약은 유지된다. diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index a63afb048..81d5c4c13 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -54,6 +54,8 @@ function Gis({ sinkholeLegendIconVisible = false, setIsSearchModalOpen, onCheckedLayersChange, + // 외부 업무 흐름에서 특정 시설물 레이어를 기존 체크 상태에 추가하도록 요청한다. + checkedLayerRequest = null, searchBar: SearchBar, waterDepth: WaterDepth = null, layerSettingView: LayerSettingView = null, @@ -309,6 +311,7 @@ function Gis({ visible={tabVisible} setVisible={setTabVisible} onCheckedChange={handleCheckedLayersChange} + checkedLayerRequest={checkedLayerRequest} onLayerIconClick={(layerNm, node) => { layerNmRef.current = layerNm; setLayerNm(layerNm); diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 16636c596..9bfac1400 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -19,6 +19,7 @@ import GisApp from "./lib/gis.core"; * * Props 요약 * - checkedNodes, setCheckedNodes: (선택) 외부 관리 체크 상태 (현재 내부 state 사용) + * - checkedLayerRequest: (선택) 기존 체크 상태를 유지하면서 지정 레이어를 추가로 체크하는 요청 * - visible, setVisible: 사이드 패널 가시성 on/off * - onCheckedChange(checkedIds, checkedNodesData): 체크 변경 시 콜백 * - onLayerIconClick(nodeNm): 각 노드의 미리보기/아이콘 클릭 시 콜백 @@ -37,6 +38,7 @@ import GisApp from "./lib/gis.core"; export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, + checkedLayerRequest = null, visible, setVisible, onCheckedChange, @@ -71,6 +73,8 @@ export default React.memo(function GisTree({ const targetNodesRef = useRef([]); const isPausedRef = useRef(false); const timeoutIdRef = useRef(null); + // 동일한 외부 체크 요청이 리렌더링마다 반복 적용되지 않도록 마지막 요청 키를 보관한다. + const lastCheckedLayerRequestKeyRef = useRef(null); // 체크 후 부모 설정영역까지 다시 렌더되어도 사용자가 보던 트리 위치를 유지한다. const layerPanelScrollRef = useRef(null); const layerPanelScrollTopRef = useRef(0); @@ -322,6 +326,37 @@ export default React.memo(function GisTree({ [nodes] ); + /** + * 업무 화면이 요청한 레이어를 기존 사용자 체크 상태에 추가하고 조상 그룹을 펼친다. + * 레이어명은 공백 차이를 무시하며, 실제 지도 반영과 onCheckedChange는 수동 체크와 같은 경로를 사용한다. + */ + useEffect(() => { + const requestKey = checkedLayerRequest?.requestKey; + if (requestKey == null || nodes.length === 0) return; + if (lastCheckedLayerRequestKeyRef.current === requestKey) return; + + const normalizeLayerName = (value) => String(value ?? "") + .replace(/\s+/g, "") + .trim(); + const requestedLayerNames = new Set( + (checkedLayerRequest?.layerNames ?? []) + .map(normalizeLayerName) + .filter(Boolean) + ); + const targetNodes = nodes.filter((node) => requestedLayerNames.has( + normalizeLayerName(node?.data?.lyrNm ?? node?.text) + )); + if (targetNodes.length === 0) return; + + setExpanded((current) => Array.from(new Set([ + ...current, + ...targetNodes.map((node) => node.id), + ...targetNodes.flatMap((node) => getAncestors(node.id, nodes)), + ]))); + targetNodes.forEach((node) => checkSpecificNode(node.id, true)); + lastCheckedLayerRequestKeyRef.current = requestKey; + }, [checkedLayerRequest, checkSpecificNode, nodes]); + // ===================== // 자동 애니메이션: 지정된 순서/간격으로 노드 체크 상태를 순차 변경 // =====================