Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev

This commit is contained in:
2026-08-31 23:17:20 +09:00
6 changed files with 81 additions and 0 deletions
+11
View File
@@ -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<any[]>([]); // 초기값을 객체 또는 null로 설정
// 통수위험 결과 행을 선택할 때 기존 체크를 유지하면서 강우 격자 레이어를 켜기 위한 요청이다.
const [checkedLayerRequest, setCheckedLayerRequest] = useState<{
requestKey: number;
layerNames: string[];
} | null>(null);
const [layerLoadError, setLayerLoadError] = useState<string | null>(null);
const gisMaps = useRef<any | null>(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 = () => {
/>
<Gis
treeData={treeData}
checkedLayerRequest={checkedLayerRequest}
isSearch={true}
gisMaps={gisMaps}
isMiniMap={false}
@@ -197,3 +197,18 @@
- 정렬 파라미터가 없는 기본 요청도 백엔드 `RAIN_BGNG_DT DESC`에 따라 2026-08-26 07:00, 2026-08-25 13:00, 10:00, 06:00 순으로 반환된다.
- 2026-08-25 경기 가평 지역 2건은 기존 키가 1개로 충돌했지만 강우시작시간·분석기준시간을 포함한 신규 키는 2개 모두 고유하다.
## 15. 2026-08-31 초단기예보격자망 자동 활성화 검증
| 검증 항목 | 결과 | 비고 |
|---|---|---|
| 통수위험 선택 메시지 연계 | 정적 통과 | 유효한 결과행·mapView 수신 후 체크 요청 생성 |
| 기존 체크 상태 보존 | 정적 통과 | 요청 레이어를 기존 배열에 추가하는 방식 |
| 동일 기능 재실행 | 정적 통과 | 행 선택마다 `requestKey` 증가 |
| 트리 가시성 | 정적 통과 | 대상 노드와 `강우관련` 조상 그룹 펼침 |
| 실제 레이어 ON | 정적 통과 | 공통 `checkSpecificNode`의 지도 반영 경로 재사용 |
| 공통 지도 패키지 빌드 | 통과 | `pnpm --filter @common/business_components run build` |
| `sn` TypeScript 검사 | 통과 | `tsc --noEmit --incremental false`, `ignoreDeprecations=5.0` 명령줄 보정 |
| 브라우저 수동 검증 | 대기 | 실제 결과 행 클릭 후 격자망 체크·WFS 표출 확인 필요 |
- Prettier 실행 파일은 현재 workspace에 설치되어 있지 않아 별도 포맷 검사는 수행하지 못했다.
@@ -208,3 +208,11 @@
- 요구사항정의서 93행은 합리식 `Q=CIA`와 Manning 공식을 요구하지만, 화면설계서 20쪽은 SWMM5 연계를 제시한다. 실제 산정 기능 설계 전에 적용 기준을 확정해야 한다.
- 2026-08-04 실제 API 연동 시 `map/page.tsx`, 세션 타입, popup 메시지 타입에 남아 있던 설정 편집·staged 관로 호환 코드도 제거해 결과 조회와 지도보기 계약만 유지했다. 상세 구현·검증은 `002-fe-SFR-011-007-통수위험분석-실API연동및검증.md`를 따른다.
## 14. 2026-08-31 행 선택 시 초단기예보격자망 자동 활성화
- 원본 추적 근거는 요구사항정의서 `SFR-011-007` 93행과 화면설계서 21쪽 `SN_MN010202`의 결과 행 선택 후 GIS 위치 이동 흐름이다.
- 사용자의 최신 지시에 따라 현재·과거분석이력 결과 행 선택으로 지도 결과를 표시할 때 시설물 레이어의 `강우관련 > 초단기예보격자망`을 자동으로 ON 한다.
- 기존 사용자가 켜둔 다른 시설물 레이어는 유지하고 초단기예보격자망만 추가 체크한다.
- 사용자가 격자망을 다시 끈 뒤 다른 결과 행을 선택해도 새 요청으로 다시 ON 한다.
- 백엔드 API·DB·통수위험 mapView 응답은 변경하지 않는다.
@@ -251,3 +251,12 @@
- 팝업 최초 조회와 현재·과거 탭 전환 시 정렬 파라미터를 비워 백엔드 기본 강우시작시간 최신순을 적용한다.
- 사용자가 다른 정렬 열을 선택하면 기존 서버 정렬을 유지하고, 탭을 전환하면 다시 강우시작시간 최신순으로 돌아간다.
- 동일 날짜·지역에 여러 강우이벤트가 존재해도 React 행 키가 겹치지 않도록 분석일·강우시작시간·분석기준시간·행정코드·지역번호를 조합한다.
## 18. 2026-08-31 통수위험 행 선택과 초단기예보격자망 연계
- 부모 지도에서 `WATER_RISK_SELECT_RESULT` 메시지를 정상 검증한 뒤 통수위험 결과·mapView 상태와 함께 초단기예보격자망 체크 요청을 생성한다.
- 공통 `Gis`에 `checkedLayerRequest`를 추가하고 `GisTree`가 레이어명을 기준으로 기존 체크 상태에 대상 노드를 합치는 방식으로 구현했다.
- 대상 노드와 조상인 `강우관련` 그룹을 펼쳐 자동 체크 결과를 사용자가 트리에서 바로 확인할 수 있게 했다.
- 요청마다 `requestKey`를 증가시켜 사용자가 격자망을 수동 해제한 후 다른 통수위험 행을 선택해도 다시 활성화한다.
- 지도 체크박스 DOM을 직접 조작하지 않고 기존 `checkSpecificNode` 경로를 사용해 실제 WFS 레이어와 체크 상태, `onCheckedLayersChange` 콜백을 함께 갱신한다.
- 공통 기능은 요청이 없는 기존 `Gis` 사용 화면에서 동작하지 않으므로 타 화면의 기본 체크 계약은 유지된다.
@@ -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);
@@ -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]);
// =====================
// 자동 애니메이션: 지정된 순서/간격으로 노드 체크 상태를 순차 변경
// =====================