diff --git a/apps/sn/app/(map-layout)/map/components/gisEdit/GisEditPipeSessionPopup.tsx b/apps/sn/app/(map-layout)/map/components/gisEdit/GisEditPipeSessionPopup.tsx index c41e61ab6..591077005 100644 --- a/apps/sn/app/(map-layout)/map/components/gisEdit/GisEditPipeSessionPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/gisEdit/GisEditPipeSessionPopup.tsx @@ -83,6 +83,9 @@ const toEditableText = (value: unknown) => { return text === "-" ? "" : text; }; +const isInvalidPipeLookupKey = (value: string) => + !value || value === "-" || /^NEW-/i.test(value) || /^(null|undefined)$/i.test(value); + const toDateInputValue = (value: unknown) => { const text = toEditableText(value).replaceAll(".", "").replaceAll("-", ""); if (!/^\d{8}$/.test(text)) return ""; @@ -181,8 +184,7 @@ export default function GisEditPipeSessionPopup({ const result = await getData("v1/fcltppl/mngInsts"); if (!isMounted) return; setMngInstOptions(Array.isArray(result) ? result : []); - } catch (error) { - console.error("관리기관 목록 조회 실패:", error); + } catch { if (isMounted) setMngInstOptions([]); } }; @@ -237,8 +239,7 @@ export default function GisEditPipeSessionPopup({ const fcltMngNo = String(change.featureRef?.fcltMngNo ?? "").trim(); const mngInstCd = String(change.featureRef?.mngInstCd ?? "").trim(); - if (!fcltMngNo || !mngInstCd) return; - if (/^NEW-/i.test(fcltMngNo) || /^NEW-/i.test(mngInstCd)) return; + if (isInvalidPipeLookupKey(fcltMngNo) || isInvalidPipeLookupKey(mngInstCd)) return; let isMounted = true; const fetchPipeInputInfo = async () => { @@ -253,8 +254,8 @@ export default function GisEditPipeSessionPopup({ ...pickFields(result, PIPE_VISIBLE_INPUT_FIELD_IDS), ...pickFields(change.basicInfo ?? {}, PIPE_VISIBLE_INPUT_FIELD_IDS), })); - } catch (error) { - console.error("GIS 편집 관로 입력정보 조회 실패:", error); + } catch { + // Optional pipe input details should not block the edit session. } }; diff --git a/apps/sn/app/(map-layout)/map/components/gisEdit/useGisEdit.ts b/apps/sn/app/(map-layout)/map/components/gisEdit/useGisEdit.ts index fd8e726c3..9da002d74 100644 --- a/apps/sn/app/(map-layout)/map/components/gisEdit/useGisEdit.ts +++ b/apps/sn/app/(map-layout)/map/components/gisEdit/useGisEdit.ts @@ -1088,7 +1088,7 @@ export const useGisEdit = (treeData: any[]) => { changeId: string, afterGeometry: Record | null, summary: string, - options?: { keepVertexEditActive?: boolean }, + options?: { keepVertexEditActive?: boolean; keepMoveToolActive?: boolean }, ) => { setLastSavedPayload(null); setChanges((previous) => @@ -1106,6 +1106,10 @@ export const useGisEdit = (treeData: any[]) => { if (options?.keepVertexEditActive) { setActiveTool("VERTEX_EDIT"); setPendingInteraction({ changeId, editType: "VERTEX_EDIT" }); + } else if (options?.keepMoveToolActive) { + // MOVE는 버튼 재클릭 전까지 도구를 유지하고, 다음 지도 클릭에서 새 대상을 선택하게 한다. + setActiveTool("MOVE"); + setPendingInteraction(null); } else { setActiveTool(null); setPendingInteraction(null); diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 30e7f8a4e..c7d3bd35c 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -6045,6 +6045,7 @@ const Map = () => { gisEdit.pendingInteraction!.changeId, writeGeometryObject(), `${layerName} ${objectId} 객체 위치를 지도에서 이동했습니다.`, + { keepMoveToolActive: true }, ); targetFeature.changed?.(); source.changed?.(); diff --git a/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/001-qa-SFR-012-003-하수관로편집-통합검증계획.md b/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/001-qa-SFR-012-003-하수관로편집-통합검증계획.md index 221bf0c55..c891fd880 100644 --- a/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/001-qa-SFR-012-003-하수관로편집-통합검증계획.md +++ b/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/001-qa-SFR-012-003-하수관로편집-통합검증계획.md @@ -93,6 +93,22 @@ - `MOVE`, `VERTEX_EDIT` 중 비하수관로 클릭 시 선택/줌 이동 없이 차단되고 상태문구만 바뀌는지 - `MOVE`, `VERTEX_EDIT`에서 하수관로 선택 즉시 편집 상세정보창이 열리는지 +### 3.10 2026-06-26 MOVE 도구 on/off 유지보수 검증 계획 + +- 구현 후 아래 순서로 검증한다. + 1. `GIS편집`을 켜고 `이동` 버튼을 한 번 클릭한다. + 2. 하수관로 A를 선택하고 drag 이동을 완료한다. + 3. 이동 완료 후 툴바의 `이동` 버튼 active 상태가 유지되는지 확인한다. + 4. `이동` 버튼을 다시 누르지 않은 상태에서 하수관로 B를 선택해 이동 interaction이 다시 준비되는지 확인한다. + 5. 하수관로 B 이동 완료 후에도 `이동` active 상태가 유지되는지 확인한다. + 6. `이동` 버튼을 다시 클릭하면 active 상태가 꺼지고, 이후 지도 클릭이 이동 변경분을 만들지 않는지 확인한다. +- 회귀 확인 + - `추가`, `꼭지점편집`, `삭제` 버튼은 기존처럼 하나만 active여야 한다. + - `스냅핑` on/off는 `이동` active 유지와 별개로 유지되어야 한다. + - `닫기`, `저장`, `세션 초기화`, `마지막 변경 취소` 후 이동 도구와 interaction이 남지 않아야 한다. +- 검증 분류 + - 툴바 active 상태와 상태 메시지는 자동 관측 가능하다. + - 실제 canvas drag와 관로별 연속 이동 체감은 수동 브라우저 검증을 기본으로 한다. ## 4. 현재 검증 범위 ### 4.1 진행 중 diff --git a/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/002-qa-SFR-012-003-하수관로편집-통합검증결과.md b/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/002-qa-SFR-012-003-하수관로편집-통합검증결과.md index e84b3098e..65907124b 100644 --- a/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/002-qa-SFR-012-003-하수관로편집-통합검증결과.md +++ b/apps/sn/docs/작업검증/SFR-012-003-하수관로편집/002-qa-SFR-012-003-하수관로편집-통합검증결과.md @@ -508,6 +508,26 @@ - 관로 중간을 잡고 `MOVE`했을 때도 가장 가까운 관로 꼭지점을 기준으로 결과가 일관적인지 - 이동 중 관로 상세 팝업이 열려도 `fetchPipeInputInfo` 500이 재발하지 않는지 +### 2.17 2026-06-26 MOVE 도구 on/off 요구사항 분석 결과 + +- 문서/코드 대조 결과 + - 툴바는 이미 같은 `이동` 버튼을 다시 누르면 `deactivateTool()`로 꺼지는 구조다. + - 현재 “한 번 이동하면 이동이 꺼지는” 동작은 사용자가 버튼을 다시 눌러서가 아니라 `MOVE` `translateend` 이후 `updateChangeGeometry()` 기본 경로가 `activeTool`을 `null`로 비우기 때문에 발생한다. + - `VERTEX_EDIT`는 `keepVertexEditActive` 옵션으로 연속 편집을 유지하는 특수 경로가 있으나, `MOVE`는 같은 유지 옵션이 없다. +- 이번 라운드 결과 + - `useGisEdit.updateChangeGeometry()`에 `keepMoveToolActive` 옵션을 추가했다. + - `MOVE` `translateend` 호출에서 `{ keepMoveToolActive: true }`를 넘겨 이동 완료 후에도 `activeTool="MOVE"`가 유지되도록 했다. + - `pendingInteraction`은 이동 완료 후 비워 다음 관로 클릭이 새 이동 대상으로 처리되게 했다. +- 정적 검증 + - 실행 명령: `node apps/sn/node_modules/typescript/bin/tsc -p apps/sn/tsconfig.json --noEmit --pretty false --ignoreDeprecations 5.0` + - 결과: 실패. 저장소 기존 오류(`components/common/index.ts`, `floodHistoryTypes.ts`, `page.tsx` 기존 null 가능성, html route, chart props implicit any`)에서 중단됐다. + - 이번 변경 파일인 `useGisEdit.ts` 관련 신규 오류는 출력되지 않았다. +- 후속 검증 필요 + - 관로 A 이동 후 이동 버튼 active 유지 여부 + - 관로 B 연속 이동 가능 여부 + - 이동 버튼 재클릭 off 여부 + - `추가`/`꼭지점편집`/`삭제`/`스냅핑`/`닫기`/`저장` 회귀 여부 + ## 3. 현재 검증 상태 ### 3.1 통과 @@ -597,3 +617,17 @@ - `008-qa-SFR-012-003-하수관로편집-실제지도조작검증결과.md` - `010-qa-SFR-012-003-하수관로편집-MOVE실드래그검증결과.md` - `012-qa-SFR-012-003-하수관로편집-VERTEX_EDIT실조작검증결과.md` +### 2.18 2026-06-26 관로 입력정보 조회 오류 방어 결과 + +- 확인된 오류 + - `GisEditPipeSessionPopup.useEffect.fetchPipeInputInfo`에서 `GET /api/v1/fcltppl/{fcltMngNo}/{mngInstCd}` 실패 응답이 `buildRequestError()`를 거쳐 `Error: ERROR`로 콘솔 출력됐다. +- 조치 결과 + - 프론트는 잘못된 상세 식별값 조회를 차단하고, 선택적 조회 실패를 콘솔 오류로 출력하지 않도록 수정했다. + - 백엔드는 관로 상세 보조속성/관리기관명 조회 실패를 빈 보조정보로 fallback하여 상세 API가 가능한 한 200 응답을 유지하도록 보강했다. +- 정적 검증 + - FE TypeScript 명령은 기존 저장소 오류로 실패했다. 이번 수정 파일인 `GisEditPipeSessionPopup.tsx` 관련 신규 오류는 출력되지 않았다. + - BE `./gradlew.bat compileJava`는 통과했다. + - FE/BE `git diff --check`는 CRLF 경고만 출력했고 whitespace 오류는 없었다. +- 남은 수동 검증 + - 브라우저에서 MOVE 후 상세 팝업 진입 시 Console Error 오버레이가 뜨지 않는지 확인이 필요하다. + - Network 탭에서 `fcltppl` 조회가 실패해도 팝업 입력 흐름이 유지되는지 확인이 필요하다. diff --git a/apps/sn/docs/작업계획/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-작업카드.md b/apps/sn/docs/작업계획/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-작업카드.md index 8098bcfd6..4c99acf00 100644 --- a/apps/sn/docs/작업계획/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-작업카드.md +++ b/apps/sn/docs/작업계획/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-작업카드.md @@ -16,6 +16,11 @@ - `COMMON_DOCS_ROOT/작업지시/SFR-012-003-하수관로편집/하수관망도편집_화면설계서_v1_1.png` ## 1-2. 최근 진행 갱신 +- 2026-06-26 + - `MOVE` 도구가 관로 1회 이동 후 자동 해제되는 현상을 요구사항 분석 범위로 확인했다. + - 현재 원인은 `useGisEdit.updateChangeGeometry()` 기본 경로가 geometry 갱신 뒤 `activeTool`과 `pendingInteraction`을 모두 비우는 구조이고, `page.tsx`의 `MOVE translateend`가 이 함수를 옵션 없이 호출하는 데 있다. + - 구현은 `MOVE` 완료 후 `activeTool="MOVE"`를 유지하고 `pendingInteraction`만 비워 다음 관로 클릭을 새 이동 대상으로 받는 방식으로 반영했다. + - 정적 타입체크는 실행했으나 저장소 기존 오류에서 실패했고, 실제 브라우저 연속 이동 검증은 다음 수동 검증 범위로 남긴다. - 2026-06-08 - `MOVE`와 `꼭지점편집` 사이에 `스냅핑` on/off 버튼을 추가하고, 편집 세션/상태 스냅샷에도 `ON/OFF`가 보이도록 연결했다. - 기존 임시 스냅 수집 방식으로는 원본 오수관에 안정적으로 붙지 않는다고 판단해, 1차 재구현 범위를 `ADD`, `VERTEX_EDIT`로 줄였다. @@ -236,3 +241,7 @@ - BE 공유용 API 요청 문서가 별도 정리됨 - 브라우저에서 정상/예외 시나리오를 확인할 수 있음 - 결과 보고 시 작업 목적, 수정 대상 파일, 영향 범위, 검증 방법, 롤백, 리스크를 정리할 수 있음 +- 2026-06-26 관로 입력정보 조회 오류 방어 + - MOVE on/off 기능 테스트 중 기존 관로 상세 입력정보 조회 실패가 `Error: ERROR` 콘솔 오류로 노출되는 문제를 확인했다. + - `GisEditPipeSessionPopup`은 선택적 입력정보 조회 실패를 편집 세션 중단/콘솔 오류로 처리하지 않고, 기존 WFS/세션 draft 값을 유지하도록 보강한다. + - 백엔드 `PplService`는 MS 보조 상세속성/관리기관명 조회 실패를 전체 API 500으로 전파하지 않고 fallback하도록 보강한다. diff --git a/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md b/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md index ed582e79e..a4c8ed08f 100644 --- a/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md +++ b/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md @@ -241,6 +241,31 @@ - 남긴 판단 - `MOVE` 체감이 여전히 어색하면 다음 단계는 grip point를 지도 위에 직접 시각화해 사용자가 “지금 어떤 점을 잡고 이동 중인지”를 보이게 해야 한다. +### 4.19 2026-06-26 MOVE 도구 on/off 유지보수 분석 + +- 사용자 요구사항 + - `GIS편집`에서 `이동`을 한 번 누르면 여러 관로를 연속으로 이동할 수 있어야 한다. + - `이동` 버튼을 다시 누르면 이동 모드가 꺼지는 on/off 방식이어야 한다. +- 현재 코드 확인 + - `GisEditToolbar`는 `activeTool === "MOVE"`일 때 `이동` 버튼을 active 색상으로 표시한다. + - `handleSelectGisEditTool()`은 같은 도구를 다시 누르면 `gisEdit.deactivateTool()`을 호출하므로 버튼 재클릭 off 구조는 이미 있다. + - 문제는 `MOVE` `Translate`의 `translateend`가 끝날 때 `gisEdit.updateChangeGeometry(...)`를 호출하고, 해당 훅 함수가 기본적으로 `setActiveTool(null)`과 `setPendingInteraction(null)`을 실행하는 데 있다. + - 따라서 한 번 이동이 끝나면 사용자가 버튼을 다시 누른 것이 아니어도 `activeTool`이 `null`로 바뀌어 이동 도구가 꺼진다. +- 다음 구현 방향 + - `MOVE` 완료 후에는 `activeTool`을 `"MOVE"`로 유지한다. + - 이동이 끝난 같은 preview feature에 interaction이 계속 묶이지 않도록 `pendingInteraction`은 비운다. + - 다음 관로 또는 preview feature를 클릭하면 기존 `registerFeatureSelection()` 흐름이 새 `pendingInteraction`을 만들고 이동 interaction을 다시 붙이게 한다. + - 기존 `VERTEX_EDIT`의 `keepVertexEditActive` 동작과 충돌하지 않도록 `MOVE` 전용 옵션 또는 범용 `keepActiveTool` 옵션으로 좁혀 구현한다. +- 이번 라운드 반영 + - `useGisEdit.updateChangeGeometry()` 옵션에 `keepMoveToolActive`를 추가했다. + - `MOVE` `translateend`에서 geometry를 세션에 반영한 뒤 `{ keepMoveToolActive: true }`를 넘겨 `이동` 도구 active 상태를 유지한다. + - 같은 preview feature에 interaction이 계속 묶이지 않도록 `pendingInteraction`은 `null`로 비운다. + - 버튼 재클릭 off는 기존 `handleSelectGisEditTool()`의 동일 도구 클릭 분기를 그대로 사용한다. +- 검증 + - `node apps/sn/node_modules/typescript/bin/tsc -p apps/sn/tsconfig.json --noEmit --pretty false --ignoreDeprecations 5.0` 실행 결과, 저장소 기존 TypeScript 오류에서 실패했다. + - 이번 변경 파일인 `useGisEdit.ts` 관련 신규 오류는 출력되지 않았고, `page.tsx`는 기존 `GisApp.Module.layer` null 가능성 오류가 남아 있다. + - 실제 브라우저에서 관로 A/B 연속 이동과 버튼 재클릭 off 검증은 아직 필요하다. + ## 5. 기능별 현재 상태 ### 5.1 `ADD` @@ -325,3 +350,14 @@ - 기대 효과 - 관로 클릭 직후 팝업 기본정보가 즉시 표시된다. - `/api/v1/fcltppl/null/null` 같은 불필요 호출을 줄여 상세 체감 지연과 백엔드 부하를 함께 낮춘다. +### 4.20 2026-06-26 관로 입력정보 조회 오류 방어 + +- 배경 + - MOVE on/off 동작 확인 중 `GisEditPipeSessionPopup`의 기존 관로 입력정보 조회에서 `Error: ERROR` 콘솔 오류가 발생했다. + - 해당 조회는 편집 세션 보조 입력정보를 채우기 위한 선택적 조회이므로, 실패해도 관로 이동/세션 편집 자체를 막지 않아야 한다. +- 반영 내용 + - `GisEditPipeSessionPopup`에 `isInvalidPipeLookupKey`를 추가해 빈 값, `-`, `NEW-*`, `null`, `undefined` 문자열이면 `GET /api/v1/fcltppl/{fcltMngNo}/{mngInstCd}` 호출을 건너뛰도록 했다. + - 관리기관 목록과 관로 입력정보 조회 실패 catch에서 `console.error`를 제거해 Next 개발 콘솔 오버레이가 뜨지 않도록 했다. + - 조회 실패 시 기존 WFS 속성/세션 draft 값을 유지하고 팝업 입력 흐름은 계속 진행한다. +- 백엔드 연동 보강 + - `PplService.findByFcltMngNo(fcltMngNo, mngInstCd)`에서 MS 관로 상세 보조속성 조회와 관리기관명 조회를 안전 래퍼로 감싸, 보조 조회 실패가 `GET /api/v1/fcltppl/...` 전체 500으로 전파되지 않게 했다.