Compare commits

...
6 Commits
13 changed files with 101 additions and 16 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
#NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&layer=korean_map&style=korean&tilematrixset=EPSG:5179&TileMatrix={z}&TileCol={x}&TileRow={y}
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y}
SERVER_API_URL = http://dev.onasys.co.kr:8000
SERVER_API_URL = https://apigateway.onasys.co.kr
SEWERAGE_API_URL = http://localhost:8883
# SEWERAGE_API_URL = http://con.mind-one.co.kr:11083
DC_URL = https://apigateway.apps.rsonesoft.com
+1 -1
View File
@@ -6,7 +6,7 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11302
NEXT_PUBLIC_SUB_DOMAIN = http://con.mind-one.co.kr:13000
SERVER_API_URL = http://dev.onasys.co.kr:8000
SERVER_API_URL = https://apigateway.onasys.co.kr
NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
@@ -66,7 +66,8 @@ export const normalizeAgedPipeStatusByCtpvFilters = (
filters: Partial<AgedPipeStatusByCtpvFilters>,
): AgedPipeStatusByCtpvFilters => {
const currentYear = new Date().getFullYear();
let startYear = Math.trunc(Number(filters.startYear ?? currentYear - 2));
// 최초 진입·초기화는 당해연도 1년 조회로 시작하며 직접 선택한 범위는 유지한다.
let startYear = Math.trunc(Number(filters.startYear ?? currentYear));
let endYear = Math.trunc(Number(filters.endYear ?? currentYear));
let elapsedYears = Math.trunc(
Number(
@@ -74,7 +75,7 @@ export const normalizeAgedPipeStatusByCtpvFilters = (
),
);
if (!Number.isFinite(startYear)) startYear = currentYear - 2;
if (!Number.isFinite(startYear)) startYear = currentYear;
if (!Number.isFinite(endYear)) endYear = currentYear;
if (
!Number.isFinite(elapsedYears) ||
@@ -226,7 +226,12 @@ export default function FloodDngrMapPopupPage() {
const response = await getFloodDngrMapJson(
`/sewerage-service/api/v1/fldng/floodDngrMap/group/extent?${params.toString()}`,
);
const payload = response as FloodDngrMapExtent;
const payload = response as FloodDngrMapExtent | null | undefined;
// 성공 응답의 null·생략된 data는 위치 부재이다. 좌표 접근과 지도 메시지를 중단한다.
if (payload == null) {
setErrorMessage("위치 정보가 없습니다");
return;
}
const extent = [
payload?.minX,
payload?.minY,
@@ -237,7 +242,7 @@ export default function FloodDngrMapPopupPage() {
extent.length !== 4 ||
extent.some((value) => !Number.isFinite(value))
) {
throw new Error("해당 도시침수지도 피처의 위치 정보가 없습니다.");
throw new Error("도시침수지도 위치 응답의 좌표가 올바르지 않습니다.");
}
window.opener?.postMessage(
{
@@ -253,11 +258,9 @@ export default function FloodDngrMapPopupPage() {
} catch (error) {
console.error("도시침수지도 위치 이동 중 오류:", error);
const message =
error instanceof Error && error.message.includes("404")
? "해당 도시침수지도 데이터에 위치 정보가 없어 이동할 수 없습니다."
: error instanceof Error
? error.message
: "도시침수지도 위치 이동 중 오류가 발생했습니다.";
error instanceof Error
? error.message
: "도시침수지도 위치 이동 중 오류가 발생했습니다.";
setErrorMessage(message);
} finally {
setIsLoading(false);
@@ -25,6 +25,8 @@ export const EXMN_PROC_SEARCH_EVENT = "SN_INFTWT_PROC_SEARCH";
// 지반침하 지도 포인트와 같은 파란 계열의 기존 레이어 트리 심볼 자산을 재사용한다.
export const SINKHOLE_TREE_ICON_NAME = "SN_SINKHOLE_SPOT_BLUE.png";
// EPSG:5179의 남한·도서 주변 여유 범위 [minX, minY, maxX, maxY].
// 화면 비율 때문에 전체 범위가 잘리지 않도록 Gis의 viewShowFullExtent와 함께 사용한다.
export const SOUTH_KOREA_VIEW_EXTENT: [number, number, number, number] = [
600000, 1300000, 1500000, 2500000,
];
@@ -3335,6 +3335,7 @@ const MapPageClient = () => {
defaultZoom={7.5}
defaultLonLat={[129, 36.3]}
viewExtent={SOUTH_KOREA_VIEW_EXTENT}
viewShowFullExtent={true}
/>
{isAgedPipeListMinimized && (
<div className="fixed left-[480px] top-[68px] z-[90] flex min-w-[250px] items-center gap-3 rounded-sm border border-red-200 bg-background px-3 py-2 shadow-[0_8px_22px_rgba(15,23,42,0.22)] backdrop-blur">
@@ -15,3 +15,14 @@
- 순번은 전체 건수 기준 역순으로 계산해 로컬 3002에서 1페이지 18~9, 2페이지 8~1 표출을 확인했다.
- 원본 근거는 요구사항정의서 `SFR-011-001`, 화면설계서 8~9번 `SN_MN010102`이며 순번·제목 건수는 원본 필수항목이 아닌 공통 그리드 표시 규칙이다.
- API, 조회조건, 연도 그룹 헤더와 Excel 다운로드 형식은 변경하지 않았다.
## 4. 2026-10-08 기본 검색기간 단년 변경
- 사용자 요청으로 디지털하수관로의 시도별 노후관로현황 기본 검색기간을 최근 3년에서 당해연도 1년으로 변경했다. 최초 진입·초기화·잘못된 연도값의 기본 처리가 동일한 정규화 함수를 사용한다.
- 원본 근거: 요구사항정의서 `3. (기능)요구사항정의 및 추적표` 87행 `SFR-011-001`, 화면설계서 슬라이드 9 `SN_MN010102`. 화면설계서의 시작·종료 연도 예시는 모두 2026년이다.
- 수정: `aged-pipe-status-by-ctpv-utils.ts`의 기본 시작연도와 비정상 시작연도 fallback만 당해연도로 변경. 사용자가 선택한 최대 3년 범위, 경과년도 기본 30년, API 계약·DB·지도 기능·Excel 적용조건 처리는 유지한다.
- 로컬 3002 실제 브라우저: 최초 진입 `2026~2026` → 2026년 단일 그룹/10행, 직접 `2024~2026` 선택 후 조회 → 3개 연도 그룹, 초기화 → `2026~2026` 복귀 확인. 해당 API 요청 모두 HTTP 200.
- 기본값·초기화 입력·비정상 연도·명시적 3년 유지·4년 제한 등 6개 확인과 `git diff --check` 통과. 사용자 개발 서버가 실행 중이므로 빌드/서버 재시작은 하지 않았다. 일회성 테스트 파일은 생성하지 않았다.
- 시도 집계 목록의 기존 클라이언트 페이징 예외(18개 고정 집계)는 변경하지 않았다. 지도 호출 영향 없음. Excel은 동일한 마지막 적용조건을 사용하는 기존 코드 유지, 실제 다운로드 재검증은 이번 범위에서 수행하지 않았다.
- 배포는 사용자가 수행한다. 원복은 기본 시작연도 및 fallback을 `currentYear - 2`로 되돌린다. 단년 조건의 성능 통과 여부는 배포 후 재시험 전까지 미확정이다.
- 후속 결과서: 시스템명은 `디지털하수관로`, 표시 메뉴명에는 `SN01` 등 시험 ID를 붙이지 않는다. 사용자가 수정한 HWP를 기준으로 추가 편집하고, 실제 캡처는 마우스 커서가 보이지 않게 준비한다. 기존 측정 원본은 변경하지 않는다.
@@ -0,0 +1,31 @@
# 관망도 남한 전체 축소 표시
- 작업일: 2026-10-07
- 목적: 최소 줌을 낮춰도 지정 영역 제한 때문에 남한 전체까지 축소되지 않는 현상 보완.
- 근거: 사용자 요청. 화면설계서 7번 SN_MN010101의 하수관망도, 요구사항정의서 기능 추적표 97~100행(PR-SN-030)을 확인했다. 해당 원본에는 정확한 줌/좌표 제한값이 없으므로 이번 값은 원본 요구사항으로 주장하지 않는다.
## 수정
| 파일 | 내용 |
| --- | --- |
| app/(map-layout)/map/model/map-constants.ts | SOUTH_KOREA_VIEW_EXTENT의 좌표계·용도 주석 추가. 기존 좌표 유지 |
| app/(map-layout)/map/ui/map-page-client.tsx | Gis에 viewShowFullExtent=true 전달 |
| packages/common/business_components/src/ui/map/Gis.js | 선택 속성을 View.showFullExtent에 연결. 기본 false로 다른 앱의 기존 동작 유지 |
범위는 EPSG:5179 `[600000, 1300000, 1500000, 2500000]`이다. 이 범위에는 남한이 포함되어 있으나, OpenLayers의 기본 extent 제약은 화면 가로·세로 중 어느 방향도 범위 밖으로 나가지 못하도록 축소를 제한한다. showFullExtent는 지정 영역 전체가 보이도록 한 축의 화면이 범위를 넘는 축소를 허용한다. 영역을 무제한으로 확장하거나 minZoom을 무시하는 설정이 아니다.
## 검증
- 사용자 SN 개발서버 3002(PID 69796)를 유지하고 HMR로 검증했다. 서버 재시작과 Next build는 수행하지 않았다.
- 기존 Chrome 탭 연결은 시간 초과하여 별도 인앱 브라우저 검증 탭을 사용했다.
- localhost:3002에서 승인된 테스트 계정으로 로그인 후 관망도 진입.
- 1280×720 브라우저에서 축소 버튼으로 줌 7.5 → 6.5 → 6.0 이동 확인.
- 줌 6.0에서 남한 본토, 제주도와 울릉도·독도가 함께 보이는 화면을 확인했다. 레이어 패널과 분석도구 패널을 유지한 상태다.
- git diff --check 통과. 기존 배경지도 타일의 모든 외부 네트워크 요청, 전체 업무 API 및 다른 화면 회귀까지 검증한 것은 아니다.
## 영향 및 롤백
- API/DB/레이어 검색·조회조건 변경 없음. 초기 줌 7.5와 중심좌표는 유지한다.
- 다른 앱은 선택 속성을 전달하지 않으므로 기존 기본값 false를 유지한다.
- 롤백은 이번 선택 속성 전달과 View 연결, 설명 주석만 제거한다. 사용자 변경이나 공통 minZoom은 되돌리지 않는다.
- 별도 테스트 파일·캡처 파일을 생성하지 않았다. 최종 문서만 보존하며 검증용 브라우저 탭은 종료한다.
@@ -0,0 +1,28 @@
# FE 도시침수지도 빈 결과 안내 작업카드
- 작성일: 2026-10-13 / 상태: 구현 완료 / 실서버·브라우저 검증 대기
- 근거: 사용자 요청. extent 위치 정보가 없으면 `위치 정보가 없습니다`로 안내한다.
## 변경 계획
- 목록 200·items=[]·total=0: 총 0건과 빈 표를 표시하고 이전 행·오류·로딩 상태를 정리한다.
- 단건·그룹 extent 200 응답에 위치 데이터가 없으면 `위치 정보가 없습니다`를 표시하고 지도 이동·선택 강조를 수행하지 않는다.
- 공통 응답에서 data가 null이거나 생략된 경우를 좌표 접근 전에 처리한다. 404 문자열로 위치 부재를 판단하는 기존 분기는 제거한다.
- 위치 데이터가 있으면 기존 지도 이동을 유지한다. 실제 HTTP 오류·잘못된 좌표는 기존 오류 처리로 구분한다.
## 수정 대상
- `app/(map-layout)/map/components/flood-dngr-map/flood-dngr-map-popup-page.tsx`: 목록·그룹 extent·안내 처리.
- sn 내 단건·그룹 extent 호출부(침수예측 현황·지도 포함): 검색으로 사용처를 확정하고 동일한 빈 결과 처리 적용.
- `src/shared/api/sewerage-api.ts`, `http-transport.ts`: null·data 생략 응답의 전달 방식 확인. 전역 오류 치환은 하지 않는다.
## 검증·영향
- 두 extent의 위치 없음: 200, 지정 안내 문구, 지도 이동 없음, null 접근 오류 없음.
- 위치 있음: 기존 이동 정상. 실제 404·401·403·5xx·timeout: 오류 안내 유지.
- 목록은 데이터 있음 → 0건 → 데이터 있음 재검색 시 행·건수·로딩이 일치한다.
- BE 성공 JSON의 data 생략·null 처리를 연동 확인한다. 빈 본문·비정상 응답을 임의로 위치 없음으로 처리하지 않는다.
- 영향은 sn의 extent 사용 화면. UI·DB 변경 없음. 롤백은 실제 수정한 기능 파일 복원.
- 남은 확인: 전체 extent 호출부와 실제 응답 형태·브라우저 동작.
FE 구현 및 타입 검사·클릭 처리 모의 검증 완료. 로그인 브라우저 검증은 미수행이다.
@@ -7,7 +7,7 @@ import Overlay from "ol/Overlay.js";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { getRawJson } from "@/shared/api/sewerage-api";
import { getData } from "@/shared/api/sewerage-api";
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/window-popup/model/types";
import { getFloodRiskGradeClassName } from "./flood-risk-grade";
@@ -251,11 +251,11 @@ export const useFloodPredictionMap = ({
removeFloodPredictionOverlay();
try {
const response = await getRawJson(
// 공통 응답의 data만 읽어 null·생략된 값을 빈 오버레이 결과로 처리한다.
const payload = await getData(
`/sewerage-service/api/v1/fldng/predc/prst/gridMap?sggCd=${encodeURIComponent(sggCd)}&analysisDateTime=${encodeURIComponent(analysisDateTime)}`,
);
if (!navigation?.isCurrent()) return;
const payload = response?.data ?? response;
const row =
payload && typeof payload === "object" && !Array.isArray(payload)
? (payload as FloodPredictionOverlayRow)
@@ -367,6 +367,11 @@ export default function FldngPredcPrstPopupContent() {
const extentData = await getData(
`/sewerage-service/api/v1/fldng/floodDngrMap/group/extent?${params.toString()}`,
);
// null·생략된 data는 정상적인 위치 부재이며 부모 지도 이동·강조를 요청하지 않는다.
if (extentData == null) {
alert("해당 도시침수지도가 없습니다.");
return;
}
const extent = [
extentData?.minX,
extentData?.minY,
@@ -374,7 +379,7 @@ export default function FldngPredcPrstPopupContent() {
extentData?.maxY,
].map(Number);
if (extent.some((value) => !Number.isFinite(value))) {
throw new Error("도시침수지도 위치 정보가 없습니다.");
throw new Error("도시침수지도 위치 응답의 좌표가 올바르지 않습니다.");
}
window.opener.postMessage(
{
+1 -1
View File
@@ -1,7 +1,7 @@
export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "SN";
export const PORT = process.env.NEXT_PUBLIC_PORT || "3000";
export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`;
export const SERVER_API_URL = process.env.SERVER_API_URL||'http://dev.onasys.co.kr:8000';
export const SERVER_API_URL = process.env.SERVER_API_URL||'https://apigateway.onasys.co.kr';
export const AUTH_LOGIN_REDIRECT_URL =
process.env.NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL || "/auth/login";
@@ -84,6 +84,8 @@ function Gis({
defaultLonLat = [127.4875, 37.4916],
viewExtent,
viewMinZoom,
// 지정 범위 전체까지 축소를 허용한다. 기본값은 기존 타 앱의 범위 제한을 유지한다.
viewShowFullExtent = false,
showZoomLevel = false,
toolBarIconSize,
zoomGuide,
@@ -176,6 +178,7 @@ function Gis({
minZoom: viewMinZoom ?? gisCore.defaults.minZoom,
maxZoom: gisCore.defaults.maxZoom,
extent: viewExtent,
showFullExtent: viewShowFullExtent,
}),
});
} else {